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