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