1 /* 2 * Copyright (C) 2014 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 android.telecom; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.net.Uri; 21 import android.os.Build; 22 import android.os.Handler; 23 import android.os.IBinder; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.RemoteException; 27 import android.telecom.InCallService.VideoCall; 28 import android.view.Surface; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.os.SomeArgs; 32 import com.android.internal.telecom.IVideoCallback; 33 import com.android.internal.telecom.IVideoProvider; 34 35 import java.util.NoSuchElementException; 36 37 /** 38 * Implementation of a Video Call, which allows InCallUi to communicate commands to the underlying 39 * {@link Connection.VideoProvider}, and direct callbacks from the 40 * {@link Connection.VideoProvider} to the appropriate {@link VideoCall.Listener}. 41 * 42 * {@hide} 43 */ 44 public class VideoCallImpl extends VideoCall { 45 46 private final IVideoProvider mVideoProvider; 47 private final VideoCallListenerBinder mBinder; 48 private VideoCall.Callback mCallback; 49 private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN; 50 private int mVideoState = VideoProfile.STATE_AUDIO_ONLY; 51 private final String mCallingPackageName; 52 53 private int mTargetSdkVersion; 54 55 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { 56 @Override 57 public void binderDied() { 58 try { 59 mVideoProvider.asBinder().unlinkToDeath(this, 0); 60 } catch (NoSuchElementException nse) { 61 // Already unlinked in destroy below. 62 } 63 } 64 }; 65 66 /** 67 * IVideoCallback stub implementation. 68 */ 69 private final class VideoCallListenerBinder extends IVideoCallback.Stub { 70 @Override receiveSessionModifyRequest(VideoProfile videoProfile)71 public void receiveSessionModifyRequest(VideoProfile videoProfile) { 72 if (mHandler == null) { 73 return; 74 } 75 mHandler.obtainMessage(MessageHandler.MSG_RECEIVE_SESSION_MODIFY_REQUEST, 76 videoProfile).sendToTarget(); 77 78 } 79 80 @Override receiveSessionModifyResponse(int status, VideoProfile requestProfile, VideoProfile responseProfile)81 public void receiveSessionModifyResponse(int status, VideoProfile requestProfile, 82 VideoProfile responseProfile) { 83 if (mHandler == null) { 84 return; 85 } 86 SomeArgs args = SomeArgs.obtain(); 87 args.arg1 = status; 88 args.arg2 = requestProfile; 89 args.arg3 = responseProfile; 90 mHandler.obtainMessage(MessageHandler.MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args) 91 .sendToTarget(); 92 } 93 94 @Override handleCallSessionEvent(int event)95 public void handleCallSessionEvent(int event) { 96 if (mHandler == null) { 97 return; 98 } 99 mHandler.obtainMessage(MessageHandler.MSG_HANDLE_CALL_SESSION_EVENT, event) 100 .sendToTarget(); 101 } 102 103 @Override changePeerDimensions(int width, int height)104 public void changePeerDimensions(int width, int height) { 105 if (mHandler == null) { 106 return; 107 } 108 SomeArgs args = SomeArgs.obtain(); 109 args.arg1 = width; 110 args.arg2 = height; 111 mHandler.obtainMessage(MessageHandler.MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget(); 112 } 113 114 @Override changeVideoQuality(int videoQuality)115 public void changeVideoQuality(int videoQuality) { 116 if (mHandler == null) { 117 return; 118 } 119 mHandler.obtainMessage(MessageHandler.MSG_CHANGE_VIDEO_QUALITY, videoQuality, 0) 120 .sendToTarget(); 121 } 122 123 @Override changeCallDataUsage(long dataUsage)124 public void changeCallDataUsage(long dataUsage) { 125 if (mHandler == null) { 126 return; 127 } 128 mHandler.obtainMessage(MessageHandler.MSG_CHANGE_CALL_DATA_USAGE, dataUsage) 129 .sendToTarget(); 130 } 131 132 @Override changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities)133 public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) { 134 if (mHandler == null) { 135 return; 136 } 137 mHandler.obtainMessage(MessageHandler.MSG_CHANGE_CAMERA_CAPABILITIES, 138 cameraCapabilities).sendToTarget(); 139 } 140 } 141 142 /** Default handler used to consolidate binder method calls onto a single thread. */ 143 private final class MessageHandler extends Handler { 144 private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 1; 145 private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 2; 146 private static final int MSG_HANDLE_CALL_SESSION_EVENT = 3; 147 private static final int MSG_CHANGE_PEER_DIMENSIONS = 4; 148 private static final int MSG_CHANGE_CALL_DATA_USAGE = 5; 149 private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 6; 150 private static final int MSG_CHANGE_VIDEO_QUALITY = 7; 151 MessageHandler(Looper looper)152 public MessageHandler(Looper looper) { 153 super(looper); 154 } 155 156 @Override handleMessage(Message msg)157 public void handleMessage(Message msg) { 158 if (mCallback == null) { 159 return; 160 } 161 162 SomeArgs args; 163 switch (msg.what) { 164 case MSG_RECEIVE_SESSION_MODIFY_REQUEST: 165 mCallback.onSessionModifyRequestReceived((VideoProfile) msg.obj); 166 break; 167 case MSG_RECEIVE_SESSION_MODIFY_RESPONSE: 168 args = (SomeArgs) msg.obj; 169 try { 170 int status = (int) args.arg1; 171 VideoProfile requestProfile = (VideoProfile) args.arg2; 172 VideoProfile responseProfile = (VideoProfile) args.arg3; 173 174 mCallback.onSessionModifyResponseReceived( 175 status, requestProfile, responseProfile); 176 } finally { 177 args.recycle(); 178 } 179 break; 180 case MSG_HANDLE_CALL_SESSION_EVENT: 181 mCallback.onCallSessionEvent((int) msg.obj); 182 break; 183 case MSG_CHANGE_PEER_DIMENSIONS: 184 args = (SomeArgs) msg.obj; 185 try { 186 int width = (int) args.arg1; 187 int height = (int) args.arg2; 188 mCallback.onPeerDimensionsChanged(width, height); 189 } finally { 190 args.recycle(); 191 } 192 break; 193 case MSG_CHANGE_CALL_DATA_USAGE: 194 mCallback.onCallDataUsageChanged((long) msg.obj); 195 break; 196 case MSG_CHANGE_CAMERA_CAPABILITIES: 197 mCallback.onCameraCapabilitiesChanged( 198 (VideoProfile.CameraCapabilities) msg.obj); 199 break; 200 case MSG_CHANGE_VIDEO_QUALITY: 201 mVideoQuality = msg.arg1; 202 mCallback.onVideoQualityChanged(msg.arg1); 203 break; 204 default: 205 break; 206 } 207 } 208 }; 209 210 private Handler mHandler; 211 VideoCallImpl(IVideoProvider videoProvider, String callingPackageName, int targetSdkVersion)212 VideoCallImpl(IVideoProvider videoProvider, String callingPackageName, int targetSdkVersion) 213 throws RemoteException { 214 mVideoProvider = videoProvider; 215 mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0); 216 217 mBinder = new VideoCallListenerBinder(); 218 mVideoProvider.addVideoCallback(mBinder); 219 mCallingPackageName = callingPackageName; 220 setTargetSdkVersion(targetSdkVersion); 221 } 222 223 @VisibleForTesting setTargetSdkVersion(int sdkVersion)224 public void setTargetSdkVersion(int sdkVersion) { 225 mTargetSdkVersion = sdkVersion; 226 } 227 228 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 127403196) destroy()229 public void destroy() { 230 unregisterCallback(mCallback); 231 try { 232 mVideoProvider.asBinder().unlinkToDeath(mDeathRecipient, 0); 233 } catch (NoSuchElementException nse) { 234 // Already unlinked in binderDied above. 235 } 236 } 237 238 /** {@inheritDoc} */ registerCallback(VideoCall.Callback callback)239 public void registerCallback(VideoCall.Callback callback) { 240 registerCallback(callback, null); 241 } 242 243 /** {@inheritDoc} */ registerCallback(VideoCall.Callback callback, Handler handler)244 public void registerCallback(VideoCall.Callback callback, Handler handler) { 245 mCallback = callback; 246 if (handler == null) { 247 mHandler = new MessageHandler(Looper.getMainLooper()); 248 } else { 249 mHandler = new MessageHandler(handler.getLooper()); 250 } 251 } 252 253 /** {@inheritDoc} */ unregisterCallback(VideoCall.Callback callback)254 public void unregisterCallback(VideoCall.Callback callback) { 255 if (callback != mCallback) { 256 return; 257 } 258 259 mCallback = null; 260 try { 261 mVideoProvider.removeVideoCallback(mBinder); 262 } catch (RemoteException e) { 263 } 264 } 265 266 /** {@inheritDoc} */ setCamera(String cameraId)267 public void setCamera(String cameraId) { 268 try { 269 Log.w(this, "setCamera: cameraId=%s, calling=%s", cameraId, mCallingPackageName); 270 mVideoProvider.setCamera(cameraId, mCallingPackageName, mTargetSdkVersion); 271 } catch (RemoteException e) { 272 } 273 } 274 275 /** {@inheritDoc} */ setPreviewSurface(Surface surface)276 public void setPreviewSurface(Surface surface) { 277 try { 278 mVideoProvider.setPreviewSurface(surface); 279 } catch (RemoteException e) { 280 } 281 } 282 283 /** {@inheritDoc} */ setDisplaySurface(Surface surface)284 public void setDisplaySurface(Surface surface) { 285 try { 286 mVideoProvider.setDisplaySurface(surface); 287 } catch (RemoteException e) { 288 } 289 } 290 291 /** {@inheritDoc} */ setDeviceOrientation(int rotation)292 public void setDeviceOrientation(int rotation) { 293 try { 294 mVideoProvider.setDeviceOrientation(rotation); 295 } catch (RemoteException e) { 296 } 297 } 298 299 /** {@inheritDoc} */ setZoom(float value)300 public void setZoom(float value) { 301 try { 302 mVideoProvider.setZoom(value); 303 } catch (RemoteException e) { 304 } 305 } 306 307 /** 308 * Sends a session modification request to the video provider. 309 * <p> 310 * The {@link InCallService} will create the {@code requestProfile} based on the current 311 * video state (i.e. {@link Call.Details#getVideoState()}). It is, however, possible that the 312 * video state maintained by the {@link InCallService} could get out of sync with what is known 313 * by the {@link android.telecom.Connection.VideoProvider}. To remove ambiguity, the 314 * {@link VideoCallImpl} passes along the pre-modify video profile to the {@code VideoProvider} 315 * to ensure it has full context of the requested change. 316 * 317 * @param requestProfile The requested video profile. 318 */ sendSessionModifyRequest(VideoProfile requestProfile)319 public void sendSessionModifyRequest(VideoProfile requestProfile) { 320 try { 321 VideoProfile originalProfile = new VideoProfile(mVideoState, mVideoQuality); 322 323 mVideoProvider.sendSessionModifyRequest(originalProfile, requestProfile); 324 } catch (RemoteException e) { 325 } 326 } 327 328 /** {@inheritDoc} */ sendSessionModifyResponse(VideoProfile responseProfile)329 public void sendSessionModifyResponse(VideoProfile responseProfile) { 330 try { 331 mVideoProvider.sendSessionModifyResponse(responseProfile); 332 } catch (RemoteException e) { 333 } 334 } 335 336 /** {@inheritDoc} */ requestCameraCapabilities()337 public void requestCameraCapabilities() { 338 try { 339 mVideoProvider.requestCameraCapabilities(); 340 } catch (RemoteException e) { 341 } 342 } 343 344 /** {@inheritDoc} */ requestCallDataUsage()345 public void requestCallDataUsage() { 346 try { 347 mVideoProvider.requestCallDataUsage(); 348 } catch (RemoteException e) { 349 } 350 } 351 352 /** {@inheritDoc} */ setPauseImage(Uri uri)353 public void setPauseImage(Uri uri) { 354 try { 355 mVideoProvider.setPauseImage(uri); 356 } catch (RemoteException e) { 357 } 358 } 359 360 /** 361 * Sets the video state for the current video call. 362 * @param videoState the new video state. 363 */ setVideoState(int videoState)364 public void setVideoState(int videoState) { 365 mVideoState = videoState; 366 } 367 368 /** 369 * Get the video provider binder. 370 * @return the video provider binder. 371 */ getVideoProvider()372 public IVideoProvider getVideoProvider() { 373 return mVideoProvider; 374 } 375 } 376