Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2014 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.systemui.statusbar.phone;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.pm.PackageManager;
     22 import android.content.pm.ResolveInfo;
     23 import android.hardware.camera2.CameraManager;
     24 import android.os.AsyncTask;
     25 import android.os.Handler;
     26 import android.provider.MediaStore;
     27 import android.util.Log;
     28 
     29 import com.android.internal.widget.LockPatternUtils;
     30 
     31 import java.util.HashMap;
     32 import java.util.List;
     33 import java.util.Map;
     34 
     35 /**
     36  * Handles launching the secure camera properly even when other applications may be using the camera
     37  * hardware.
     38  *
     39  * When other applications (e.g., Face Unlock) are using the camera, they must close the camera to
     40  * allow the secure camera to open it.  Since we want to minimize the delay when opening the secure
     41  * camera, other apps should close the camera at the first possible opportunity (i.e., as soon as
     42  * the user begins swiping to go to the secure camera).
     43  *
     44  * If the camera is unavailable when the user begins to swipe, the SecureCameraLaunchManager sends a
     45  * broadcast to tell other apps to close the camera.  When and if the user completes their swipe to
     46  * launch the secure camera, the SecureCameraLaunchManager delays launching the secure camera until
     47  * a callback indicates that the camera has become available.  If it doesn't receive that callback
     48  * within a specified timeout period, the secure camera is launched anyway.
     49  *
     50  * Ideally, the secure camera would handle waiting for the camera to become available.  This allows
     51  * some of the time necessary to close the camera to happen in parallel with starting the secure
     52  * camera app.  We can't rely on all third-party camera apps to handle this.  However, an app can
     53  * put com.android.systemui.statusbar.phone.will_wait_for_camera_available in its meta-data to
     54  * indicate that it will be responsible for waiting for the camera to become available.
     55  *
     56  * It is assumed that the functions in this class, including the constructor, will be called from
     57  * the UI thread.
     58  */
     59 public class SecureCameraLaunchManager {
     60     private static final boolean DEBUG = false;
     61     private static final String TAG = "SecureCameraLaunchManager";
     62 
     63     // Action sent as a broadcast to tell other apps to stop using the camera.  Other apps that use
     64     // the camera from keyguard (e.g., Face Unlock) should listen for this broadcast and close the
     65     // camera as soon as possible after receiving it.
     66     private static final String CLOSE_CAMERA_ACTION_NAME =
     67             "com.android.systemui.statusbar.phone.CLOSE_CAMERA";
     68 
     69     // Apps should put this field in their meta-data to indicate that they will take on the
     70     // responsibility of waiting for the camera to become available.  If this field is present, the
     71     // SecureCameraLaunchManager launches the secure camera even if the camera hardware has not
     72     // become available.  Having the secure camera app do the waiting is the optimal approach, but
     73     // without this field, the SecureCameraLaunchManager doesn't launch the secure camera until the
     74     // camera hardware is available.
     75     private static final String META_DATA_WILL_WAIT_FOR_CAMERA_AVAILABLE =
     76             "com.android.systemui.statusbar.phone.will_wait_for_camera_available";
     77 
     78     // If the camera hardware hasn't become available after this period of time, the
     79     // SecureCameraLaunchManager launches the secure camera anyway.
     80     private static final int CAMERA_AVAILABILITY_TIMEOUT_MS = 1000;
     81 
     82     private Context mContext;
     83     private Handler mHandler;
     84     private LockPatternUtils mLockPatternUtils;
     85     private KeyguardBottomAreaView mKeyguardBottomArea;
     86 
     87     private CameraManager mCameraManager;
     88     private CameraAvailabilityCallback mCameraAvailabilityCallback;
     89     private Map<String, Boolean> mCameraAvailabilityMap;
     90     private boolean mWaitingToLaunchSecureCamera;
     91     private Runnable mLaunchCameraRunnable;
     92 
     93     private class CameraAvailabilityCallback extends CameraManager.AvailabilityCallback {
     94         @Override
     95         public void onCameraUnavailable(String cameraId) {
     96             if (DEBUG) Log.d(TAG, "onCameraUnavailble(" + cameraId + ")");
     97             mCameraAvailabilityMap.put(cameraId, false);
     98         }
     99 
    100         @Override
    101         public void onCameraAvailable(String cameraId) {
    102             if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")");
    103             mCameraAvailabilityMap.put(cameraId, true);
    104 
    105             // If we were waiting for the camera hardware to become available to launch the
    106             // secure camera, we can launch it now if all cameras are available.  If one or more
    107             // cameras are still not available, we will get this callback again for those
    108             // cameras.
    109             if (mWaitingToLaunchSecureCamera && areAllCamerasAvailable()) {
    110                 mKeyguardBottomArea.launchCamera();
    111                 mWaitingToLaunchSecureCamera = false;
    112 
    113                 // We no longer need to launch the camera after the timeout hits.
    114                 mHandler.removeCallbacks(mLaunchCameraRunnable);
    115             }
    116         }
    117     }
    118 
    119     public SecureCameraLaunchManager(Context context, KeyguardBottomAreaView keyguardBottomArea) {
    120         mContext = context;
    121         mHandler = new Handler();
    122         mLockPatternUtils = new LockPatternUtils(context);
    123         mKeyguardBottomArea = keyguardBottomArea;
    124 
    125         mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    126         mCameraAvailabilityCallback = new CameraAvailabilityCallback();
    127 
    128         // An onCameraAvailable() or onCameraUnavailable() callback will be received for each camera
    129         // when the availability callback is registered, thus initializing the map.
    130         //
    131         // Keeping track of the state of all cameras using the onCameraAvailable() and
    132         // onCameraUnavailable() callbacks can get messy when dealing with hot-pluggable cameras.
    133         // However, we have a timeout in place such that we will never hang waiting for cameras.
    134         mCameraAvailabilityMap = new HashMap<String, Boolean>();
    135 
    136         mWaitingToLaunchSecureCamera = false;
    137         mLaunchCameraRunnable = new Runnable() {
    138                 @Override
    139                 public void run() {
    140                     if (mWaitingToLaunchSecureCamera) {
    141                         Log.w(TAG, "Timeout waiting for camera availability");
    142                         mKeyguardBottomArea.launchCamera();
    143                         mWaitingToLaunchSecureCamera = false;
    144                     }
    145                 }
    146             };
    147     }
    148 
    149     /**
    150      * Initializes the SecureCameraManager and starts listening for camera availability.
    151      */
    152     public void create() {
    153         mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, mHandler);
    154     }
    155 
    156     /**
    157      * Stops listening for camera availability and cleans up the SecureCameraManager.
    158      */
    159     public void destroy() {
    160         mCameraManager.unregisterAvailabilityCallback(mCameraAvailabilityCallback);
    161     }
    162 
    163     /**
    164      * Called when the user is starting to swipe horizontally, possibly to start the secure camera.
    165      * Although this swipe ultimately may not result in the secure camera opening, we need to stop
    166      * all other camera usage (e.g., Face Unlock) as soon as possible.  We send out a broadcast to
    167      * notify other apps that they should close the camera immediately.  The broadcast is sent even
    168      * if the camera appears to be available, because there could be an app that is about to open
    169      * the camera.
    170      */
    171     public void onSwipingStarted() {
    172         if (DEBUG) Log.d(TAG, "onSwipingStarted");
    173         AsyncTask.execute(new Runnable() {
    174                 @Override
    175                 public void run() {
    176                     Intent intent = new Intent();
    177                     intent.setAction(CLOSE_CAMERA_ACTION_NAME);
    178                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    179                     mContext.sendBroadcast(intent);
    180                 }
    181             });
    182     }
    183 
    184     /**
    185      * Called when the secure camera should be started.  If the camera is available or the secure
    186      * camera app has indicated that it will wait for camera availability, the secure camera app is
    187      * launched immediately.  Otherwise, we wait for the camera to become available (or timeout)
    188      * before launching the secure camera.
    189      */
    190     public void startSecureCameraLaunch() {
    191         if (DEBUG) Log.d(TAG, "startSecureCameraLunch");
    192         if (areAllCamerasAvailable() || targetWillWaitForCameraAvailable()) {
    193             mKeyguardBottomArea.launchCamera();
    194         } else {
    195             mWaitingToLaunchSecureCamera = true;
    196             mHandler.postDelayed(mLaunchCameraRunnable, CAMERA_AVAILABILITY_TIMEOUT_MS);
    197         }
    198     }
    199 
    200     /**
    201      * Returns true if all of the cameras we are tracking are currently available.
    202      */
    203     private boolean areAllCamerasAvailable() {
    204         for (boolean cameraAvailable: mCameraAvailabilityMap.values()) {
    205             if (!cameraAvailable) {
    206                 return false;
    207             }
    208         }
    209         return true;
    210     }
    211 
    212     /**
    213      * Determines if the secure camera app will wait for the camera hardware to become available
    214      * before trying to open the camera.  If so, we can fire off an intent to start the secure
    215      * camera app before the camera is available.  Otherwise, it is our responsibility to wait for
    216      * the camera hardware to become available before firing off the intent to start the secure
    217      * camera.
    218      *
    219      * Ideally we are able to fire off the secure camera intent as early as possibly so that, if the
    220      * camera is closing, it can continue to close while the secure camera app is opening.  This
    221      * improves secure camera startup time.
    222      */
    223     private boolean targetWillWaitForCameraAvailable() {
    224         // Create intent that would launch the secure camera.
    225         Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
    226                 .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    227         PackageManager packageManager = mContext.getPackageManager();
    228 
    229         // Get the list of applications that can handle the intent.
    230         final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
    231                 intent, PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser());
    232         if (appList.size() == 0) {
    233             if (DEBUG) Log.d(TAG, "No targets found for secure camera intent");
    234             return false;
    235         }
    236 
    237         // Get the application that the intent resolves to.
    238         ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
    239                 PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
    240                 mLockPatternUtils.getCurrentUser());
    241 
    242         if (resolved == null || resolved.activityInfo == null) {
    243             return false;
    244         }
    245 
    246         // If we would need to launch the resolver activity, then we can't assume that the target
    247         // is one that would wait for the camera.
    248         if (wouldLaunchResolverActivity(resolved, appList)) {
    249             if (DEBUG) Log.d(TAG, "Secure camera intent would launch resolver");
    250             return false;
    251         }
    252 
    253         // If the target doesn't have meta-data we must assume it won't wait for the camera.
    254         if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) {
    255             if (DEBUG) Log.d(TAG, "No meta-data found for secure camera application");
    256             return false;
    257         }
    258 
    259         // Check the secure camera app meta-data to see if it indicates that it will wait for the
    260         // camera to become available.
    261         boolean willWaitForCameraAvailability =
    262                 resolved.activityInfo.metaData.getBoolean(META_DATA_WILL_WAIT_FOR_CAMERA_AVAILABLE);
    263 
    264         if (DEBUG) Log.d(TAG, "Target will wait for camera: " + willWaitForCameraAvailability);
    265 
    266         return willWaitForCameraAvailability;
    267     }
    268 
    269     /**
    270      * Determines if the activity that would be launched by the intent is the ResolverActivity.
    271      */
    272     private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
    273         // If the list contains the resolved activity, then it can't be the ResolverActivity itself.
    274         for (int i = 0; i < appList.size(); i++) {
    275             ResolveInfo tmp = appList.get(i);
    276             if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
    277                     && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
    278                 return false;
    279             }
    280         }
    281         return true;
    282     }
    283 }
    284