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