FaceUnlock.java revision 1108a2cb412b054cc9e4acc48182c46c45180c0c
1/* 2 * Copyright (C) 2012 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 17package com.android.internal.policy.impl.keyguard; 18 19import com.android.internal.policy.IFaceLockCallback; 20import com.android.internal.policy.IFaceLockInterface; 21import com.android.internal.widget.LockPatternUtils; 22 23import android.app.admin.DevicePolicyManager; 24import android.content.ComponentName; 25import android.content.Context; 26import android.content.Intent; 27import android.content.ServiceConnection; 28import android.os.Handler; 29import android.os.IBinder; 30import android.os.Looper; 31import android.os.Message; 32import android.os.PowerManager; 33import android.os.RemoteException; 34import android.util.Log; 35import android.view.View; 36 37public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback { 38 39 private static final boolean DEBUG = false; 40 private static final String TAG = "FULLockscreen"; 41 42 private final Context mContext; 43 private final LockPatternUtils mLockPatternUtils; 44 45 // TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null? 46 private boolean mServiceRunning = false; 47 // TODO: now that the code has been restructure to do almost all operations from a handler, this 48 // lock may no longer be necessary. 49 private final Object mServiceRunningLock = new Object(); 50 private IFaceLockInterface mService; 51 private boolean mBoundToService = false; 52 private View mFaceUnlockView; 53 54 private Handler mHandler; 55 private final int MSG_SERVICE_CONNECTED = 0; 56 private final int MSG_SERVICE_DISCONNECTED = 1; 57 private final int MSG_UNLOCK = 2; 58 private final int MSG_CANCEL = 3; 59 private final int MSG_REPORT_FAILED_ATTEMPT = 4; 60 private final int MSG_EXPOSE_FALLBACK = 5; 61 private final int MSG_POKE_WAKELOCK = 6; 62 63 // TODO: This was added for the purpose of adhering to what the biometric interface expects 64 // the isRunning() function to return. However, it is probably not necessary to have both 65 // mRunning and mServiceRunning. I'd just rather wait to change that logic. 66 private volatile boolean mIsRunning = false; 67 68 // So the user has a consistent amount of time when brought to the backup method from Face 69 // Unlock 70 private final int BACKUP_LOCK_TIMEOUT = 5000; 71 72 KeyguardSecurityCallback mKeyguardScreenCallback; 73 74 /** 75 * Stores some of the structures that Face Unlock will need to access and creates the handler 76 * will be used to execute messages on the UI thread. 77 */ 78 public FaceUnlock(Context context) { 79 mContext = context; 80 mLockPatternUtils = new LockPatternUtils(context); 81 mHandler = new Handler(this); 82 } 83 84 public void setKeyguardCallback(KeyguardSecurityCallback keyguardScreenCallback) { 85 mKeyguardScreenCallback = keyguardScreenCallback; 86 } 87 88 /** 89 * Stores and displays the view that Face Unlock is allowed to draw within. 90 * TODO: since the layout object will eventually be shared by multiple biometric unlock 91 * methods, we will have to add our other views (background, cancel button) here. 92 */ 93 public void initializeView(View biometricUnlockView) { 94 Log.d(TAG, "initializeView()"); 95 mFaceUnlockView = biometricUnlockView; 96 } 97 98 /** 99 * Indicates whether Face Unlock is currently running. 100 */ 101 public boolean isRunning() { 102 return mIsRunning; 103 } 104 105 /** 106 * Dismisses face unlock and goes to the backup lock 107 */ 108 public void stopAndShowBackup() { 109 if (DEBUG) Log.d(TAG, "stopAndShowBackup()"); 110 mHandler.sendEmptyMessage(MSG_CANCEL); 111 } 112 113 /** 114 * Binds to the Face Unlock service. Face Unlock will be started when the bind completes. The 115 * Face Unlock view is displayed to hide the backup lock while the service is starting up. 116 * Called on the UI thread. 117 */ 118 public boolean start() { 119 if (DEBUG) Log.d(TAG, "start()"); 120 if (mHandler.getLooper() != Looper.myLooper()) { 121 Log.e(TAG, "start() called off of the UI thread"); 122 } 123 124 if (mIsRunning) { 125 Log.w(TAG, "start() called when already running"); 126 } 127 128 if (!mBoundToService) { 129 Log.d(TAG, "Binding to Face Unlock service for user=" 130 + mLockPatternUtils.getCurrentUser()); 131 mContext.bindService(new Intent(IFaceLockInterface.class.getName()), 132 mConnection, 133 Context.BIND_AUTO_CREATE, 134 mLockPatternUtils.getCurrentUser()); 135 mBoundToService = true; 136 } else { 137 Log.w(TAG, "Attempt to bind to Face Unlock when already bound"); 138 } 139 140 mIsRunning = true; 141 return true; 142 } 143 144 /** 145 * Stops Face Unlock and unbinds from the service. Called on the UI thread. 146 */ 147 public boolean stop() { 148 if (DEBUG) Log.d(TAG, "stop()"); 149 if (mHandler.getLooper() != Looper.myLooper()) { 150 Log.e(TAG, "stop() called from non-UI thread"); 151 } 152 153 boolean mWasRunning = mIsRunning; 154 try { 155 if (mService != null) { 156 mService.makeInvisible(); 157 } 158 } catch (RemoteException e) { 159 Log.e(TAG, "Caught exception making Face Unlock invisible: " + e.toString()); 160 } 161 162 stopUi(); 163 164 if (mBoundToService) { 165 if (mService != null) { 166 try { 167 mService.unregisterCallback(mFaceUnlockCallback); 168 } catch (RemoteException e) { 169 // Not much we can do 170 } 171 } 172 Log.d(TAG, "Unbinding from Face Unlock service"); 173 mContext.unbindService(mConnection); 174 mBoundToService = false; 175 } else { 176 // This is usually not an error when this happens. Sometimes we will tell it to 177 // unbind multiple times because it's called from both onWindowFocusChanged and 178 // onDetachedFromWindow. 179 if (DEBUG) Log.d(TAG, "Attempt to unbind from Face Unlock when not bound"); 180 } 181 mIsRunning = false; 182 return mWasRunning; 183 } 184 185 /** 186 * Frees up resources used by Face Unlock and stops it if it is still running. 187 */ 188 public void cleanUp() { 189 if (DEBUG) Log.d(TAG, "cleanUp()"); 190 if (mService != null) { 191 try { 192 mService.unregisterCallback(mFaceUnlockCallback); 193 } catch (RemoteException e) { 194 // Not much we can do 195 } 196 stopUi(); 197 mService = null; 198 } 199 } 200 201 /** 202 * Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK. 203 */ 204 public int getQuality() { 205 return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; 206 } 207 208 /** 209 * Handles messages such that everything happens on the UI thread in a deterministic order. 210 * Calls from the Face Unlock service come from binder threads. Calls from lockscreen typically 211 * come from the UI thread. This makes sure there are no race conditions between those calls. 212 */ 213 public boolean handleMessage(Message msg) { 214 switch (msg.what) { 215 case MSG_SERVICE_CONNECTED: 216 handleServiceConnected(); 217 break; 218 case MSG_SERVICE_DISCONNECTED: 219 handleServiceDisconnected(); 220 break; 221 case MSG_UNLOCK: 222 handleUnlock(); 223 break; 224 case MSG_CANCEL: 225 handleCancel(); 226 break; 227 case MSG_REPORT_FAILED_ATTEMPT: 228 handleReportFailedAttempt(); 229 break; 230 case MSG_EXPOSE_FALLBACK: 231 handleExposeFallback(); 232 break; 233 case MSG_POKE_WAKELOCK: 234 handlePokeWakelock(msg.arg1); 235 break; 236 default: 237 Log.e(TAG, "Unhandled message"); 238 return false; 239 } 240 return true; 241 } 242 243 /** 244 * Tells the service to start its UI via an AIDL interface. Called when the 245 * onServiceConnected() callback is received. 246 */ 247 void handleServiceConnected() { 248 Log.d(TAG, "handleServiceConnected()"); 249 250 // It is possible that an unbind has occurred in the time between the bind and when this 251 // function is reached. If an unbind has already occurred, proceeding on to call startUi() 252 // can result in a fatal error. Note that the onServiceConnected() callback is 253 // asynchronous, so this possibility would still exist if we executed this directly in 254 // onServiceConnected() rather than using a handler. 255 if (!mBoundToService) { 256 Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound"); 257 return; 258 } 259 260 try { 261 mService.registerCallback(mFaceUnlockCallback); 262 } catch (RemoteException e) { 263 Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString()); 264 mService = null; 265 mBoundToService = false; 266 mIsRunning = false; 267 return; 268 } 269 270 if (mFaceUnlockView != null) { 271 IBinder windowToken = mFaceUnlockView.getWindowToken(); 272 if (windowToken != null) { 273 // When switching between portrait and landscape view while Face Unlock is running, 274 // the screen will eventually go dark unless we poke the wakelock when Face Unlock 275 // is restarted. 276 mKeyguardScreenCallback.userActivity(0); 277 278 int[] position; 279 position = new int[2]; 280 mFaceUnlockView.getLocationInWindow(position); 281 startUi(windowToken, position[0], position[1], mFaceUnlockView.getWidth(), 282 mFaceUnlockView.getHeight()); 283 } else { 284 Log.e(TAG, "windowToken is null in handleServiceConnected()"); 285 } 286 } 287 } 288 289 /** 290 * Called when the onServiceDisconnected() callback is received. This should not happen during 291 * normal operation. It indicates an error has occurred. 292 */ 293 void handleServiceDisconnected() { 294 Log.e(TAG, "handleServiceDisconnected()"); 295 // TODO: this lock may no longer be needed now that everything is being called from a 296 // handler 297 synchronized (mServiceRunningLock) { 298 mService = null; 299 mServiceRunning = false; 300 } 301 mBoundToService = false; 302 mIsRunning = false; 303 } 304 305 /** 306 * Stops the Face Unlock service and tells the device to grant access to the user. 307 */ 308 void handleUnlock() { 309 if (DEBUG) Log.d(TAG, "handleUnlock()"); 310 stop(); 311 mKeyguardScreenCallback.reportSuccessfulUnlockAttempt(); 312 mKeyguardScreenCallback.dismiss(true); 313 } 314 315 /** 316 * Stops the Face Unlock service and goes to the backup lock. 317 */ 318 void handleCancel() { 319 if (DEBUG) Log.d(TAG, "handleCancel()"); 320 mKeyguardScreenCallback.showBackupSecurity(); 321 stop(); 322 mKeyguardScreenCallback.userActivity(BACKUP_LOCK_TIMEOUT); 323 } 324 325 /** 326 * Increments the number of failed Face Unlock attempts. 327 */ 328 void handleReportFailedAttempt() { 329 if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()"); 330 mKeyguardScreenCallback.reportFailedUnlockAttempt(); 331 } 332 333 /** 334 * Hides the Face Unlock view to expose the backup lock. Called when the Face Unlock service UI 335 * is started, indicating there is no need to continue displaying the underlying view because 336 * the service UI is now covering the backup lock. 337 */ 338 void handleExposeFallback() { 339 if (DEBUG) Log.d(TAG, "handleExposeFallback()"); 340 // No longer required because face unlock doesn't cover backup unlock. 341 } 342 343 /** 344 * If the screen is on, pokes the wakelock to keep the screen alive and active for a specific 345 * amount of time. 346 */ 347 void handlePokeWakelock(int millis) { 348 PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 349 if (powerManager.isScreenOn()) { 350 mKeyguardScreenCallback.userActivity(millis); 351 } 352 } 353 354 /** 355 * Implements service connection methods. 356 */ 357 private ServiceConnection mConnection = new ServiceConnection() { 358 /** 359 * Called when the Face Unlock service connects after calling bind(). 360 */ 361 public void onServiceConnected(ComponentName className, IBinder iservice) { 362 Log.d(TAG, "Connected to Face Unlock service"); 363 mService = IFaceLockInterface.Stub.asInterface(iservice); 364 mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED); 365 } 366 367 /** 368 * Called if the Face Unlock service unexpectedly disconnects. This indicates an error. 369 */ 370 public void onServiceDisconnected(ComponentName className) { 371 Log.e(TAG, "Unexpected disconnect from Face Unlock service"); 372 mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED); 373 } 374 }; 375 376 /** 377 * Tells the Face Unlock service to start displaying its UI and start processing. 378 */ 379 private void startUi(IBinder windowToken, int x, int y, int w, int h) { 380 if (DEBUG) Log.d(TAG, "startUi()"); 381 synchronized (mServiceRunningLock) { 382 if (!mServiceRunning) { 383 Log.d(TAG, "Starting Face Unlock"); 384 try { 385 mService.startUi(windowToken, x, y, w, h, 386 mLockPatternUtils.isBiometricWeakLivelinessEnabled()); 387 } catch (RemoteException e) { 388 Log.e(TAG, "Caught exception starting Face Unlock: " + e.toString()); 389 return; 390 } 391 mServiceRunning = true; 392 } else { 393 Log.w(TAG, "startUi() attempted while running"); 394 } 395 } 396 } 397 398 /** 399 * Tells the Face Unlock service to stop displaying its UI and stop processing. 400 */ 401 private void stopUi() { 402 if (DEBUG) Log.d(TAG, "stopUi()"); 403 // Note that attempting to stop Face Unlock when it's not running is not an issue. 404 // Face Unlock can return, which stops it and then we try to stop it when the 405 // screen is turned off. That's why we check. 406 synchronized (mServiceRunningLock) { 407 if (mServiceRunning) { 408 Log.d(TAG, "Stopping Face Unlock"); 409 try { 410 mService.stopUi(); 411 } catch (RemoteException e) { 412 Log.e(TAG, "Caught exception stopping Face Unlock: " + e.toString()); 413 } 414 mServiceRunning = false; 415 } else { 416 // This is usually not an error when this happens. Sometimes we will tell it to 417 // stop multiple times because it's called from both onWindowFocusChanged and 418 // onDetachedFromWindow. 419 if (DEBUG) Log.d(TAG, "stopUi() attempted while not running"); 420 } 421 } 422 } 423 424 /** 425 * Implements the AIDL biometric unlock service callback interface. 426 */ 427 private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() { 428 /** 429 * Called when Face Unlock wants to grant access to the user. 430 */ 431 public void unlock() { 432 if (DEBUG) Log.d(TAG, "unlock()"); 433 mHandler.sendEmptyMessage(MSG_UNLOCK); 434 } 435 436 /** 437 * Called when Face Unlock wants to go to the backup. 438 */ 439 public void cancel() { 440 if (DEBUG) Log.d(TAG, "cancel()"); 441 mHandler.sendEmptyMessage(MSG_CANCEL); 442 } 443 444 /** 445 * Called when Face Unlock wants to increment the number of failed attempts. 446 */ 447 public void reportFailedAttempt() { 448 if (DEBUG) Log.d(TAG, "reportFailedAttempt()"); 449 mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT); 450 } 451 452 /** 453 * Called when the Face Unlock service starts displaying the UI, indicating that the backup 454 * unlock can be exposed because the Face Unlock service is now covering the backup with its 455 * UI. 456 */ 457 public void exposeFallback() { 458 if (DEBUG) Log.d(TAG, "exposeFallback()"); 459 mHandler.sendEmptyMessage(MSG_EXPOSE_FALLBACK); 460 } 461 462 /** 463 * Called when Face Unlock wants to keep the screen alive and active for a specific amount 464 * of time. 465 */ 466 public void pokeWakelock(int millis) { 467 if (DEBUG) Log.d(TAG, "pokeWakelock() for " + millis + "ms"); 468 Message message = mHandler.obtainMessage(MSG_POKE_WAKELOCK, millis, -1); 469 mHandler.sendMessage(message); 470 } 471 472 }; 473} 474