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.NonNull;
     22 import android.support.annotation.Nullable;
     23 import android.support.annotation.VisibleForTesting;
     24 import android.telecom.Call;
     25 import android.telecom.Call.Details;
     26 import android.telecom.PhoneAccountHandle;
     27 import android.telecom.VideoProfile;
     28 import com.android.dialer.common.Assert;
     29 import com.android.dialer.common.LogUtil;
     30 import com.android.dialer.logging.DialerImpression;
     31 import com.android.dialer.logging.LoggingBindings;
     32 import com.android.dialer.util.CallUtil;
     33 import com.android.incallui.video.protocol.VideoCallScreen;
     34 import com.android.incallui.video.protocol.VideoCallScreenDelegate;
     35 import com.android.incallui.videotech.VideoTech;
     36 import com.android.incallui.videotech.utils.SessionModificationState;
     37 
     38 /** ViLTE implementation */
     39 public class ImsVideoTech implements VideoTech {
     40   private final LoggingBindings logger;
     41   private final Call call;
     42   private final VideoTechListener listener;
     43   @VisibleForTesting ImsVideoCallCallback callback;
     44   private @SessionModificationState int sessionModificationState =
     45       SessionModificationState.NO_REQUEST;
     46   private int previousVideoState = VideoProfile.STATE_AUDIO_ONLY;
     47   private boolean paused = false;
     48   private String savedCameraId;
     49 
     50   // Hold onto a flag of whether or not stopTransmission was called but resumeTransmission has not
     51   // been. This is needed because there is time between calling stopTransmission and
     52   // call.getDetails().getVideoState() reflecting the change. During that time, pause() and
     53   // unpause() will send the incorrect VideoProfile.
     54   private boolean transmissionStopped = false;
     55 
     56   public ImsVideoTech(LoggingBindings logger, VideoTechListener listener, Call call) {
     57     this.logger = logger;
     58     this.listener = listener;
     59     this.call = call;
     60   }
     61 
     62   @Override
     63   public boolean isAvailable(Context context, PhoneAccountHandle phoneAccountHandle) {
     64     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
     65       return false;
     66     }
     67 
     68     if (call.getVideoCall() == null) {
     69       return false;
     70     }
     71 
     72     // We are already in an IMS video call
     73     if (VideoProfile.isVideo(call.getDetails().getVideoState())) {
     74       return true;
     75     }
     76 
     77     // The user has disabled IMS video calling in system settings
     78     if (!CallUtil.isVideoEnabled(context)) {
     79       return false;
     80     }
     81 
     82     // The current call doesn't support transmitting video
     83     if (!call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX)) {
     84       return false;
     85     }
     86 
     87     // The current call remote device doesn't support receiving video
     88     if (!call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX)) {
     89       return false;
     90     }
     91 
     92     return true;
     93   }
     94 
     95   @Override
     96   public boolean isTransmittingOrReceiving() {
     97     return VideoProfile.isVideo(call.getDetails().getVideoState());
     98   }
     99 
    100   @Override
    101   public boolean isSelfManagedCamera() {
    102     // Return false to indicate that the answer UI shouldn't open the camera itself.
    103     // For IMS Video the modem is responsible for opening the camera.
    104     return false;
    105   }
    106 
    107   @Override
    108   public boolean shouldUseSurfaceView() {
    109     return false;
    110   }
    111 
    112   @Override
    113   public boolean isPaused() {
    114     return paused;
    115   }
    116 
    117   @Override
    118   public VideoCallScreenDelegate createVideoCallScreenDelegate(
    119       Context context, VideoCallScreen videoCallScreen) {
    120     // TODO move creating VideoCallPresenter here
    121     throw Assert.createUnsupportedOperationFailException();
    122   }
    123 
    124   @Override
    125   public void onCallStateChanged(
    126       Context context, int newState, PhoneAccountHandle phoneAccountHandle) {
    127     if (!isAvailable(context, phoneAccountHandle)) {
    128       return;
    129     }
    130 
    131     if (callback == null) {
    132       callback = new ImsVideoCallCallback(logger, call, this, listener, context);
    133       call.getVideoCall().registerCallback(callback);
    134     }
    135 
    136     if (getSessionModificationState()
    137             == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE
    138         && isTransmittingOrReceiving()) {
    139       // We don't clear the session modification state right away when we find out the video upgrade
    140       // request was accepted to avoid having the UI switch from video to voice to video.
    141       // Once the underlying telecom call updates to video mode it's safe to clear the state.
    142       LogUtil.i(
    143           "ImsVideoTech.onCallStateChanged",
    144           "upgraded to video, clearing session modification state");
    145       setSessionModificationState(SessionModificationState.NO_REQUEST);
    146     }
    147 
    148     // Determines if a received upgrade to video request should be cancelled. This can happen if
    149     // another InCall UI responds to the upgrade to video request.
    150     int newVideoState = call.getDetails().getVideoState();
    151     if (newVideoState != previousVideoState
    152         && sessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
    153       LogUtil.i("ImsVideoTech.onCallStateChanged", "cancelling upgrade notification");
    154       setSessionModificationState(SessionModificationState.NO_REQUEST);
    155     }
    156     previousVideoState = newVideoState;
    157   }
    158 
    159   @Override
    160   public void onRemovedFromCallList() {}
    161 
    162   @Override
    163   public int getSessionModificationState() {
    164     return sessionModificationState;
    165   }
    166 
    167   void setSessionModificationState(@SessionModificationState int state) {
    168     if (state != sessionModificationState) {
    169       LogUtil.i(
    170           "ImsVideoTech.setSessionModificationState", "%d -> %d", sessionModificationState, state);
    171       sessionModificationState = state;
    172       listener.onSessionModificationStateChanged();
    173     }
    174   }
    175 
    176   @Override
    177   public void upgradeToVideo(@NonNull Context context) {
    178     LogUtil.enterBlock("ImsVideoTech.upgradeToVideo");
    179 
    180     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    181     call.getVideoCall()
    182         .sendSessionModifyRequest(
    183             new VideoProfile(unpausedVideoState | VideoProfile.STATE_BIDIRECTIONAL));
    184     setSessionModificationState(SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE);
    185     logger.logImpression(DialerImpression.Type.IMS_VIDEO_UPGRADE_REQUESTED);
    186   }
    187 
    188   @Override
    189   public void acceptVideoRequest(@NonNull Context context) {
    190     int requestedVideoState = callback.getRequestedVideoState();
    191     Assert.checkArgument(requestedVideoState != VideoProfile.STATE_AUDIO_ONLY);
    192     LogUtil.i("ImsVideoTech.acceptUpgradeRequest", "videoState: " + requestedVideoState);
    193     call.getVideoCall().sendSessionModifyResponse(new VideoProfile(requestedVideoState));
    194     // Telecom manages audio route for us
    195     listener.onUpgradedToVideo(false /* switchToSpeaker */);
    196     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_ACCEPTED);
    197   }
    198 
    199   @Override
    200   public void acceptVideoRequestAsAudio() {
    201     LogUtil.enterBlock("ImsVideoTech.acceptVideoRequestAsAudio");
    202     call.getVideoCall().sendSessionModifyResponse(new VideoProfile(VideoProfile.STATE_AUDIO_ONLY));
    203     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_ACCEPTED_AS_AUDIO);
    204   }
    205 
    206   @Override
    207   public void declineVideoRequest() {
    208     LogUtil.enterBlock("ImsVideoTech.declineUpgradeRequest");
    209     call.getVideoCall()
    210         .sendSessionModifyResponse(new VideoProfile(call.getDetails().getVideoState()));
    211     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_DECLINED);
    212   }
    213 
    214   @Override
    215   public boolean isTransmitting() {
    216     return VideoProfile.isTransmissionEnabled(call.getDetails().getVideoState());
    217   }
    218 
    219   @Override
    220   public void stopTransmission() {
    221     LogUtil.enterBlock("ImsVideoTech.stopTransmission");
    222 
    223     transmissionStopped = true;
    224 
    225     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    226     call.getVideoCall()
    227         .sendSessionModifyRequest(
    228             new VideoProfile(unpausedVideoState & ~VideoProfile.STATE_TX_ENABLED));
    229   }
    230 
    231   @Override
    232   public void resumeTransmission(@NonNull Context context) {
    233     LogUtil.enterBlock("ImsVideoTech.resumeTransmission");
    234 
    235     transmissionStopped = false;
    236 
    237     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    238     call.getVideoCall()
    239         .sendSessionModifyRequest(
    240             new VideoProfile(unpausedVideoState | VideoProfile.STATE_TX_ENABLED));
    241     setSessionModificationState(SessionModificationState.WAITING_FOR_RESPONSE);
    242   }
    243 
    244   @Override
    245   public void pause() {
    246     if (call.getState() != Call.STATE_ACTIVE) {
    247       LogUtil.i("ImsVideoTech.pause", "not pausing because call is not active");
    248       return;
    249     }
    250 
    251     if (!isTransmittingOrReceiving()) {
    252       LogUtil.i("ImsVideoTech.pause", "not pausing because this is not a video call");
    253       return;
    254     }
    255 
    256     if (paused) {
    257       LogUtil.i("ImsVideoTech.pause", "already paused");
    258       return;
    259     }
    260 
    261     paused = true;
    262 
    263     if (canPause()) {
    264       LogUtil.i("ImsVideoTech.pause", "sending pause request");
    265       int pausedVideoState = call.getDetails().getVideoState() | VideoProfile.STATE_PAUSED;
    266       if (transmissionStopped && VideoProfile.isTransmissionEnabled(pausedVideoState)) {
    267         LogUtil.i("ImsVideoTech.pause", "overriding TX to false due to user request");
    268         pausedVideoState &= ~VideoProfile.STATE_TX_ENABLED;
    269       }
    270       call.getVideoCall().sendSessionModifyRequest(new VideoProfile(pausedVideoState));
    271     } else {
    272       // This video call does not support pause so we fall back to disabling the camera
    273       LogUtil.i("ImsVideoTech.pause", "disabling camera");
    274       call.getVideoCall().setCamera(null);
    275     }
    276   }
    277 
    278   @Override
    279   public void unpause() {
    280     if (call.getState() != Call.STATE_ACTIVE) {
    281       LogUtil.i("ImsVideoTech.unpause", "not unpausing because call is not active");
    282       return;
    283     }
    284 
    285     if (!isTransmittingOrReceiving()) {
    286       LogUtil.i("ImsVideoTech.unpause", "not unpausing because this is not a video call");
    287       return;
    288     }
    289 
    290     if (!paused) {
    291       LogUtil.i("ImsVideoTech.unpause", "already unpaused");
    292       return;
    293     }
    294 
    295     paused = false;
    296 
    297     if (canPause()) {
    298       LogUtil.i("ImsVideoTech.unpause", "sending unpause request");
    299       int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    300       if (transmissionStopped && VideoProfile.isTransmissionEnabled(unpausedVideoState)) {
    301         LogUtil.i("ImsVideoTech.unpause", "overriding TX to false due to user request");
    302         unpausedVideoState &= ~VideoProfile.STATE_TX_ENABLED;
    303       }
    304       call.getVideoCall().sendSessionModifyRequest(new VideoProfile(unpausedVideoState));
    305     } else {
    306       // This video call does not support pause so we fall back to re-enabling the camera
    307       LogUtil.i("ImsVideoTech.pause", "re-enabling camera");
    308       setCamera(savedCameraId);
    309     }
    310   }
    311 
    312   @Override
    313   public void setCamera(@Nullable String cameraId) {
    314     savedCameraId = cameraId;
    315     call.getVideoCall().setCamera(cameraId);
    316     call.getVideoCall().requestCameraCapabilities();
    317   }
    318 
    319   @Override
    320   public void setDeviceOrientation(int rotation) {
    321     call.getVideoCall().setDeviceOrientation(rotation);
    322   }
    323 
    324   @Override
    325   public void becomePrimary() {
    326     listener.onImpressionLoggingNeeded(
    327         DialerImpression.Type.UPGRADE_TO_VIDEO_CALL_BUTTON_SHOWN_FOR_IMS);
    328   }
    329 
    330   @Override
    331   public com.android.dialer.logging.VideoTech.Type getVideoTechType() {
    332     return com.android.dialer.logging.VideoTech.Type.IMS_VIDEO_TECH;
    333   }
    334 
    335   private boolean canPause() {
    336     return call.getDetails().can(Details.CAPABILITY_CAN_PAUSE_VIDEO);
    337   }
    338 
    339   static int getUnpausedVideoState(int videoState) {
    340     return videoState & (~VideoProfile.STATE_PAUSED);
    341   }
    342 }
    343