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 ImsVideoCallCallback( final LoggingBindings logger, final Call call, ImsVideoTech videoTech, VideoTechListener listener, Context context)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 onSessionModifyRequestReceived(VideoProfile videoProfile)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 onSessionModifyResponseReceived( int status, VideoProfile requestedProfile, VideoProfile responseProfile)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 final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status); 107 if (status == VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { 108 // Telecom manages audio route for us 109 listener.onUpgradedToVideo(false /* switchToSpeaker */); 110 } else { 111 // This will update the video UI to display the error message. 112 videoTech.setSessionModificationState(newSessionModificationState); 113 } 114 115 // If the other person accepted the upgrade request then this will keep the video UI up until 116 // the call's video state change. Without this we would switch to the voice call and then 117 // switch back to video UI. 118 clearFailedResponseState(newSessionModificationState); 119 } else if (videoTech.getSessionModificationState() 120 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { 121 requestedVideoState = VideoProfile.STATE_AUDIO_ONLY; 122 videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST); 123 } else if (videoTech.getSessionModificationState() 124 == SessionModificationState.WAITING_FOR_RESPONSE) { 125 final int newSessionModificationState = getSessionModificationStateFromTelecomStatus(status); 126 videoTech.setSessionModificationState(newSessionModificationState); 127 if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { 128 clearFailedResponseState(newSessionModificationState); 129 } 130 } else { 131 LogUtil.i( 132 "ImsVideoCallCallback.onSessionModifyResponseReceived", 133 "call is not waiting for response, doing nothing"); 134 } 135 } 136 clearFailedResponseState(final int newSessionModificationState)137 private void clearFailedResponseState(final int newSessionModificationState) { 138 handler.removeCallbacksAndMessages(null); // Clear everything 139 // Wait for 4 seconds and then clean the session modification state. This allows the video UI 140 // to stay up so that the user can read the error message. 141 handler.postDelayed( 142 () -> { 143 if (videoTech.getSessionModificationState() == newSessionModificationState) { 144 LogUtil.i("ImsVideoCallCallback.onSessionModifyResponseReceived", "clearing state"); 145 videoTech.setSessionModificationState(SessionModificationState.NO_REQUEST); 146 } else { 147 LogUtil.i( 148 "ImsVideoCallCallback.onSessionModifyResponseReceived", 149 "session modification state has changed, not clearing state"); 150 } 151 }, 152 CLEAR_FAILED_REQUEST_TIMEOUT_MILLIS); 153 } 154 155 @SessionModificationState getSessionModificationStateFromTelecomStatus(int telecomStatus)156 private int getSessionModificationStateFromTelecomStatus(int telecomStatus) { 157 switch (telecomStatus) { 158 case VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS: 159 return SessionModificationState.NO_REQUEST; 160 case VideoProvider.SESSION_MODIFY_REQUEST_FAIL: 161 case VideoProvider.SESSION_MODIFY_REQUEST_INVALID: 162 // Check if it's already video call, which means the request is not video upgrade request. 163 if (VideoProfile.isVideo(call.getDetails().getVideoState())) { 164 return SessionModificationState.REQUEST_FAILED; 165 } else { 166 return SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_FAILED; 167 } 168 case VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT: 169 return SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT; 170 case VideoProvider.SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE: 171 return SessionModificationState.REQUEST_REJECTED; 172 default: 173 LogUtil.e( 174 "ImsVideoCallCallback.getSessionModificationStateFromTelecomStatus", 175 "unknown status: %d", 176 telecomStatus); 177 return SessionModificationState.REQUEST_FAILED; 178 } 179 } 180 181 // In the vendor code rx_pause and rx_resume get triggered when the video player starts or stops 182 // playing the incoming video stream. For the case where you're resuming a held call, its 183 // definitely a good signal to use to know that the video is resuming (though the video state 184 // should change to indicate its not paused in this case as well). However, keep in mind you'll 185 // get these signals as well on carriers that don't support the video pause signalling (like TMO) 186 // so you want to ensure you don't send sessionModifyRequests with pause/resume based on these 187 // signals. Also, its technically possible to have a pause/resume if the video signal degrades. 188 @Override onCallSessionEvent(int event)189 public void onCallSessionEvent(int event) { 190 switch (event) { 191 case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE: 192 LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "rx_pause"); 193 break; 194 case Connection.VideoProvider.SESSION_EVENT_RX_RESUME: 195 LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "rx_resume"); 196 break; 197 case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE: 198 LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "camera_failure"); 199 break; 200 case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY: 201 LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "camera_ready"); 202 break; 203 default: 204 LogUtil.i("ImsVideoCallCallback.onCallSessionEvent", "unknown event = : " + event); 205 break; 206 } 207 } 208 209 @Override onPeerDimensionsChanged(int width, int height)210 public void onPeerDimensionsChanged(int width, int height) { 211 listener.onPeerDimensionsChanged(width, height); 212 } 213 214 @Override onVideoQualityChanged(int videoQuality)215 public void onVideoQualityChanged(int videoQuality) { 216 LogUtil.i("ImsVideoCallCallback.onVideoQualityChanged", "videoQuality: %d", videoQuality); 217 } 218 219 @Override onCallDataUsageChanged(long dataUsage)220 public void onCallDataUsageChanged(long dataUsage) { 221 LogUtil.i("ImsVideoCallCallback.onCallDataUsageChanged", "dataUsage: %d", dataUsage); 222 } 223 224 @Override onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities)225 public void onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities) { 226 if (cameraCapabilities != null) { 227 listener.onCameraDimensionsChanged( 228 cameraCapabilities.getWidth(), cameraCapabilities.getHeight()); 229 } 230 } 231 getRequestedVideoState()232 int getRequestedVideoState() { 233 return requestedVideoState; 234 } 235 } 236