1 /* 2 * Copyright (C) 2015 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.server.telecom; 18 19 import android.Manifest; 20 import android.app.AppOpsManager; 21 import android.content.Context; 22 import android.net.Uri; 23 import android.os.Binder; 24 import android.os.Build; 25 import android.os.IBinder; 26 import android.os.Looper; 27 import android.os.RemoteException; 28 import android.os.UserHandle; 29 import android.telecom.Connection; 30 import android.telecom.InCallService; 31 import android.telecom.Log; 32 import android.telecom.VideoProfile; 33 import android.text.TextUtils; 34 import android.view.Surface; 35 36 import com.android.internal.telecom.IVideoCallback; 37 import com.android.internal.telecom.IVideoProvider; 38 39 import java.util.Collections; 40 import java.util.Set; 41 import java.util.concurrent.ConcurrentHashMap; 42 43 import static android.Manifest.permission.CALL_PHONE; 44 45 /** 46 * Proxies video provider messages from {@link InCallService.VideoCall} 47 * implementations to the underlying {@link Connection.VideoProvider} implementation. Also proxies 48 * callbacks from the {@link Connection.VideoProvider} to {@link InCallService.VideoCall} 49 * implementations. 50 * 51 * Also provides a means for Telecom to send and receive these messages. 52 */ 53 public class VideoProviderProxy extends Connection.VideoProvider { 54 55 /** 56 * Listener for Telecom components interested in callbacks from the video provider. 57 */ 58 interface Listener { 59 void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile); 60 } 61 62 /** 63 * Set of listeners on this VideoProviderProxy. 64 * 65 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 66 * load factor before resizing, 1 means we only expect a single thread to 67 * access the map so make only a single shard 68 */ 69 private final Set<Listener> mListeners = Collections.newSetFromMap( 70 new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1)); 71 72 /** The TelecomSystem SyncRoot used for synchronized operations. */ 73 private final TelecomSystem.SyncRoot mLock; 74 75 /** 76 * The {@link android.telecom.Connection.VideoProvider} implementation residing with the 77 * {@link android.telecom.ConnectionService} which is being wrapped by this 78 * {@link VideoProviderProxy}. 79 */ 80 private final IVideoProvider mConectionServiceVideoProvider; 81 82 /** 83 * Binder used to bind to the {@link android.telecom.ConnectionService}'s 84 * {@link com.android.internal.telecom.IVideoCallback}. 85 */ 86 private final VideoCallListenerBinder mVideoCallListenerBinder; 87 88 /** 89 * The Telecom {@link Call} this {@link VideoProviderProxy} is associated with. 90 */ 91 private Call mCall; 92 93 /** 94 * Interface providing access to the currently logged in user. 95 */ 96 private CurrentUserProxy mCurrentUserProxy; 97 98 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { 99 @Override 100 public void binderDied() { 101 mConectionServiceVideoProvider.asBinder().unlinkToDeath(this, 0); 102 } 103 }; 104 105 /** 106 * Creates a new instance of the {@link VideoProviderProxy}, binding it to the passed in 107 * {@code videoProvider} residing with the {@link android.telecom.ConnectionService}. 108 * 109 * 110 * @param lock 111 * @param videoProvider The {@link android.telecom.ConnectionService}'s video provider. 112 * @param call The current call. 113 * @throws RemoteException Remote exception. 114 */ 115 VideoProviderProxy(TelecomSystem.SyncRoot lock, 116 IVideoProvider videoProvider, Call call, CurrentUserProxy currentUserProxy) 117 throws RemoteException { 118 119 super(Looper.getMainLooper()); 120 121 mLock = lock; 122 123 mConectionServiceVideoProvider = videoProvider; 124 mConectionServiceVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0); 125 126 mVideoCallListenerBinder = new VideoCallListenerBinder(); 127 mConectionServiceVideoProvider.addVideoCallback(mVideoCallListenerBinder); 128 mCall = call; 129 mCurrentUserProxy = currentUserProxy; 130 } 131 132 public void clearVideoCallback() { 133 try { 134 mConectionServiceVideoProvider.removeVideoCallback(mVideoCallListenerBinder); 135 } catch (RemoteException e) { 136 } 137 } 138 139 /** 140 * IVideoCallback stub implementation. An instance of this class receives callbacks from the 141 * {@code ConnectionService}'s video provider. 142 */ 143 private final class VideoCallListenerBinder extends IVideoCallback.Stub { 144 /** 145 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 146 * {@link InCallService} when a session modification request is received. 147 * 148 * @param videoProfile The requested video profile. 149 */ 150 @Override 151 public void receiveSessionModifyRequest(VideoProfile videoProfile) { 152 try { 153 Log.startSession("VPP.rSMR"); 154 synchronized (mLock) { 155 logFromVideoProvider("receiveSessionModifyRequest: " + videoProfile); 156 Log.addEvent(mCall, LogUtils.Events.RECEIVE_VIDEO_REQUEST, 157 VideoProfile.videoStateToString(videoProfile.getVideoState())); 158 159 mCall.getAnalytics().addVideoEvent( 160 Analytics.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST, 161 videoProfile.getVideoState()); 162 163 if (!mCall.isVideoCallingSupported() && 164 VideoProfile.isVideo(videoProfile.getVideoState())) { 165 // If video calling is not supported by the phone account, and we receive 166 // a request to upgrade to video, automatically reject it without informing 167 // the InCallService. 168 169 Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE, "video not supported"); 170 VideoProfile responseProfile = new VideoProfile( 171 VideoProfile.STATE_AUDIO_ONLY); 172 try { 173 mConectionServiceVideoProvider.sendSessionModifyResponse( 174 responseProfile); 175 } catch (RemoteException e) { 176 } 177 178 // Don't want to inform listeners of the request as we've just rejected it. 179 return; 180 } 181 182 // Inform other Telecom components of the session modification request. 183 for (Listener listener : mListeners) { 184 listener.onSessionModifyRequestReceived(mCall, videoProfile); 185 } 186 187 VideoProviderProxy.this.receiveSessionModifyRequest(videoProfile); 188 } 189 } finally { 190 Log.endSession(); 191 } 192 } 193 194 /** 195 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 196 * {@link InCallService} when a session modification response is received. 197 * 198 * @param status The status of the response. 199 * @param requestProfile The requested video profile. 200 * @param responseProfile The response video profile. 201 */ 202 @Override 203 public void receiveSessionModifyResponse(int status, VideoProfile requestProfile, 204 VideoProfile responseProfile) { 205 logFromVideoProvider("receiveSessionModifyResponse: status=" + status + 206 " requestProfile=" + requestProfile + " responseProfile=" + responseProfile); 207 String eventMessage = "Status Code : " + status + " Video State: " + 208 (responseProfile != null ? responseProfile.getVideoState() : "null"); 209 Log.addEvent(mCall, LogUtils.Events.RECEIVE_VIDEO_RESPONSE, eventMessage); 210 synchronized (mLock) { 211 if (status == Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { 212 mCall.getAnalytics().addVideoEvent( 213 Analytics.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE, 214 responseProfile == null ? 215 VideoProfile.STATE_AUDIO_ONLY : 216 responseProfile.getVideoState()); 217 } 218 VideoProviderProxy.this.receiveSessionModifyResponse(status, requestProfile, 219 responseProfile); 220 } 221 } 222 223 /** 224 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 225 * {@link InCallService} when a call session event occurs. 226 * 227 * @param event The call session event. 228 */ 229 @Override 230 public void handleCallSessionEvent(int event) { 231 synchronized (mLock) { 232 logFromVideoProvider("handleCallSessionEvent: " + 233 Connection.VideoProvider.sessionEventToString(event)); 234 VideoProviderProxy.this.handleCallSessionEvent(event); 235 } 236 } 237 238 /** 239 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 240 * {@link InCallService} when the peer dimensions change. 241 * 242 * @param width The width of the peer's video. 243 * @param height The height of the peer's video. 244 */ 245 @Override 246 public void changePeerDimensions(int width, int height) { 247 synchronized (mLock) { 248 logFromVideoProvider("changePeerDimensions: width=" + width + " height=" + 249 height); 250 VideoProviderProxy.this.changePeerDimensions(width, height); 251 } 252 } 253 254 /** 255 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 256 * {@link InCallService} when the video quality changes. 257 * 258 * @param videoQuality The video quality. 259 */ 260 @Override 261 public void changeVideoQuality(int videoQuality) { 262 synchronized (mLock) { 263 logFromVideoProvider("changeVideoQuality: " + videoQuality); 264 VideoProviderProxy.this.changeVideoQuality(videoQuality); 265 } 266 } 267 268 /** 269 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 270 * {@link InCallService} when the call data usage changes. 271 * 272 * Also tracks the current call data usage on the {@link Call} for use when writing to the 273 * call log. 274 * 275 * @param dataUsage The data usage. 276 */ 277 @Override 278 public void changeCallDataUsage(long dataUsage) { 279 synchronized (mLock) { 280 logFromVideoProvider("changeCallDataUsage: " + dataUsage); 281 VideoProviderProxy.this.setCallDataUsage(dataUsage); 282 mCall.setCallDataUsage(dataUsage); 283 } 284 } 285 286 /** 287 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 288 * {@link InCallService} when the camera capabilities change. 289 * 290 * @param cameraCapabilities The camera capabilities. 291 */ 292 @Override 293 public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) { 294 synchronized (mLock) { 295 logFromVideoProvider("changeCameraCapabilities: " + cameraCapabilities); 296 VideoProviderProxy.this.changeCameraCapabilities(cameraCapabilities); 297 } 298 } 299 } 300 301 @Override 302 public void onSetCamera(String cameraId) { 303 // No-op. We implement the other prototype of onSetCamera so that we can use the calling 304 // package, uid and pid to verify permission. 305 } 306 307 /** 308 * Proxies a request from the {@link InCallService} to the 309 * {@link #mConectionServiceVideoProvider} to change the camera. 310 * 311 * @param cameraId The id of the camera. 312 * @param callingPackage The package calling in. 313 * @param callingUid The UID of the caller. 314 * @param callingPid The PID of the caller. 315 * @param targetSdkVersion The target SDK version of the calling InCallService where the camera 316 * request originated. 317 */ 318 @Override 319 public void onSetCamera(String cameraId, String callingPackage, int callingUid, 320 int callingPid, int targetSdkVersion) { 321 synchronized (mLock) { 322 logFromInCall("setCamera: " + cameraId + " callingPackage=" + callingPackage + 323 "; callingUid=" + callingUid); 324 325 if (!TextUtils.isEmpty(cameraId)) { 326 if (!canUseCamera(mCall.getContext(), callingPackage, callingUid, callingPid)) { 327 // Calling app is not permitted to use the camera. Ignore the request and send 328 // back a call session event indicating the error. 329 Log.i(this, "onSetCamera: camera permission denied; package=%s, uid=%d, " 330 + "pid=%d, targetSdkVersion=%d", 331 callingPackage, callingUid, callingPid, targetSdkVersion); 332 333 // API 26 introduces a new camera permission error we can use here since the 334 // caller supports that API version. 335 if (targetSdkVersion > Build.VERSION_CODES.N_MR1) { 336 VideoProviderProxy.this.handleCallSessionEvent( 337 Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR); 338 } else { 339 VideoProviderProxy.this.handleCallSessionEvent( 340 Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE); 341 } 342 return; 343 } 344 } 345 try { 346 mConectionServiceVideoProvider.setCamera(cameraId, callingPackage, 347 targetSdkVersion); 348 } catch (RemoteException e) { 349 VideoProviderProxy.this.handleCallSessionEvent( 350 Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE); 351 } 352 } 353 } 354 355 /** 356 * Proxies a request from the {@link InCallService} to the 357 * {@link #mConectionServiceVideoProvider} to set the preview surface. 358 * 359 * @param surface The surface. 360 */ 361 @Override 362 public void onSetPreviewSurface(Surface surface) { 363 synchronized (mLock) { 364 logFromInCall("setPreviewSurface"); 365 try { 366 mConectionServiceVideoProvider.setPreviewSurface(surface); 367 } catch (RemoteException e) { 368 } 369 } 370 } 371 372 /** 373 * Proxies a request from the {@link InCallService} to the 374 * {@link #mConectionServiceVideoProvider} to change the display surface. 375 * 376 * @param surface The surface. 377 */ 378 @Override 379 public void onSetDisplaySurface(Surface surface) { 380 synchronized (mLock) { 381 logFromInCall("setDisplaySurface"); 382 try { 383 mConectionServiceVideoProvider.setDisplaySurface(surface); 384 } catch (RemoteException e) { 385 } 386 } 387 } 388 389 /** 390 * Proxies a request from the {@link InCallService} to the 391 * {@link #mConectionServiceVideoProvider} to change the device orientation. 392 * 393 * @param rotation The device orientation, in degrees. 394 */ 395 @Override 396 public void onSetDeviceOrientation(int rotation) { 397 synchronized (mLock) { 398 logFromInCall("setDeviceOrientation: " + rotation); 399 try { 400 mConectionServiceVideoProvider.setDeviceOrientation(rotation); 401 } catch (RemoteException e) { 402 } 403 } 404 } 405 406 /** 407 * Proxies a request from the {@link InCallService} to the 408 * {@link #mConectionServiceVideoProvider} to change the camera zoom ratio. 409 * 410 * @param value The camera zoom ratio. 411 */ 412 @Override 413 public void onSetZoom(float value) { 414 synchronized (mLock) { 415 logFromInCall("setZoom: " + value); 416 try { 417 mConectionServiceVideoProvider.setZoom(value); 418 } catch (RemoteException e) { 419 } 420 } 421 } 422 423 /** 424 * Proxies a request from the {@link InCallService} to the 425 * {@link #mConectionServiceVideoProvider} to provide a response to a session modification 426 * request. 427 * 428 * @param fromProfile The video properties prior to the request. 429 * @param toProfile The video properties with the requested changes made. 430 */ 431 @Override 432 public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) { 433 synchronized (mLock) { 434 logFromInCall("sendSessionModifyRequest: from=" + fromProfile + " to=" + toProfile); 435 Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_REQUEST, 436 VideoProfile.videoStateToString(toProfile.getVideoState())); 437 mCall.getAnalytics().addVideoEvent( 438 Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST, 439 toProfile.getVideoState()); 440 try { 441 mConectionServiceVideoProvider.sendSessionModifyRequest(fromProfile, toProfile); 442 } catch (RemoteException e) { 443 } 444 } 445 } 446 447 /** 448 * Proxies a request from the {@link InCallService} to the 449 * {@link #mConectionServiceVideoProvider} to send a session modification request. 450 * 451 * @param responseProfile The response connection video properties. 452 */ 453 @Override 454 public void onSendSessionModifyResponse(VideoProfile responseProfile) { 455 synchronized (mLock) { 456 logFromInCall("sendSessionModifyResponse: " + responseProfile); 457 Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE, 458 VideoProfile.videoStateToString(responseProfile.getVideoState())); 459 mCall.getAnalytics().addVideoEvent( 460 Analytics.SEND_LOCAL_SESSION_MODIFY_RESPONSE, 461 responseProfile.getVideoState()); 462 try { 463 mConectionServiceVideoProvider.sendSessionModifyResponse(responseProfile); 464 } catch (RemoteException e) { 465 } 466 } 467 } 468 469 /** 470 * Proxies a request from the {@link InCallService} to the 471 * {@link #mConectionServiceVideoProvider} to request the camera capabilities. 472 */ 473 @Override 474 public void onRequestCameraCapabilities() { 475 synchronized (mLock) { 476 logFromInCall("requestCameraCapabilities"); 477 try { 478 mConectionServiceVideoProvider.requestCameraCapabilities(); 479 } catch (RemoteException e) { 480 } 481 } 482 } 483 484 /** 485 * Proxies a request from the {@link InCallService} to the 486 * {@link #mConectionServiceVideoProvider} to request the connection data usage. 487 */ 488 @Override 489 public void onRequestConnectionDataUsage() { 490 synchronized (mLock) { 491 logFromInCall("requestCallDataUsage"); 492 try { 493 mConectionServiceVideoProvider.requestCallDataUsage(); 494 } catch (RemoteException e) { 495 } 496 } 497 } 498 499 /** 500 * Proxies a request from the {@link InCallService} to the 501 * {@link #mConectionServiceVideoProvider} to set the pause image. 502 * 503 * @param uri URI of image to display. 504 */ 505 @Override 506 public void onSetPauseImage(Uri uri) { 507 synchronized (mLock) { 508 logFromInCall("setPauseImage: " + uri); 509 try { 510 mConectionServiceVideoProvider.setPauseImage(uri); 511 } catch (RemoteException e) { 512 } 513 } 514 } 515 516 /** 517 * Add a listener to this {@link VideoProviderProxy}. 518 * 519 * @param listener The listener. 520 */ 521 public void addListener(Listener listener) { 522 mListeners.add(listener); 523 } 524 525 /** 526 * Remove a listener from this {@link VideoProviderProxy}. 527 * 528 * @param listener The listener. 529 */ 530 public void removeListener(Listener listener) { 531 if (listener != null) { 532 mListeners.remove(listener); 533 } 534 } 535 536 /** 537 * Logs a message originating from the {@link InCallService}. 538 * 539 * @param toLog The message to log. 540 */ 541 private void logFromInCall(String toLog) { 542 Log.i(this, "IC->VP (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog); 543 } 544 545 /** 546 * Logs a message originating from the {@link android.telecom.ConnectionService}'s 547 * {@link Connection.VideoProvider}. 548 * 549 * @param toLog The message to log. 550 */ 551 private void logFromVideoProvider(String toLog) { 552 Log.i(this, "VP->IC (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog); 553 } 554 555 /** 556 * Determines if the caller has permission to use the camera. 557 * 558 * @param context The context. 559 * @param callingPackage The package name of the caller (i.e. Dialer). 560 * @param callingUid The UID of the caller. 561 * @param callingPid The PID of the caller. 562 * @return {@code true} if the calling uid and package can use the camera, {@code false} 563 * otherwise. 564 */ 565 private boolean canUseCamera(Context context, String callingPackage, int callingUid, 566 int callingPid) { 567 568 UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); 569 UserHandle currentUserHandle = mCurrentUserProxy.getCurrentUserHandle(); 570 if (currentUserHandle != null && !currentUserHandle.equals(callingUser)) { 571 Log.w(this, "canUseCamera attempt to user camera by background user."); 572 return false; 573 } 574 575 try { 576 context.enforcePermission(Manifest.permission.CAMERA, callingPid, callingUid, 577 "Camera permission required."); 578 } catch (SecurityException se) { 579 return false; 580 } 581 582 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService( 583 Context.APP_OPS_SERVICE); 584 585 try { 586 // Some apps that have the permission can be restricted via app ops. 587 return appOpsManager != null && appOpsManager.noteOp(AppOpsManager.OP_CAMERA, 588 callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED; 589 } catch (SecurityException se) { 590 Log.w(this, "canUseCamera got appOpps Exception " + se.toString()); 591 return false; 592 } 593 } 594 595 } 596