• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onSessionModifyRequestReceived(Call call, VideoProfile videoProfile)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      */
VideoProviderProxy(TelecomSystem.SyncRoot lock, IVideoProvider videoProvider, Call call, CurrentUserProxy currentUserProxy)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 
clearVideoCallback()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
receiveSessionModifyRequest(VideoProfile videoProfile)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
receiveSessionModifyResponse(int status, VideoProfile requestProfile, VideoProfile responseProfile)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
handleCallSessionEvent(int event)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
changePeerDimensions(int width, int height)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
changeVideoQuality(int videoQuality)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
changeCallDataUsage(long dataUsage)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
changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities)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
onSetCamera(String cameraId)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
onSetCamera(String cameraId, String callingPackage, int callingUid, int callingPid, int targetSdkVersion)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
onSetPreviewSurface(Surface surface)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
onSetDisplaySurface(Surface surface)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
onSetDeviceOrientation(int rotation)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
onSetZoom(float value)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
onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile)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
onSendSessionModifyResponse(VideoProfile responseProfile)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
onRequestCameraCapabilities()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
onRequestConnectionDataUsage()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
onSetPauseImage(Uri uri)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      */
addListener(Listener listener)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      */
removeListener(Listener listener)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      */
logFromInCall(String toLog)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      */
logFromVideoProvider(String toLog)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      */
canUseCamera(Context context, String callingPackage, int callingUid, int callingPid)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