FaceUnlock.java revision 5cf17879a31b7b78c09ec50b727f921840dcf783
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.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.os.UserHandle; 35import android.util.Log; 36import android.view.View; 37 38public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback { 39 40 private static final boolean DEBUG = KeyguardConstants.DEBUG; 41 private static final String TAG = "FULLockscreen"; 42 private static final String FACE_LOCK_PACKAGE = "com.android.facelock"; 43 44 private final Context mContext; 45 private final LockPatternUtils mLockPatternUtils; 46 47 // TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null? 48 private boolean mServiceRunning = false; 49 // TODO: now that the code has been restructure to do almost all operations from a handler, this 50 // lock may no longer be necessary. 51 private final Object mServiceRunningLock = new Object(); 52 private IFaceLockInterface mService; 53 private boolean mBoundToService = false; 54 private View mFaceUnlockView; 55 56 private Handler mHandler; 57 private final int MSG_SERVICE_CONNECTED = 0; 58 private final int MSG_SERVICE_DISCONNECTED = 1; 59 private final int MSG_UNLOCK = 2; 60 private final int MSG_CANCEL = 3; 61 private final int MSG_REPORT_FAILED_ATTEMPT = 4; 62 private final int MSG_POKE_WAKELOCK = 5; 63 64 // TODO: This was added for the purpose of adhering to what the biometric interface expects 65 // the isRunning() function to return. However, it is probably not necessary to have both 66 // mRunning and mServiceRunning. I'd just rather wait to change that logic. 67 private volatile boolean mIsRunning = false; 68 69 // So the user has a consistent amount of time when brought to the backup method from Face 70 // Unlock 71 private final int BACKUP_LOCK_TIMEOUT = 5000; 72 73 KeyguardSecurityCallback mKeyguardScreenCallback; 74 75 /** 76 * Stores some of the structures that Face Unlock will need to access and creates the handler 77 * will be used to execute messages on the UI thread. 78 */ 79 public FaceUnlock(Context context) { 80 mContext = context; 81 mLockPatternUtils = new LockPatternUtils(context); 82 mHandler = new Handler(this); 83 } 84 85 public void setKeyguardCallback(KeyguardSecurityCallback keyguardScreenCallback) { 86 mKeyguardScreenCallback = keyguardScreenCallback; 87 } 88 89 /** 90 * Stores and displays the view that Face Unlock is allowed to draw within. 91 * TODO: since the layout object will eventually be shared by multiple biometric unlock 92 * methods, we will have to add our other views (background, cancel button) here. 93 */ 94 public void initializeView(View biometricUnlockView) { 95 Log.d(TAG, "initializeView()"); 96 mFaceUnlockView = biometricUnlockView; 97 } 98 99 /** 100 * Indicates whether Face Unlock is currently running. 101 */ 102 public boolean isRunning() { 103 return mIsRunning; 104 } 105 106 /** 107 * Dismisses face unlock and goes to the backup lock 108 */ 109 public void stopAndShowBackup() { 110 if (DEBUG) Log.d(TAG, "stopAndShowBackup()"); 111 mHandler.sendEmptyMessage(MSG_CANCEL); 112 } 113 114 /** 115 * Binds to the Face Unlock service. Face Unlock will be started when the bind completes. The 116 * Face Unlock view is displayed to hide the backup lock while the service is starting up. 117 * Called on the UI thread. 118 */ 119 public boolean start() { 120 if (DEBUG) Log.d(TAG, "start()"); 121 if (mHandler.getLooper() != Looper.myLooper()) { 122 Log.e(TAG, "start() called off of the UI thread"); 123 } 124 125 if (mIsRunning) { 126 Log.w(TAG, "start() called when already running"); 127 } 128 129 if (!mBoundToService) { 130 Log.d(TAG, "Binding to Face Unlock service for user=" 131 + mLockPatternUtils.getCurrentUser()); 132 mContext.bindServiceAsUser( 133 new Intent(IFaceLockInterface.class.getName()).setPackage(FACE_LOCK_PACKAGE), 134 mConnection, 135 Context.BIND_AUTO_CREATE, 136 new UserHandle(mLockPatternUtils.getCurrentUser())); 137 mBoundToService = true; 138 } else { 139 Log.w(TAG, "Attempt to bind to Face Unlock when already bound"); 140 } 141 142 mIsRunning = true; 143 return true; 144 } 145 146 /** 147 * Stops Face Unlock and unbinds from the service. Called on the UI thread. 148 */ 149 public boolean stop() { 150 if (DEBUG) Log.d(TAG, "stop()"); 151 if (mHandler.getLooper() != Looper.myLooper()) { 152 Log.e(TAG, "stop() called from non-UI thread"); 153 } 154 155 // Clearing any old service connected messages. 156 mHandler.removeMessages(MSG_SERVICE_CONNECTED); 157 158 boolean mWasRunning = mIsRunning; 159 160 stopUi(); 161 162 if (mBoundToService) { 163 if (mService != null) { 164 try { 165 mService.unregisterCallback(mFaceUnlockCallback); 166 } catch (RemoteException e) { 167 // Not much we can do 168 } 169 } 170 Log.d(TAG, "Unbinding from Face Unlock service"); 171 mContext.unbindService(mConnection); 172 mBoundToService = false; 173 } else { 174 // This is usually not an error when this happens. Sometimes we will tell it to 175 // unbind multiple times because it's called from both onWindowFocusChanged and 176 // onDetachedFromWindow. 177 if (DEBUG) Log.d(TAG, "Attempt to unbind from Face Unlock when not bound"); 178 } 179 mIsRunning = false; 180 return mWasRunning; 181 } 182 183 /** 184 * Frees up resources used by Face Unlock and stops it if it is still running. 185 */ 186 public void cleanUp() { 187 if (DEBUG) Log.d(TAG, "cleanUp()"); 188 if (mService != null) { 189 try { 190 mService.unregisterCallback(mFaceUnlockCallback); 191 } catch (RemoteException e) { 192 // Not much we can do 193 } 194 stopUi(); 195 mService = null; 196 } 197 } 198 199 /** 200 * Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK. 201 */ 202 public int getQuality() { 203 return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; 204 } 205 206 /** 207 * Handles messages such that everything happens on the UI thread in a deterministic order. 208 * Calls from the Face Unlock service come from binder threads. Calls from lockscreen typically 209 * come from the UI thread. This makes sure there are no race conditions between those calls. 210 */ 211 public boolean handleMessage(Message msg) { 212 switch (msg.what) { 213 case MSG_SERVICE_CONNECTED: 214 handleServiceConnected(); 215 break; 216 case MSG_SERVICE_DISCONNECTED: 217 handleServiceDisconnected(); 218 break; 219 case MSG_UNLOCK: 220 handleUnlock(msg.arg1); 221 break; 222 case MSG_CANCEL: 223 handleCancel(); 224 break; 225 case MSG_REPORT_FAILED_ATTEMPT: 226 handleReportFailedAttempt(); 227 break; 228 case MSG_POKE_WAKELOCK: 229 handlePokeWakelock(msg.arg1); 230 break; 231 default: 232 Log.e(TAG, "Unhandled message"); 233 return false; 234 } 235 return true; 236 } 237 238 /** 239 * Tells the service to start its UI via an AIDL interface. Called when the 240 * onServiceConnected() callback is received. 241 */ 242 void handleServiceConnected() { 243 Log.d(TAG, "handleServiceConnected()"); 244 245 // It is possible that an unbind has occurred in the time between the bind and when this 246 // function is reached. If an unbind has already occurred, proceeding on to call startUi() 247 // can result in a fatal error. Note that the onServiceConnected() callback is 248 // asynchronous, so this possibility would still exist if we executed this directly in 249 // onServiceConnected() rather than using a handler. 250 if (!mBoundToService) { 251 Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound"); 252 return; 253 } 254 255 try { 256 mService.registerCallback(mFaceUnlockCallback); 257 } catch (RemoteException e) { 258 Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString()); 259 mService = null; 260 mBoundToService = false; 261 mIsRunning = false; 262 return; 263 } 264 265 if (mFaceUnlockView != null) { 266 IBinder windowToken = mFaceUnlockView.getWindowToken(); 267 if (windowToken != null) { 268 // When switching between portrait and landscape view while Face Unlock is running, 269 // the screen will eventually go dark unless we poke the wakelock when Face Unlock 270 // is restarted. 271 mKeyguardScreenCallback.userActivity(0); 272 273 int[] position; 274 position = new int[2]; 275 mFaceUnlockView.getLocationInWindow(position); 276 startUi(windowToken, position[0], position[1], mFaceUnlockView.getWidth(), 277 mFaceUnlockView.getHeight()); 278 } else { 279 Log.e(TAG, "windowToken is null in handleServiceConnected()"); 280 } 281 } 282 } 283 284 /** 285 * Called when the onServiceDisconnected() callback is received. This should not happen during 286 * normal operation. It indicates an error has occurred. 287 */ 288 void handleServiceDisconnected() { 289 Log.e(TAG, "handleServiceDisconnected()"); 290 // TODO: this lock may no longer be needed now that everything is being called from a 291 // handler 292 synchronized (mServiceRunningLock) { 293 mService = null; 294 mServiceRunning = false; 295 } 296 mBoundToService = false; 297 mIsRunning = false; 298 } 299 300 /** 301 * Stops the Face Unlock service and tells the device to grant access to the user. 302 */ 303 void handleUnlock(int authenticatedUserId) { 304 if (DEBUG) Log.d(TAG, "handleUnlock()"); 305 stop(); 306 int currentUserId = mLockPatternUtils.getCurrentUser(); 307 if (authenticatedUserId == currentUserId) { 308 if (DEBUG) Log.d(TAG, "Unlocking for user " + authenticatedUserId); 309 mKeyguardScreenCallback.reportUnlockAttempt(true); 310 mKeyguardScreenCallback.dismiss(true); 311 } else { 312 Log.d(TAG, "Ignoring unlock for authenticated user (" + authenticatedUserId + 313 ") because the current user is " + currentUserId); 314 } 315 } 316 317 /** 318 * Stops the Face Unlock service and goes to the backup lock. 319 */ 320 void handleCancel() { 321 if (DEBUG) Log.d(TAG, "handleCancel()"); 322 // We are going to the backup method, so we don't want to see Face Unlock again until the 323 // next time the user visits keyguard. 324 KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(false); 325 326 mKeyguardScreenCallback.showBackupSecurity(); 327 stop(); 328 mKeyguardScreenCallback.userActivity(BACKUP_LOCK_TIMEOUT); 329 } 330 331 /** 332 * Increments the number of failed Face Unlock attempts. 333 */ 334 void handleReportFailedAttempt() { 335 if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()"); 336 // We are going to the backup method, so we don't want to see Face Unlock again until the 337 // next time the user visits keyguard. 338 KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(false); 339 340 mKeyguardScreenCallback.reportUnlockAttempt(false); 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 Message message = mHandler.obtainMessage(MSG_UNLOCK, UserHandle.getCallingUserId(), -1); 434 mHandler.sendMessage(message); 435 } 436 437 /** 438 * Called when Face Unlock wants to go to the backup. 439 */ 440 public void cancel() { 441 if (DEBUG) Log.d(TAG, "cancel()"); 442 mHandler.sendEmptyMessage(MSG_CANCEL); 443 } 444 445 /** 446 * Called when Face Unlock wants to increment the number of failed attempts. 447 */ 448 public void reportFailedAttempt() { 449 if (DEBUG) Log.d(TAG, "reportFailedAttempt()"); 450 mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT); 451 } 452 453 /** 454 * Called when Face Unlock wants to keep the screen alive and active for a specific amount 455 * of time. 456 */ 457 public void pokeWakelock(int millis) { 458 if (DEBUG) Log.d(TAG, "pokeWakelock() for " + millis + "ms"); 459 Message message = mHandler.obtainMessage(MSG_POKE_WAKELOCK, millis, -1); 460 mHandler.sendMessage(message); 461 } 462 463 }; 464} 465