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.telecom.Call;
     22 import android.telecom.Call.Details;
     23 import android.telecom.VideoProfile;
     24 import com.android.dialer.common.Assert;
     25 import com.android.dialer.common.LogUtil;
     26 import com.android.dialer.logging.DialerImpression;
     27 import com.android.dialer.logging.LoggingBindings;
     28 import com.android.incallui.video.protocol.VideoCallScreen;
     29 import com.android.incallui.video.protocol.VideoCallScreenDelegate;
     30 import com.android.incallui.videotech.VideoTech;
     31 import com.android.incallui.videotech.utils.SessionModificationState;
     32 
     33 /** ViLTE implementation */
     34 public class ImsVideoTech implements VideoTech {
     35   private final LoggingBindings logger;
     36   private final Call call;
     37   private final VideoTechListener listener;
     38   private ImsVideoCallCallback callback;
     39   private @SessionModificationState int sessionModificationState =
     40       SessionModificationState.NO_REQUEST;
     41   private int previousVideoState = VideoProfile.STATE_AUDIO_ONLY;
     42   private boolean paused = false;
     43 
     44   public ImsVideoTech(LoggingBindings logger, VideoTechListener listener, Call call) {
     45     this.logger = logger;
     46     this.listener = listener;
     47     this.call = call;
     48   }
     49 
     50   @Override
     51   public boolean isAvailable(Context context) {
     52     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
     53       return false;
     54     }
     55 
     56     boolean hasCapabilities =
     57         call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX)
     58             && call.getDetails().can(Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX);
     59 
     60     return call.getVideoCall() != null
     61         && (hasCapabilities || VideoProfile.isVideo(call.getDetails().getVideoState()));
     62   }
     63 
     64   @Override
     65   public boolean isTransmittingOrReceiving() {
     66     return VideoProfile.isVideo(call.getDetails().getVideoState());
     67   }
     68 
     69   @Override
     70   public boolean isSelfManagedCamera() {
     71     // Return false to indicate that the answer UI shouldn't open the camera itself.
     72     // For IMS Video the modem is responsible for opening the camera.
     73     return false;
     74   }
     75 
     76   @Override
     77   public boolean shouldUseSurfaceView() {
     78     return false;
     79   }
     80 
     81   @Override
     82   public VideoCallScreenDelegate createVideoCallScreenDelegate(
     83       Context context, VideoCallScreen videoCallScreen) {
     84     // TODO move creating VideoCallPresenter here
     85     throw Assert.createUnsupportedOperationFailException();
     86   }
     87 
     88   @Override
     89   public void onCallStateChanged(Context context, int newState) {
     90     if (!isAvailable(context)) {
     91       return;
     92     }
     93 
     94     if (callback == null) {
     95       callback = new ImsVideoCallCallback(logger, call, this, listener);
     96       call.getVideoCall().registerCallback(callback);
     97     }
     98 
     99     if (getSessionModificationState()
    100             == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE
    101         && isTransmittingOrReceiving()) {
    102       // We don't clear the session modification state right away when we find out the video upgrade
    103       // request was accepted to avoid having the UI switch from video to voice to video.
    104       // Once the underlying telecom call updates to video mode it's safe to clear the state.
    105       LogUtil.i(
    106           "ImsVideoTech.onCallStateChanged",
    107           "upgraded to video, clearing session modification state");
    108       setSessionModificationState(SessionModificationState.NO_REQUEST);
    109     }
    110 
    111     // Determines if a received upgrade to video request should be cancelled. This can happen if
    112     // another InCall UI responds to the upgrade to video request.
    113     int newVideoState = call.getDetails().getVideoState();
    114     if (newVideoState != previousVideoState
    115         && sessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
    116       LogUtil.i("ImsVideoTech.onCallStateChanged", "cancelling upgrade notification");
    117       setSessionModificationState(SessionModificationState.NO_REQUEST);
    118     }
    119     previousVideoState = newVideoState;
    120   }
    121 
    122   @Override
    123   public int getSessionModificationState() {
    124     return sessionModificationState;
    125   }
    126 
    127   void setSessionModificationState(@SessionModificationState int state) {
    128     if (state != sessionModificationState) {
    129       LogUtil.i(
    130           "ImsVideoTech.setSessionModificationState", "%d -> %d", sessionModificationState, state);
    131       sessionModificationState = state;
    132       listener.onSessionModificationStateChanged();
    133     }
    134   }
    135 
    136   @Override
    137   public void upgradeToVideo() {
    138     LogUtil.enterBlock("ImsVideoTech.upgradeToVideo");
    139 
    140     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    141     call.getVideoCall()
    142         .sendSessionModifyRequest(
    143             new VideoProfile(unpausedVideoState | VideoProfile.STATE_BIDIRECTIONAL));
    144     setSessionModificationState(SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE);
    145     logger.logImpression(DialerImpression.Type.IMS_VIDEO_UPGRADE_REQUESTED);
    146   }
    147 
    148   @Override
    149   public void acceptVideoRequest() {
    150     int requestedVideoState = callback.getRequestedVideoState();
    151     Assert.checkArgument(requestedVideoState != VideoProfile.STATE_AUDIO_ONLY);
    152     LogUtil.i("ImsVideoTech.acceptUpgradeRequest", "videoState: " + requestedVideoState);
    153     call.getVideoCall().sendSessionModifyResponse(new VideoProfile(requestedVideoState));
    154     setSessionModificationState(SessionModificationState.NO_REQUEST);
    155     // Telecom manages audio route for us
    156     listener.onUpgradedToVideo(false /* switchToSpeaker */);
    157     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_ACCEPTED);
    158   }
    159 
    160   @Override
    161   public void acceptVideoRequestAsAudio() {
    162     LogUtil.enterBlock("ImsVideoTech.acceptVideoRequestAsAudio");
    163     call.getVideoCall().sendSessionModifyResponse(new VideoProfile(VideoProfile.STATE_AUDIO_ONLY));
    164     setSessionModificationState(SessionModificationState.NO_REQUEST);
    165     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_ACCEPTED_AS_AUDIO);
    166   }
    167 
    168   @Override
    169   public void declineVideoRequest() {
    170     LogUtil.enterBlock("ImsVideoTech.declineUpgradeRequest");
    171     call.getVideoCall()
    172         .sendSessionModifyResponse(new VideoProfile(call.getDetails().getVideoState()));
    173     setSessionModificationState(SessionModificationState.NO_REQUEST);
    174     logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_DECLINED);
    175   }
    176 
    177   @Override
    178   public boolean isTransmitting() {
    179     return VideoProfile.isTransmissionEnabled(call.getDetails().getVideoState());
    180   }
    181 
    182   @Override
    183   public void stopTransmission() {
    184     LogUtil.enterBlock("ImsVideoTech.stopTransmission");
    185 
    186     setCamera(null);
    187 
    188     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    189     call.getVideoCall()
    190         .sendSessionModifyRequest(
    191             new VideoProfile(unpausedVideoState & ~VideoProfile.STATE_TX_ENABLED));
    192   }
    193 
    194   @Override
    195   public void resumeTransmission() {
    196     LogUtil.enterBlock("ImsVideoTech.resumeTransmission");
    197 
    198     int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    199     call.getVideoCall()
    200         .sendSessionModifyRequest(
    201             new VideoProfile(unpausedVideoState | VideoProfile.STATE_TX_ENABLED));
    202     setSessionModificationState(SessionModificationState.WAITING_FOR_RESPONSE);
    203   }
    204 
    205   @Override
    206   public void pause() {
    207     if (canPause() && !paused) {
    208       LogUtil.i("ImsVideoTech.pause", "sending pause request");
    209       paused = true;
    210       int pausedVideoState = call.getDetails().getVideoState() | VideoProfile.STATE_PAUSED;
    211       call.getVideoCall().sendSessionModifyRequest(new VideoProfile(pausedVideoState));
    212     } else {
    213       LogUtil.i(
    214           "ImsVideoTech.pause",
    215           "not sending request: canPause: %b, paused: %b",
    216           canPause(),
    217           paused);
    218     }
    219   }
    220 
    221   @Override
    222   public void unpause() {
    223     if (canPause() && paused) {
    224       LogUtil.i("ImsVideoTech.unpause", "sending unpause request");
    225       paused = false;
    226       int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
    227       call.getVideoCall().sendSessionModifyRequest(new VideoProfile(unpausedVideoState));
    228     } else {
    229       LogUtil.i(
    230           "ImsVideoTech.unpause",
    231           "not sending request: canPause: %b, paused: %b",
    232           canPause(),
    233           paused);
    234     }
    235   }
    236 
    237   @Override
    238   public void setCamera(String cameraId) {
    239     call.getVideoCall().setCamera(cameraId);
    240     call.getVideoCall().requestCameraCapabilities();
    241   }
    242 
    243   @Override
    244   public void setDeviceOrientation(int rotation) {
    245     call.getVideoCall().setDeviceOrientation(rotation);
    246   }
    247 
    248   private boolean canPause() {
    249     return call.getDetails().can(Details.CAPABILITY_CAN_PAUSE_VIDEO)
    250         && call.getState() == Call.STATE_ACTIVE
    251         && isTransmitting();
    252   }
    253 
    254   static int getUnpausedVideoState(int videoState) {
    255     return videoState & (~VideoProfile.STATE_PAUSED);
    256   }
    257 }
    258