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.Handler;
     21 import android.telecom.Call;
     22 import android.telecom.Connection;
     23 import android.telecom.Connection.VideoProvider;
     24 import android.telecom.InCallService.VideoCall;
     25 import android.telecom.VideoProfile;
     26 import android.telecom.VideoProfile.CameraCapabilities;
     27 import com.android.dialer.common.LogUtil;
     28 import com.android.dialer.logging.DialerImpression;
     29 import com.android.dialer.logging.LoggingBindings;
     30 import com.android.incallui.videotech.VideoTech.VideoTechListener;
     31 import com.android.incallui.videotech.utils.SessionModificationState;
     32 
     33 /** Receives IMS video call state updates. */
     34 public class ImsVideoCallCallback extends VideoCall.Callback {
     35   private static final int CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS = 4000;
     36   private final Handler handler = new Handler();
     37   private final LoggingBindings logger;
     38   private final Call call;
     39   private final ImsVideoTech videoTech;
     40   private final VideoTechListener listener;
     41   private final Context context;
     42   private int requestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
     43 
     44   ImsVideoCallCallback(
     45       final LoggingBindings logger,
     46       final Call call,
     47       ImsVideoTech videoTech,
     48       VideoTechListener listener,
     49       Context context) {
     50     this.logger = logger;
     51     this.call = call;
     52     this.videoTech = videoTech;
     53     this.listener = listener;
     54     this.context = context;
     55   }
     56 
     57   @Override
     58   public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
     59     LogUtil.i(
     60         "ImsVideoCallCallback.onSessionModifyRequestReceived", "videoProfile: " + videoProfile);
     61 
     62     int previousVideoState = ImsVideoTech.getUnpausedVideoState(call.getDetails().getVideoState());
     63     int newVideoState = ImsVideoTech.getUnpausedVideoState(videoProfile.getVideoState());
     64 
     65     boolean wasVideoCall = VideoProfile.isVideo(previousVideoState);
     66     boolean isVideoCall = VideoProfile.isVideo(newVideoState);
     67 
     68     if (wasVideoCall && !isVideoCall) {
     69       LogUtil.i(
     70           "ImsVideoTech.onSessionModifyRequestReceived", "call downgraded to %d", newVideoState);
     71     } else if (previousVideoState != newVideoState) {
     72       requestedVideoState = newVideoState;
     73       if (!wasVideoCall) {
     74         videoTech.setSessionModificationState(
     75             SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST);
     76         listener.onVideoUpgradeRequestReceived();
     77         logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_RECEIVED);
     78       } else {
     79         LogUtil.i(
     80             "ImsVideoTech.onSessionModifyRequestReceived", "call updated to %d", newVideoState);
     81         videoTech.acceptVideoRequest(context);
     82       }
     83     }
     84   }
     85 
     86   /**
     87    * @param status Status of the session modify request. Valid values are {@link
     88    *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS}, {@link
     89    *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL}, {@link
     90    *     Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
     91    * @param responseProfile The actual profile changes made by the peer device.
     92    */
     93   @Override
     94   public void onSessionModifyResponseReceived(
     95       int status, VideoProfile requestedProfile, VideoProfile responseProfile) {
     96     LogUtil.i(
     97         "ImsVideoCallCallback.onSessionModifyResponseReceived",
     98         "status: %d, requestedProfile: %s, responseProfile: %s, session modification state: %d",
     99         status,
    100         requestedProfile,
    101         responseProfile,
    102         videoTech.getSessionModificationState());
    103 
    104     if (videoTech.getSessionModificationState()
    105         == SessionModificationState.WAITING_FOR_UPGRADE_TO_VIDEO_RESPONSE) {
    106       handler.removeCallbacksAndMessages(null); // Clear everything
    107 
    108       final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status);
    109       if (status == VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
    110         // Telecom manages audio route for us
    111         listener.onUpgradedToVideo(false /* switchToSpeaker */);
    112       } else {
    113         // This will update the video UI to display the error message.
    114         videoTech.setSessionModificationState(newSessionModificationState);
    115       }
    116 
    117       // Wait for 4 seconds and then clean the session modification state. This allows the video UI
    118       // to stay up so that the user can read the error message.
    119       //
    120       // If the other person accepted the upgrade request then this will keep the video UI up until
    121       // the call's video state change. Without this we would switch to the voice call and then
    122       // switch back to video UI.
    123       handler.postDelayed(
    124           () -> {
    125             if (videoTech.getSessionModificationState() == newSessionModificationState) {
    126               LogUtil.i("ImsVideoCallCallback.onSessionModifyResponseReceived", "clearing state");
    127               videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
    128             } else {
    129               LogUtil.i(
    130                   "ImsVideoCallCallback.onSessionModifyResponseReceived",
    131                   "session modification state has changed, not clearing state");
    132             }
    133           },
    134           CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS);
    135     } else if (videoTech.getSessionModificationState()
    136         == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
    137       requestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
    138       videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST);
    139     } else if (videoTech.getSessionModificationState()
    140         == SessionModificationState.WAITING_FOR_RESPONSE) {
    141       videoTech.setSessionModificationState(getSessionModificationStateFromTelecomStatus(status));
    142     } else {
    143       LogUtil.i(
    144           "ImsVideoCallCallback.onSessionModifyResponseReceived",
    145           "call is not waiting for response, doing nothing");
    146     }
    147   }
    148 
    149   @SessionModificationState
    150   private int getSessionModificationStateFromTelecomStatus(int telecomStatus) {
    151     switch (telecomStatus) {
    152       case VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS:
    153         return SessionModificationState.NO_REQUEST;
    154       case VideoProvider.SESSION_MODIFY_REQUEST_FAIL:
    155       case VideoProvider.SESSION_MODIFY_REQUEST_INVALID:
    156         // Check if it's already video call, which means the request is not video upgrade request.
    157         if (VideoProfile.isVideo(call.getDetails().getVideoState())) {
    158           return SessionModificationState.REQUEST_FAILED;
    159         } else {
    160           return SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_FAILED;
    161         }
    162       case VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT:
    163         return SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT;
    164       case VideoProvider.SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE:
    165         return SessionModificationState.REQUEST_REJECTED;
    166       default:
    167         LogUtil.e(
    168             "ImsVideoCallCallback.getSessionModificationStateFromTelecomStatus",
    169             "unknown status: %d",
    170             telecomStatus);
    171         return SessionModificationState.REQUEST_FAILED;
    172     }
    173   }
    174 
    175   // In the vendor code rx_pause and rx_resume get triggered when the video player starts or stops
    176   // playing the incoming video stream.  For the case where you're resuming a held call, its
    177   // definitely a good signal to use to know that the video is resuming (though the video state
    178   // should change to indicate its not paused in this case as well).  However, keep in mind you'll
    179   // get these signals as well on carriers that don't support the video pause signalling (like TMO)
    180   // so you want to ensure you don't send sessionModifyRequests with pause/resume based on these
    181   // signals. Also, its technically possible to have a pause/resume if the video signal degrades.
    182   @Override
    183   public void onCallSessionEvent(int event) {
    184     switch (event) {
    185       case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE:
    186         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "rx_pause");
    187         break;
    188       case Connection.VideoProvider.SESSION_EVENT_RX_RESUME:
    189         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "rx_resume");
    190         break;
    191       case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
    192         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "camera_failure");
    193         break;
    194       case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY:
    195         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "camera_ready");
    196         break;
    197       default:
    198         LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "unknown event = : " + event);
    199         break;
    200     }
    201   }
    202 
    203   @Override
    204   public void onPeerDimensionsChanged(int width, int height) {
    205     listener.onPeerDimensionsChanged(width, height);
    206   }
    207 
    208   @Override
    209   public void onVideoQualityChanged(int videoQuality) {
    210     LogUtil.i("ImsVideoCallCallback.onVideoQualityChanged", "videoQuality: %d", videoQuality);
    211   }
    212 
    213   @Override
    214   public void onCallDataUsageChanged(long dataUsage) {
    215     LogUtil.i("ImsVideoCallCallback.onCallDataUsageChanged", "dataUsage: %d", dataUsage);
    216   }
    217 
    218   @Override
    219   public void onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities) {
    220     if (cameraCapabilities != null) {
    221       listener.onCameraDimensionsChanged(
    222           cameraCapabilities.getWidth(), cameraCapabilities.getHeight());
    223     }
    224   }
    225 
    226   int getRequestedVideoState() {
    227     return requestedVideoState;
    228   }
    229 }
    230