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