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