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 /** 133 * IVideoCallback stub implementation. An instance of this class receives callbacks from the 134 * {@code ConnectionService}'s video provider. 135 */ 136 private final class VideoCallListenerBinder extends IVideoCallback.Stub { 137 /** 138 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 139 * {@link InCallService} when a session modification request is received. 140 * 141 * @param videoProfile The requested video profile. 142 */ 143 @Override 144 public void receiveSessionModifyRequest(VideoProfile videoProfile) { 145 try { 146 Log.startSession("VPP.rSMR"); 147 synchronized (mLock) { 148 logFromVideoProvider("receiveSessionModifyRequest: " + videoProfile); 149 Log.addEvent(mCall, LogUtils.Events.RECEIVE_VIDEO_REQUEST, 150 VideoProfile.videoStateToString(videoProfile.getVideoState())); 151 152 mCall.getAnalytics().addVideoEvent( 153 Analytics.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST, 154 videoProfile.getVideoState()); 155 156 if (!mCall.isVideoCallingSupported() && 157 VideoProfile.isVideo(videoProfile.getVideoState())) { 158 // If video calling is not supported by the phone account, and we receive 159 // a request to upgrade to video, automatically reject it without informing 160 // the InCallService. 161 162 Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE, "video not supported"); 163 VideoProfile responseProfile = new VideoProfile( 164 VideoProfile.STATE_AUDIO_ONLY); 165 try { 166 mConectionServiceVideoProvider.sendSessionModifyResponse( 167 responseProfile); 168 } catch (RemoteException e) { 169 } 170 171 // Don't want to inform listeners of the request as we've just rejected it. 172 return; 173 } 174 175 // Inform other Telecom components of the session modification request. 176 for (Listener listener : mListeners) { 177 listener.onSessionModifyRequestReceived(mCall, videoProfile); 178 } 179 180 VideoProviderProxy.this.receiveSessionModifyRequest(videoProfile); 181 } 182 } finally { 183 Log.endSession(); 184 } 185 } 186 187 /** 188 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 189 * {@link InCallService} when a session modification response is received. 190 * 191 * @param status The status of the response. 192 * @param requestProfile The requested video profile. 193 * @param responseProfile The response video profile. 194 */ 195 @Override 196 public void receiveSessionModifyResponse(int status, VideoProfile requestProfile, 197 VideoProfile responseProfile) { 198 logFromVideoProvider("receiveSessionModifyResponse: status=" + status + 199 " requestProfile=" + requestProfile + " responseProfile=" + responseProfile); 200 String eventMessage = "Status Code : " + status + " Video State: " + 201 (responseProfile != null ? responseProfile.getVideoState() : "null"); 202 Log.addEvent(mCall, LogUtils.Events.RECEIVE_VIDEO_RESPONSE, eventMessage); 203 synchronized (mLock) { 204 if (status == Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) { 205 mCall.getAnalytics().addVideoEvent( 206 Analytics.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE, 207 responseProfile == null ? 208 VideoProfile.STATE_AUDIO_ONLY : 209 responseProfile.getVideoState()); 210 } 211 VideoProviderProxy.this.receiveSessionModifyResponse(status, requestProfile, 212 responseProfile); 213 } 214 } 215 216 /** 217 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 218 * {@link InCallService} when a call session event occurs. 219 * 220 * @param event The call session event. 221 */ 222 @Override 223 public void handleCallSessionEvent(int event) { 224 synchronized (mLock) { 225 logFromVideoProvider("handleCallSessionEvent: " + 226 Connection.VideoProvider.sessionEventToString(event)); 227 VideoProviderProxy.this.handleCallSessionEvent(event); 228 } 229 } 230 231 /** 232 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 233 * {@link InCallService} when the peer dimensions change. 234 * 235 * @param width The width of the peer's video. 236 * @param height The height of the peer's video. 237 */ 238 @Override 239 public void changePeerDimensions(int width, int height) { 240 synchronized (mLock) { 241 logFromVideoProvider("changePeerDimensions: width=" + width + " height=" + 242 height); 243 VideoProviderProxy.this.changePeerDimensions(width, height); 244 } 245 } 246 247 /** 248 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 249 * {@link InCallService} when the video quality changes. 250 * 251 * @param videoQuality The video quality. 252 */ 253 @Override 254 public void changeVideoQuality(int videoQuality) { 255 synchronized (mLock) { 256 logFromVideoProvider("changeVideoQuality: " + videoQuality); 257 VideoProviderProxy.this.changeVideoQuality(videoQuality); 258 } 259 } 260 261 /** 262 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 263 * {@link InCallService} when the call data usage changes. 264 * 265 * Also tracks the current call data usage on the {@link Call} for use when writing to the 266 * call log. 267 * 268 * @param dataUsage The data usage. 269 */ 270 @Override 271 public void changeCallDataUsage(long dataUsage) { 272 synchronized (mLock) { 273 logFromVideoProvider("changeCallDataUsage: " + dataUsage); 274 VideoProviderProxy.this.setCallDataUsage(dataUsage); 275 mCall.setCallDataUsage(dataUsage); 276 } 277 } 278 279 /** 280 * Proxies a request from the {@link #mConectionServiceVideoProvider} to the 281 * {@link InCallService} when the camera capabilities change. 282 * 283 * @param cameraCapabilities The camera capabilities. 284 */ 285 @Override 286 public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) { 287 synchronized (mLock) { 288 logFromVideoProvider("changeCameraCapabilities: " + cameraCapabilities); 289 VideoProviderProxy.this.changeCameraCapabilities(cameraCapabilities); 290 } 291 } 292 } 293 294 @Override 295 public void onSetCamera(String cameraId) { 296 // No-op. We implement the other prototype of onSetCamera so that we can use the calling 297 // package, uid and pid to verify permission. 298 } 299 300 /** 301 * Proxies a request from the {@link InCallService} to the 302 * {@link #mConectionServiceVideoProvider} to change the camera. 303 * 304 * @param cameraId The id of the camera. 305 * @param callingPackage The package calling in. 306 * @param callingUid The UID of the caller. 307 * @param callingPid The PID of the caller. 308 * @param targetSdkVersion The target SDK version of the calling InCallService where the camera 309 * request originated. 310 */ 311 @Override 312 public void onSetCamera(String cameraId, String callingPackage, int callingUid, 313 int callingPid, int targetSdkVersion) { 314 synchronized (mLock) { 315 logFromInCall("setCamera: " + cameraId + " callingPackage=" + callingPackage + 316 "; callingUid=" + callingUid); 317 318 if (!TextUtils.isEmpty(cameraId)) { 319 if (!canUseCamera(mCall.getContext(), callingPackage, callingUid, callingPid)) { 320 // Calling app is not permitted to use the camera. Ignore the request and send 321 // back a call session event indicating the error. 322 Log.i(this, "onSetCamera: camera permission denied; package=%s, uid=%d, " 323 + "pid=%d, targetSdkVersion=%d", 324 callingPackage, callingUid, callingPid, targetSdkVersion); 325 326 // API 26 introduces a new camera permission error we can use here since the 327 // caller supports that API version. 328 if (targetSdkVersion > Build.VERSION_CODES.N_MR1) { 329 VideoProviderProxy.this.handleCallSessionEvent( 330 Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR); 331 } else { 332 VideoProviderProxy.this.handleCallSessionEvent( 333 Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE); 334 } 335 return; 336 } 337 } 338 try { 339 mConectionServiceVideoProvider.setCamera(cameraId, callingPackage, 340 targetSdkVersion); 341 } catch (RemoteException e) { 342 VideoProviderProxy.this.handleCallSessionEvent( 343 Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE); 344 } 345 } 346 } 347 348 /** 349 * Proxies a request from the {@link InCallService} to the 350 * {@link #mConectionServiceVideoProvider} to set the preview surface. 351 * 352 * @param surface The surface. 353 */ 354 @Override 355 public void onSetPreviewSurface(Surface surface) { 356 synchronized (mLock) { 357 logFromInCall("setPreviewSurface"); 358 try { 359 mConectionServiceVideoProvider.setPreviewSurface(surface); 360 } catch (RemoteException e) { 361 } 362 } 363 } 364 365 /** 366 * Proxies a request from the {@link InCallService} to the 367 * {@link #mConectionServiceVideoProvider} to change the display surface. 368 * 369 * @param surface The surface. 370 */ 371 @Override 372 public void onSetDisplaySurface(Surface surface) { 373 synchronized (mLock) { 374 logFromInCall("setDisplaySurface"); 375 try { 376 mConectionServiceVideoProvider.setDisplaySurface(surface); 377 } catch (RemoteException e) { 378 } 379 } 380 } 381 382 /** 383 * Proxies a request from the {@link InCallService} to the 384 * {@link #mConectionServiceVideoProvider} to change the device orientation. 385 * 386 * @param rotation The device orientation, in degrees. 387 */ 388 @Override 389 public void onSetDeviceOrientation(int rotation) { 390 synchronized (mLock) { 391 logFromInCall("setDeviceOrientation: " + rotation); 392 try { 393 mConectionServiceVideoProvider.setDeviceOrientation(rotation); 394 } catch (RemoteException e) { 395 } 396 } 397 } 398 399 /** 400 * Proxies a request from the {@link InCallService} to the 401 * {@link #mConectionServiceVideoProvider} to change the camera zoom ratio. 402 * 403 * @param value The camera zoom ratio. 404 */ 405 @Override 406 public void onSetZoom(float value) { 407 synchronized (mLock) { 408 logFromInCall("setZoom: " + value); 409 try { 410 mConectionServiceVideoProvider.setZoom(value); 411 } catch (RemoteException e) { 412 } 413 } 414 } 415 416 /** 417 * Proxies a request from the {@link InCallService} to the 418 * {@link #mConectionServiceVideoProvider} to provide a response to a session modification 419 * request. 420 * 421 * @param fromProfile The video properties prior to the request. 422 * @param toProfile The video properties with the requested changes made. 423 */ 424 @Override 425 public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) { 426 synchronized (mLock) { 427 logFromInCall("sendSessionModifyRequest: from=" + fromProfile + " to=" + toProfile); 428 Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_REQUEST, 429 VideoProfile.videoStateToString(toProfile.getVideoState())); 430 mCall.getAnalytics().addVideoEvent( 431 Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST, 432 toProfile.getVideoState()); 433 try { 434 mConectionServiceVideoProvider.sendSessionModifyRequest(fromProfile, toProfile); 435 } catch (RemoteException e) { 436 } 437 } 438 } 439 440 /** 441 * Proxies a request from the {@link InCallService} to the 442 * {@link #mConectionServiceVideoProvider} to send a session modification request. 443 * 444 * @param responseProfile The response connection video properties. 445 */ 446 @Override 447 public void onSendSessionModifyResponse(VideoProfile responseProfile) { 448 synchronized (mLock) { 449 logFromInCall("sendSessionModifyResponse: " + responseProfile); 450 Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE, 451 VideoProfile.videoStateToString(responseProfile.getVideoState())); 452 mCall.getAnalytics().addVideoEvent( 453 Analytics.SEND_LOCAL_SESSION_MODIFY_RESPONSE, 454 responseProfile.getVideoState()); 455 try { 456 mConectionServiceVideoProvider.sendSessionModifyResponse(responseProfile); 457 } catch (RemoteException e) { 458 } 459 } 460 } 461 462 /** 463 * Proxies a request from the {@link InCallService} to the 464 * {@link #mConectionServiceVideoProvider} to request the camera capabilities. 465 */ 466 @Override 467 public void onRequestCameraCapabilities() { 468 synchronized (mLock) { 469 logFromInCall("requestCameraCapabilities"); 470 try { 471 mConectionServiceVideoProvider.requestCameraCapabilities(); 472 } catch (RemoteException e) { 473 } 474 } 475 } 476 477 /** 478 * Proxies a request from the {@link InCallService} to the 479 * {@link #mConectionServiceVideoProvider} to request the connection data usage. 480 */ 481 @Override 482 public void onRequestConnectionDataUsage() { 483 synchronized (mLock) { 484 logFromInCall("requestCallDataUsage"); 485 try { 486 mConectionServiceVideoProvider.requestCallDataUsage(); 487 } catch (RemoteException e) { 488 } 489 } 490 } 491 492 /** 493 * Proxies a request from the {@link InCallService} to the 494 * {@link #mConectionServiceVideoProvider} to set the pause image. 495 * 496 * @param uri URI of image to display. 497 */ 498 @Override 499 public void onSetPauseImage(Uri uri) { 500 synchronized (mLock) { 501 logFromInCall("setPauseImage: " + uri); 502 try { 503 mConectionServiceVideoProvider.setPauseImage(uri); 504 } catch (RemoteException e) { 505 } 506 } 507 } 508 509 /** 510 * Add a listener to this {@link VideoProviderProxy}. 511 * 512 * @param listener The listener. 513 */ 514 public void addListener(Listener listener) { 515 mListeners.add(listener); 516 } 517 518 /** 519 * Remove a listener from this {@link VideoProviderProxy}. 520 * 521 * @param listener The listener. 522 */ 523 public void removeListener(Listener listener) { 524 if (listener != null) { 525 mListeners.remove(listener); 526 } 527 } 528 529 /** 530 * Logs a message originating from the {@link InCallService}. 531 * 532 * @param toLog The message to log. 533 */ 534 private void logFromInCall(String toLog) { 535 Log.i(this, "IC->VP (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog); 536 } 537 538 /** 539 * Logs a message originating from the {@link android.telecom.ConnectionService}'s 540 * {@link Connection.VideoProvider}. 541 * 542 * @param toLog The message to log. 543 */ 544 private void logFromVideoProvider(String toLog) { 545 Log.i(this, "VP->IC (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog); 546 } 547 548 /** 549 * Determines if the caller has permission to use the camera. 550 * 551 * @param context The context. 552 * @param callingPackage The package name of the caller (i.e. Dialer). 553 * @param callingUid The UID of the caller. 554 * @param callingPid The PID of the caller. 555 * @return {@code true} if the calling uid and package can use the camera, {@code false} 556 * otherwise. 557 */ 558 private boolean canUseCamera(Context context, String callingPackage, int callingUid, 559 int callingPid) { 560 561 UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); 562 UserHandle currentUserHandle = mCurrentUserProxy.getCurrentUserHandle(); 563 if (currentUserHandle != null && !currentUserHandle.equals(callingUser)) { 564 Log.w(this, "canUseCamera attempt to user camera by background user."); 565 return false; 566 } 567 568 try { 569 context.enforcePermission(Manifest.permission.CAMERA, callingPid, callingUid, 570 "Camera permission required."); 571 } catch (SecurityException se) { 572 return false; 573 } 574 575 AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService( 576 Context.APP_OPS_SERVICE); 577 578 try { 579 // Some apps that have the permission can be restricted via app ops. 580 return appOpsManager != null && appOpsManager.noteOp(AppOpsManager.OP_CAMERA, 581 callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED; 582 } catch (SecurityException se) { 583 Log.w(this, "canUseCamera got appOpps Exception " + se.toString()); 584 return false; 585 } 586 } 587 588 } 589