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