Home | History | Annotate | Download | only in ims
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.incallui.videotech.ims;
     18 
     19 import android.content.Context;
     20 import android.os.Build;
     21 import android.support.annotation.Nullable;
     22 import android.telecom.Call;
     23 import android.telecom.Call.Details;
     24 import android.telecom.VideoProfile;
     25 import com.android.dialer.common.Assert;
     26 import com.android.dialer.common.LogUtil;
     27 import com.android.dialer.logging.DialerImpression;
     28 import com.android.dialer.logging.LoggingBindings;
     29 import com.android.dialer.util.CallUtil;
     30 import com.android.incallui.video.protocol.VideoCallScreen;
     31 import com.android.incallui.video.protocol.VideoCallScreenDelegate;
     32 import com.android.incallui.videotech.VideoTech;
     33 import com.android.incallui.videotech.utils.SessionModificationState;
     34 
     35 /** ViLTE implementation */
     36 public class ImsVideoTech implements VideoTech {
     37   private final LoggingBindings logger;
     38   private final Call call;
     39   private final VideoTechListener listener;
     40   private ImsVideoCallCallback callback;
     41   private @SessionModificationState int sessionModificationState =
     42       SessionModificationState.NO_REQUEST;
     43   private int previousVideoState = VideoProfile.STATE_AUDIO_ONLY;
     44   private boolean paused = false;
     45 
     46   // Hold onto a flag of whether or not stopTransmission was called but resumeTransmission has not
     47   // been. This is needed because there is time between calling stopTransmission and
     48   // call.getDetails().getVideoState() reflecting the change. During that time, pause() and
     49   // unpause() will send the incorrect VideoProfile.
     50   private boolean transmissionStopped = false;
     51 
     52   public ImsVideoTech(LoggingBindings logger, VideoTechListener listener, Call call) {
     53     this.logger = logger;
     54     this.listener = listener;
     55     this.call = call;
     56   }
     57 
     58   @Override
     59   public boolean isAvailable(Context context) {
     60     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
     61       return false;
     62     }
     63 
     64     if (call.getVideoCall() == null) {
     65       return false;
     66     }
     67 
     68     // We are already in an IMS video call
     69     if (VideoProfile.isVideo(call.getDetails().getVideoState())) {
     70       return true;
     71     }
     72 
     73     // The user has disabled IMS video calling in system settings
     74     if (!CallUtil.isVideoEnabled(context)) {
     75       return false;
     76     }
     77 
     78     // The current call doesn't support transmitting video
     79     if (!call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX)) {
     80       return false;
     81     }
     82 
     83     // The current call remote device doesn't support receiving video
     84     if (!call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX)) {
     85       return false;
     86     }
     87 
     88     return true;
     89   }
     90 
     91   @Override
     92   public boolean isTransmittingOrReceiving() {
     93     return VideoProfile.isVideo(call.getDetails().getVideoState());
     94   }
     95 
     96   @Override
     97   public boolean isSelfManagedCamera() {
     98     // Return false to indicate that the answer UI shouldn't open the camera itself.
     99     // For IMS Video the modem is responsible for opening the camera.
    100     return false;
    101   }
    102 
    103   @Override
    104   public boolean shouldUseSurfaceView() {
    105     return false;
    106   }
    107 
    108   @Override
    109   public VideoCallScreenDelegate createVideoCallScreenDelegate(
    110       Context context, VideoCallScreen videoCallScreen) {
    111     // TODO move creating VideoCallPresenter here
    112     throw Assert.createUnsupportedOperationFailException();
    113   }
    114 
    115   @Override
    116   public void onCallStateChanged(Context context, int newState) {
    117     if (!isAvailable(context)) {
    118       return;
    119     }
    120 
    121     if (callback == null) {
    122       callback = new ImsVideoCallCallback(logger, call, this, listener);
    123       call.getVideoCall().registerCallback(callback);
    124     }
    125 
    126     if (getSessionModificationState()
    127             == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE
    128         && isTransmittingOrReceiving()) {
    129       // We don't clear the session modification state right away when we find out the video upgrade
    130       // request was accepted to avoid having the UI switch from video to voice to video.
    131       // Once the underlying telecom call updates to video mode it's safe to clear the state.
    132       LogUtil.i(
    133           "ImsVideoTech.onCallStateChanged",
    134           "upgraded to video, clearing session modification state");
    135       setSessionModificationState(SessionModificationState.NO_REQUEST);
    136     }
    137 
    138     // Determines if a received upgrade to video request should be cancelled. This can happen if
    139     // another InCall UI responds to the upgrade to video request.
    140     int newVideoState = call.getDetails().getVideoState();
    141     if (newVideoState != previousVideoState
    142         && sessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
    143       LogUtil.i("ImsVideoTech.onCallStateChanged", "cancelling upgrade notification");
    144       setSessionModificationState(SessionModificationState.NO_REQUEST);
    145     }
    146     previousVideoState = newVideoState;
    147   }
    148 
    149   @Override
    150   public void onRemovedFromCallList() {}
    151 
    152   @Override
    153   public int getSessionModificationState() {
    154     return sessionModificationState;
    155   }
    156 
    157   void setSessionModificationState(@SessionModificationState int state) {
    158     if (state != sessionModificationState) {
    159       LogUtil.i(
    160           "ImsVideoTech.setSessionModificationState", "%d -> %d", sessionModificationState, state);
    161       sessionModificationState = state;
    162       listener.onSessionModificationStateChanged();
    163     }
    164   }
    165 
    166   @Override
    167   public void upgradeToVideo() {
    168     LogUtil.enterBlock("ImsVideoTech.upgradeToVideo");
    169 
    170     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    171     call.getVideoCall()
    172         .sendSessionModifyRequest(
    173             new VideoProfile(unpausedVideoState | VideoProfile.STATE_BIDIRECTIONAL));
    174     setSessionModificationState(SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE);
    175     logger.logImpression(DialerImpression.Type.IMS_VIDEO_UPGRADE_REQUESTED);
    176   }
    177 
    178   @Override
    179   public void acceptVideoRequest() {
    180     int requestedVideoState = callback.getRequestedVideoState();
    181     Assert.checkArgument(requestedVideoState != VideoProfile.STATE_AUDIO_ONLY);
    182     LogUtil.i("ImsVideoTech.acceptUpgradeRequest", "videoState: " + requestedVideoState);
    183     call.getVideoCall().sendSessionModifyResponse(new VideoProfile(requestedVideoState));
    184     setSessionModificationState(SessionModificationState.NO_REQUEST);
    185     // Telecom manages audio route for us
    186     listener.onUpgradedToVideo(false /* switchToSpeaker */);
    187     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_ACCEPTED);
    188   }
    189 
    190   @Override
    191   public void acceptVideoRequestAsAudio() {
    192     LogUtil.enterBlock("ImsVideoTech.acceptVideoRequestAsAudio");
    193     call.getVideoCall().sendSessionModifyResponse(new VideoProfile(VideoProfile.STATE_AUDIO_ONLY));
    194     setSessionModificationState(SessionModificationState.NO_REQUEST);
    195     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_ACCEPTED_AS_AUDIO);
    196   }
    197 
    198   @Override
    199   public void declineVideoRequest() {
    200     LogUtil.enterBlock("ImsVideoTech.declineUpgradeRequest");
    201     call.getVideoCall()
    202         .sendSessionModifyResponse(new VideoProfile(call.getDetails().getVideoState()));
    203     setSessionModificationState(SessionModificationState.NO_REQUEST);
    204     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_DECLINED);
    205   }
    206 
    207   @Override
    208   public boolean isTransmitting() {
    209     return VideoProfile.isTransmissionEnabled(call.getDetails().getVideoState());
    210   }
    211 
    212   @Override
    213   public void stopTransmission() {
    214     LogUtil.enterBlock("ImsVideoTech.stopTransmission");
    215 
    216     transmissionStopped = true;
    217 
    218     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    219     call.getVideoCall()
    220         .sendSessionModifyRequest(
    221             new VideoProfile(unpausedVideoState & ~VideoProfile.STATE_TX_ENABLED));
    222   }
    223 
    224   @Override
    225   public void resumeTransmission() {
    226     LogUtil.enterBlock("ImsVideoTech.resumeTransmission");
    227 
    228     transmissionStopped = false;
    229 
    230     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    231     call.getVideoCall()
    232         .sendSessionModifyRequest(
    233             new VideoProfile(unpausedVideoState | VideoProfile.STATE_TX_ENABLED));
    234     setSessionModificationState(SessionModificationState.WAITING_FOR_RESPONSE);
    235   }
    236 
    237   @Override
    238   public void pause() {
    239     if (canPause() && !paused) {
    240       LogUtil.i("ImsVideoTech.pause", "sending pause request");
    241       paused = true;
    242       int pausedVideoState = call.getDetails().getVideoState() | VideoProfile.STATE_PAUSED;
    243       if (transmissionStopped && VideoProfile.isTransmissionEnabled(pausedVideoState)) {
    244         LogUtil.i("ImsVideoTech.pause", "overriding TX to false due to user request");
    245         pausedVideoState &= ~VideoProfile.STATE_TX_ENABLED;
    246       }
    247       call.getVideoCall().sendSessionModifyRequest(new VideoProfile(pausedVideoState));
    248     } else {
    249       LogUtil.i(
    250           "ImsVideoTech.pause",
    251           "not sending request: canPause: %b, paused: %b",
    252           canPause(),
    253           paused);
    254     }
    255   }
    256 
    257   @Override
    258   public void unpause() {
    259     if (canPause() && paused) {
    260       LogUtil.i("ImsVideoTech.unpause", "sending unpause request");
    261       paused = false;
    262       int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    263       if (transmissionStopped && VideoProfile.isTransmissionEnabled(unpausedVideoState)) {
    264         LogUtil.i("ImsVideoTech.unpause", "overriding TX to false due to user request");
    265         unpausedVideoState &= ~VideoProfile.STATE_TX_ENABLED;
    266       }
    267       call.getVideoCall().sendSessionModifyRequest(new VideoProfile(unpausedVideoState));
    268     } else {
    269       LogUtil.i(
    270           "ImsVideoTech.unpause",
    271           "not sending request: canPause: %b, paused: %b",
    272           canPause(),
    273           paused);
    274     }
    275   }
    276 
    277   @Override
    278   public void setCamera(@Nullable String cameraId) {
    279     call.getVideoCall().setCamera(cameraId);
    280     call.getVideoCall().requestCameraCapabilities();
    281   }
    282 
    283   @Override
    284   public void setDeviceOrientation(int rotation) {
    285     call.getVideoCall().setDeviceOrientation(rotation);
    286   }
    287 
    288   private boolean canPause() {
    289     return call.getDetails().can(Details.CAPABILITY_CAN_PAUSE_VIDEO)
    290         && call.getState() == Call.STATE_ACTIVE
    291         && isTransmittingOrReceiving();
    292   }
    293 
    294   static int getUnpausedVideoState(int videoState) {
    295     return videoState & (~VideoProfile.STATE_PAUSED);
    296   }
    297 }
    298