• 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 
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
receiveSessionModifyRequest(VideoProfile videoProfile)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
receiveSessionModifyResponse(int status, VideoProfile requestProfile, VideoProfile responseProfile)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
handleCallSessionEvent(int event)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
changePeerDimensions(int width, int height)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
changeVideoQuality(int videoQuality)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
changeCallDataUsage(long dataUsage)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
changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities)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
onSetCamera(String cameraId)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
onSetCamera(String cameraId, String callingPackage, int callingUid, int callingPid, int targetSdkVersion)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
onSetPreviewSurface(Surface surface)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
onSetDisplaySurface(Surface surface)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
onSetDeviceOrientation(int rotation)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
onSetZoom(float value)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
onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile)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
onSendSessionModifyResponse(VideoProfile responseProfile)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
onRequestCameraCapabilities()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
onRequestConnectionDataUsage()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
onSetPauseImage(Uri uri)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      */
addListener(Listener listener)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      */
removeListener(Listener listener)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      */
logFromInCall(String toLog)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      */
logFromVideoProvider(String toLog)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      */
canUseCamera(Context context, String callingPackage, int callingUid, int callingPid)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