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