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