Merge "Changes to biometric sensor interface in lockscreen" into jb-dev

This commit is contained in:
Brian Colonna
2012-04-26 14:41:18 -07:00
committed by Android (Google) Code Review
5 changed files with 243 additions and 162 deletions

View File

@@ -19,33 +19,63 @@ package com.android.internal.policy.impl;
import android.view.View;
interface BiometricSensorUnlock {
// Returns 'true' if the biometric sensor has started its unlock procedure but has not yet
// accepted or rejected the user.
/**
* Initializes the view provided for the biometric unlock UI to work within. The provided area
* completely covers the backup unlock mechanism. The view is then displayed in the same manner
* as if {@link BiometricSensorUnlock#show(long)} was called with a timeout of 0.
* @param biometricUnlockView View provided for the biometric unlock UI.
*/
public void initializeView(View biometricUnlockView);
/**
* Indicates whether the biometric unlock is running. Before
* {@link BiometricSensorUnlock#start} is called, isRunning() returns false. After a successful
* call to {@link BiometricSensorUnlock#start}, isRunning() returns true until the biometric
* unlock completes, {@link BiometricSensorUnlock#stop} has been called, or an error has
* forced the biometric unlock to stop.
* @return whether the biometric unlock is currently running.
*/
public boolean isRunning();
// Show the interface, but don't start the unlock procedure. The interface should disappear
// after the specified timeout. If the timeout is 0, the interface shows until another event,
// such as calling hide(), causes it to disappear.
// Called on the UI Thread
/**
* Covers the backup unlock mechanism by showing the contents of the view initialized in
* {@link BiometricSensorUnlock#initializeView(View)}. The view should disappear after the
* specified timeout. If the timeout is 0, the interface shows until another event, such as
* calling {@link BiometricSensorUnlock#hide()}, causes it to disappear. Called on the UI
* thread.
* @param timeoutMilliseconds Amount of time in milliseconds to display the view before
* disappearing. A value of 0 means the view should remain visible.
*/
public void show(long timeoutMilliseconds);
// Hide the interface, if any, exposing the lockscreen.
/**
* Uncovers the backup unlock mechanism by hiding the contents of the view initialized in
* {@link BiometricSensorUnlock#initializeView(View)}.
*/
public void hide();
// Stop the unlock procedure if running. Returns 'true' if it was in fact running.
/**
* Binds to the biometric unlock service and starts the unlock procedure. Called on the UI
* thread.
* @return false if it can't be started or the backup should be used.
*/
public boolean start();
/**
* Stops the biometric unlock procedure and unbinds from the service.
* @return whether the biometric unlock was running when called.
*/
public boolean stop();
// Start the unlock procedure. Returns ‘false’ if it can’t be started or if the backup should
// be used.
// Called on the UI thread.
public boolean start(boolean suppressBiometricUnlock);
// Provide a view to work within.
public void initializeAreaView(View topView);
// Clean up any resources used by the biometric unlock.
/**
* Cleans up any resources used by the biometric unlock.
*/
public void cleanUp();
// Returns the Device Policy Manager quality (e.g. PASSWORD_QUALITY_BIOMETRIC_WEAK).
/**
* Gets the Device Policy Manager quality of the biometric unlock sensor
* (e.g., PASSWORD_QUALITY_BIOMETRIC_WEAK).
* @return biometric unlock sensor quality, as defined by Device Policy Manager.
*/
public int getQuality();
}

View File

@@ -40,31 +40,34 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
private static final String TAG = "FULLockscreen";
private final Context mContext;
private final LockPatternUtils mLockPatternUtils;
private final KeyguardUpdateMonitor mUpdateMonitor;
// TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null?
private boolean mServiceRunning = false;
private final Object mServiceRunningLock = new Object();
private IFaceLockInterface mService;
private boolean mBoundToService = false;
private View mAreaView;
private View mFaceUnlockView;
private Handler mHandler;
private final int MSG_SHOW_AREA_VIEW = 0;
private final int MSG_HIDE_AREA_VIEW = 1;
private boolean mServiceRunning = false;
private final Object mServiceRunningLock = new Object();
// TODO: This was added for the purpose of adhering to what the biometric interface expects
// the isRunning() function to return. However, it is probably not necessary to have both
// mRunning and mServiceRunning. I'd just rather wait to change that logic.
private boolean mIsRunning = false;
// Long enough to stay visible while the service starts
// Short enough to not have to wait long for backup if service fails to start or crashes
// The service can take a couple of seconds to start on the first try after boot
private final int VIEW_AREA_SERVICE_TIMEOUT = 3000;
// So the user has a consistent amount of time when brought to the backup method from FaceLock
// So the user has a consistent amount of time when brought to the backup method from Face
// Unlock
private final int BACKUP_LOCK_TIMEOUT = 5000;
/**
* Used to lookup the state of the lock pattern
*/
private final LockPatternUtils mLockPatternUtils;
KeyguardScreenCallback mKeyguardScreenCallback;
@@ -77,102 +80,116 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
mHandler = new Handler(this);
}
public boolean isRunning() {
return mServiceRunning;
/**
* Stores and displays the view that Face Unlock is allowed to draw within.
* TODO: since the layout object will eventually be shared by multiple biometric unlock
* methods, we will have to add our other views (background, cancel button) here.
*/
public void initializeView(View biometricUnlockView) {
mFaceUnlockView = biometricUnlockView;
show(0);
}
// Shows the FaceLock area for a period of time
// Called on the UI thread
/**
* Indicates whether Face Unlock is currently running.
*/
public boolean isRunning() {
return mIsRunning;
}
/**
* Sets the Face Unlock view to visible, hiding it after the specified amount of time. If
* timeoutMillis is 0, no hide is performed.
*/
public void show(long timeoutMillis) {
removeAreaDisplayMessages();
showArea();
if (timeoutMillis > 0)
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.VISIBLE);
}
if (timeoutMillis > 0) {
mHandler.sendEmptyMessageDelayed(MSG_HIDE_AREA_VIEW, timeoutMillis);
}
}
// Hides the FaceLock area immediately
/**
* Hides the Face Unlock view.
*/
public void hide() {
// Remove messages to prevent a delayed show message from undo-ing the hide
removeAreaDisplayMessages();
mHandler.sendEmptyMessage(MSG_HIDE_AREA_VIEW);
}
// Tells FaceLock to stop and then unbinds from the FaceLock service
/**
* Binds to the Face Unlock service. Face Unlock will be started when the bind completes. The
* Face Unlock area is displayed to hide the backup while the service is starting up.
*/
public boolean start() {
if (mIsRunning) {
Log.w(TAG, "start() called when already running");
}
// Show Face Unlock view, but only for a little bit so lockpattern will become visible if
// Face Unlock fails to start or crashes
// This must show before bind to guarantee that Face Unlock has a place to display
show(VIEW_AREA_SERVICE_TIMEOUT);
if (!mBoundToService) {
if (DEBUG) Log.d(TAG, "before bind to Face Unlock service");
mContext.bindService(new Intent(IFaceLockInterface.class.getName()),
mConnection,
Context.BIND_AUTO_CREATE,
mLockPatternUtils.getCurrentUser());
if (DEBUG) Log.d(TAG, "after bind to Face Unlock service");
mBoundToService = true;
} else {
Log.w(TAG, "Attempt to bind to Face Unlock when already bound");
}
// When switching between portrait and landscape view while Face Unlock is running, the
// screen will eventually go dark unless we poke the wakelock when Face Unlock is
// restarted
mKeyguardScreenCallback.pokeWakelock();
mIsRunning = true;
return true;
}
/**
* Stops Face Unlock and unbinds from the service.
*/
public boolean stop() {
boolean wasRunning = false;
boolean mWasRunning = mIsRunning;
stopUi();
if (mBoundToService) {
wasRunning = true;
if (DEBUG) Log.d(TAG, "before unbind from FaceLock service");
if (DEBUG) Log.d(TAG, "before unbind from Face Unlock service");
if (mService != null) {
try {
mService.unregisterCallback(mFaceLockCallback);
mService.unregisterCallback(mFaceUnlockCallback);
} catch (RemoteException e) {
// Not much we can do
}
}
mContext.unbindService(mConnection);
if (DEBUG) Log.d(TAG, "after unbind from FaceLock service");
if (DEBUG) Log.d(TAG, "after unbind from Face Unlock service");
mBoundToService = false;
} else {
// This is usually not an error when this happens. Sometimes we will tell it to
// unbind multiple times because it's called from both onWindowFocusChanged and
// onDetachedFromWindow.
if (DEBUG) Log.d(TAG, "Attempt to unbind from FaceLock when not bound");
if (DEBUG) Log.d(TAG, "Attempt to unbind from Face Unlock when not bound");
}
return wasRunning;
mIsRunning = false;
return mWasRunning;
}
/**
* When screen is turned on and focused, need to bind to FaceLock service if we are using
* FaceLock, but only if we're not dealing with a call
* Called on the UI thread
* Frees up resources used by Face Unlock and stops it if it is still running.
*/
public boolean start(boolean suppressBiometricUnlock) {
final boolean tooManyFaceUnlockTries = mUpdateMonitor.getMaxFaceUnlockAttemptsReached();
final int failedBackupAttempts = mUpdateMonitor.getFailedAttempts();
final boolean backupIsTimedOut =
(failedBackupAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
if (tooManyFaceUnlockTries) Log.i(TAG, "tooManyFaceUnlockTries: " + tooManyFaceUnlockTries);
if (mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
&& !suppressBiometricUnlock
&& !tooManyFaceUnlockTries
&& !backupIsTimedOut) {
// Show FaceLock area, but only for a little bit so lockpattern will become visible if
// FaceLock fails to start or crashes
// This must show before bind to guarantee that Face Unlock has a place to display
show(VIEW_AREA_SERVICE_TIMEOUT);
bind();
// When switching between portrait and landscape view while FaceLock is running, the
// screen will eventually go dark unless we poke the wakelock when FaceLock is
// restarted
mKeyguardScreenCallback.pokeWakelock();
} else {
hide();
return false;
}
return true;
}
// Takes care of FaceLock area when layout is created
public void initializeAreaView(View topView) {
mAreaView = topView.findViewById(R.id.faceLockAreaView);
if (mAreaView == null) {
Log.e(TAG, "Layout does not have areaView and FaceLock is enabled");
} else {
show(0);
}
}
public void cleanUp() {
if (mService != null) {
try {
mService.unregisterCallback(mFaceLockCallback);
mService.unregisterCallback(mFaceUnlockCallback);
} catch (RemoteException e) {
// Not much we can do
}
@@ -181,30 +198,31 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
}
}
/**
* Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK.
*/
public int getQuality() {
return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
}
// Shows the FaceLock area
// Called on the UI thread
private void showArea() {
if (mAreaView != null) {
mAreaView.setVisibility(View.VISIBLE);
}
}
// Handles covering or exposing FaceLock area on the client side when FaceLock starts or stops
// This needs to be done in a handler because the call could be coming from a callback from the
// FaceLock service that is in a thread that can't modify the UI
/**
* Handles showing the Face Unlock view (hiding the backup lock) and hiding the Face Unlock view
* (exposing the backup lock). In cases where 'show' needs to happen immediately,
* setVisibility() is called directly (without using this handler). This handler is used when
* 'show' needs to happen from a non-UI thread. It also handles hide() messages since they
* often require a delay.
*/
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_SHOW_AREA_VIEW:
showArea();
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.VISIBLE);
}
break;
case MSG_HIDE_AREA_VIEW:
if (mAreaView != null) {
mAreaView.setVisibility(View.INVISIBLE);
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.INVISIBLE);
}
break;
default:
@@ -214,53 +232,44 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
return true;
}
// Removes show and hide messages from the message queue
/**
* Removes show and hide messages from the message queue
*/
private void removeAreaDisplayMessages() {
mHandler.removeMessages(MSG_SHOW_AREA_VIEW);
mHandler.removeMessages(MSG_HIDE_AREA_VIEW);
}
// Binds to FaceLock service. This call does not tell it to start, but it causes the service
// to call the onServiceConnected callback, which then starts FaceLock.
private void bind() {
if (!mBoundToService) {
if (DEBUG) Log.d(TAG, "before bind to FaceLock service");
mContext.bindService(new Intent(IFaceLockInterface.class.getName()),
mConnection,
Context.BIND_AUTO_CREATE,
mLockPatternUtils.getCurrentUser());
if (DEBUG) Log.d(TAG, "after bind to FaceLock service");
mBoundToService = true;
} else {
Log.w(TAG, "Attempt to bind to FaceLock when already bound");
}
}
private ServiceConnection mConnection = new ServiceConnection() {
// Completes connection, registers callback and starts FaceLock when service is bound
/**
* Completes connection, registers callback, and starts Face Unlock when service is bound
*/
@Override
public void onServiceConnected(ComponentName className, IBinder iservice) {
mService = IFaceLockInterface.Stub.asInterface(iservice);
if (DEBUG) Log.d(TAG, "Connected to FaceLock service");
if (DEBUG) Log.d(TAG, "Connected to Face Unlock service");
try {
mService.registerCallback(mFaceLockCallback);
mService.registerCallback(mFaceUnlockCallback);
} catch (RemoteException e) {
Log.e(TAG, "Caught exception connecting to FaceLock: " + e.toString());
Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString());
mService = null;
mBoundToService = false;
mIsRunning = false;
return;
}
if (mAreaView != null) {
if (mFaceUnlockView != null) {
int[] position;
position = new int[2];
mAreaView.getLocationInWindow(position);
startUi(mAreaView.getWindowToken(), position[0], position[1],
mAreaView.getWidth(), mAreaView.getHeight());
mFaceUnlockView.getLocationInWindow(position);
startUi(mFaceUnlockView.getWindowToken(), position[0], position[1],
mFaceUnlockView.getWidth(), mFaceUnlockView.getHeight());
}
}
// Cleans up if FaceLock service unexpectedly disconnects
/**
* Cleans up if Face Unlock service unexpectedly disconnects
*/
@Override
public void onServiceDisconnected(ComponentName className) {
synchronized(mServiceRunningLock) {
@@ -268,20 +277,23 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
mServiceRunning = false;
}
mBoundToService = false;
Log.w(TAG, "Unexpected disconnect from FaceLock service");
mIsRunning = false;
Log.w(TAG, "Unexpected disconnect from Face Unlock service");
}
};
// Tells the FaceLock service to start displaying its UI and perform recognition
/**
* Tells the Face Unlock service to start displaying its UI and perform recognition
*/
private void startUi(IBinder windowToken, int x, int y, int w, int h) {
synchronized (mServiceRunningLock) {
if (!mServiceRunning) {
if (DEBUG) Log.d(TAG, "Starting FaceLock");
if (DEBUG) Log.d(TAG, "Starting Face Unlock");
try {
mService.startUi(windowToken, x, y, w, h,
mLockPatternUtils.isBiometricWeakLivelinessEnabled());
} catch (RemoteException e) {
Log.e(TAG, "Caught exception starting FaceLock: " + e.toString());
Log.e(TAG, "Caught exception starting Face Unlock: " + e.toString());
return;
}
mServiceRunning = true;
@@ -291,30 +303,36 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
}
}
// Tells the FaceLock service to stop displaying its UI and stop recognition
/**
* Tells the Face Unlock service to stop displaying its UI and stop recognition
*/
private void stopUi() {
// Note that attempting to stop FaceLock when it's not running is not an issue.
// FaceLock can return, which stops it and then we try to stop it when the
// Note that attempting to stop Face Unlock when it's not running is not an issue.
// Face Unlock can return, which stops it and then we try to stop it when the
// screen is turned off. That's why we check.
synchronized (mServiceRunningLock) {
if (mServiceRunning) {
try {
if (DEBUG) Log.d(TAG, "Stopping FaceLock");
if (DEBUG) Log.d(TAG, "Stopping Face Unlock");
mService.stopUi();
} catch (RemoteException e) {
Log.e(TAG, "Caught exception stopping FaceLock: " + e.toString());
Log.e(TAG, "Caught exception stopping Face Unlock: " + e.toString());
}
mServiceRunning = false;
}
}
}
// Implements the FaceLock service callback interface defined in AIDL
private final IFaceLockCallback mFaceLockCallback = new IFaceLockCallback.Stub() {
// Stops the FaceLock UI and indicates that the phone should be unlocked
/**
* Implements the biometric unlock service callback interface defined in AIDL
*/
private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() {
/**
* Stops the Face Unlock UI and indicates that the phone should be unlocked
*/
@Override
public void unlock() {
if (DEBUG) Log.d(TAG, "FaceLock unlock()");
if (DEBUG) Log.d(TAG, "unlock()");
// Keep fallback covered
removeAreaDisplayMessages();
@@ -326,38 +344,46 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
}
// Stops the FaceLock UI and exposes the backup method without unlocking
// This means the user has cancelled out
/**
* Stops the Face Unlock UI and exposes the backup method without unlocking
* This means the user has cancelled out
*/
@Override
public void cancel() {
if (DEBUG) Log.d(TAG, "FaceLock cancel()");
if (DEBUG) Log.d(TAG, "cancel()");
hide(); // Expose fallback
stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
// Stops the FaceLock UI and exposes the backup method without unlocking
// This means FaceLock failed to recognize them
/**
* Stops the Face Unlock UI and exposes the backup method without unlocking
* This means Face Unlock failed to recognize them
*/
@Override
public void reportFailedAttempt() {
if (DEBUG) Log.d(TAG, "FaceLock reportFailedAttempt()");
mUpdateMonitor.reportFailedFaceUnlockAttempt();
if (DEBUG) Log.d(TAG, "reportFailedAttempt()");
mUpdateMonitor.reportFailedBiometricUnlockAttempt();
hide(); // Expose fallback
stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
// Removes the black area that covers the backup unlock method
/**
* Removes the black area that covers the backup unlock method
**/
@Override
public void exposeFallback() {
if (DEBUG) Log.d(TAG, "FaceLock exposeFallback()");
if (DEBUG) Log.d(TAG, "exposeFallback()");
hide(); // Expose fallback
}
// Allows the Face Unlock service to poke the wake lock to keep the lockscreen alive
/**
* Allows the Face Unlock service to poke the wake lock to keep the lockscreen alive
*/
@Override
public void pokeWakelock() {
if (DEBUG) Log.d(TAG, "FaceLock pokeWakelock()");
if (DEBUG) Log.d(TAG, "pokeWakelock()");
mKeyguardScreenCallback.pokeWakelock();
}
};

View File

@@ -303,8 +303,10 @@ class KeyguardStatusViewManager implements OnClickListener {
mUpdateMonitor.registerInfoCallback(mInfoCallback);
mUpdateMonitor.registerSimStateCallback(mSimStateCallback);
resetStatusInfo();
//Issue the faceunlock failure message in a centralized place
if (mUpdateMonitor.getMaxFaceUnlockAttemptsReached()) {
// Issue the biometric unlock failure message in a centralized place
// TODO: we either need to make the Face Unlock multiple failures string a more general
// 'biometric unlock' or have each biometric unlock handle this on their own.
if (mUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) {
setInstructionText(getContext().getString(R.string.faceunlock_multiple_failures));
}
}

View File

@@ -82,8 +82,8 @@ public class KeyguardUpdateMonitor {
private CharSequence mTelephonySpn;
private int mFailedAttempts = 0;
private int mFailedFaceUnlockAttempts = 0;
private static final int FAILED_FACE_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 15;
private int mFailedBiometricUnlockAttempts = 0;
private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 15;
private boolean mClockVisible;
@@ -703,7 +703,7 @@ public class KeyguardUpdateMonitor {
public void clearFailedAttempts() {
mFailedAttempts = 0;
mFailedFaceUnlockAttempts = 0;
mFailedBiometricUnlockAttempts = 0;
}
public void reportFailedAttempt() {
@@ -718,12 +718,12 @@ public class KeyguardUpdateMonitor {
return mPhoneState;
}
public void reportFailedFaceUnlockAttempt() {
mFailedFaceUnlockAttempts++;
public void reportFailedBiometricUnlockAttempt() {
mFailedBiometricUnlockAttempts++;
}
public boolean getMaxFaceUnlockAttemptsReached() {
return mFailedFaceUnlockAttempts >= FAILED_FACE_UNLOCK_ATTEMPTS_BEFORE_BACKUP;
public boolean getMaxBiometricUnlockAttemptsReached() {
return mFailedBiometricUnlockAttempts >= FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP;
}
public boolean isSimLocked() {

View File

@@ -580,7 +580,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase {
restoreWidgetState();
if (mBiometricUnlock != null && startBiometricUnlock) {
mBiometricUnlock.start(mSuppressBiometricUnlock);
maybeStartBiometricUnlock();
}
}
@@ -623,7 +623,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase {
} else {
mHasDialog = false;
if (mBiometricUnlock != null && startBiometricUnlock) {
mBiometricUnlock.start(mSuppressBiometricUnlock);
maybeStartBiometricUnlock();
}
}
}
@@ -855,7 +855,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase {
}
recreateUnlockScreen(unlockMode);
if (mBiometricUnlock != null && restartBiometricUnlock) {
mBiometricUnlock.start(mSuppressBiometricUnlock);
maybeStartBiometricUnlock();
}
}
}
@@ -971,7 +971,10 @@ public class LockPatternKeyguardView extends KeyguardViewBase {
initializeTransportControlView(unlockView);
if (mBiometricUnlock != null) {
mBiometricUnlock.initializeAreaView(unlockView);
// TODO: make faceLockAreaView a more general biometricUnlockView
// We will need to add our Face Unlock specific child views programmatically in
// initializeView rather than having them in the XML files.
mBiometricUnlock.initializeView(unlockView.findViewById(R.id.faceLockAreaView));
}
mUnlockScreenMode = unlockMode;
@@ -1154,4 +1157,24 @@ public class LockPatternKeyguardView extends KeyguardViewBase {
return mBitmap.getHeight();
}
}
/**
* Starts the biometric unlock if it should be started based on a number of factors including
* the mSuppressBiometricUnlock flag. If it should not be started, it hides the biometric
* unlock area.
*/
private void maybeStartBiometricUnlock() {
if (mBiometricUnlock != null) {
final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >=
LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
if (!mSuppressBiometricUnlock
&& mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
&& !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached()
&& !backupIsTimedOut) {
mBiometricUnlock.start();
} else {
mBiometricUnlock.hide();
}
}
}
}