Merge "Fix 6395288: Added lock to avoid unbind race condition" into jb-dev

This commit is contained in:
Brian Colonna
2012-05-01 12:54:02 -07:00
committed by Android (Google) Code Review
2 changed files with 243 additions and 96 deletions

View File

@@ -62,7 +62,7 @@ interface BiometricSensorUnlock {
public boolean start();
/**
* Stops the biometric unlock procedure and unbinds from the service.
* Stops the biometric unlock procedure and unbinds from the service. Called on the UI thread.
* @return whether the biometric unlock was running when called.
*/
public boolean stop();

View File

@@ -45,32 +45,44 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
// TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null?
private boolean mServiceRunning = false;
// TODO: now that the code has been restructure to do almost all operations from a handler, this
// lock may no longer be necessary.
private final Object mServiceRunningLock = new Object();
private IFaceLockInterface mService;
private boolean mBoundToService = false;
private View mFaceUnlockView;
private Handler mHandler;
private final int MSG_SHOW_AREA_VIEW = 0;
private final int MSG_HIDE_AREA_VIEW = 1;
private final int MSG_SHOW_FACE_UNLOCK_VIEW = 0;
private final int MSG_HIDE_FACE_UNLOCK_VIEW = 1;
private final int MSG_SERVICE_CONNECTED = 2;
private final int MSG_SERVICE_DISCONNECTED = 3;
private final int MSG_UNLOCK = 4;
private final int MSG_CANCEL = 5;
private final int MSG_REPORT_FAILED_ATTEMPT = 6;
private final int MSG_EXPOSE_FALLBACK = 7;
private final int MSG_POKE_WAKELOCK = 8;
// 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;
private volatile 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;
private final int SERVICE_STARTUP_VIEW_TIMEOUT = 3000;
// 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;
KeyguardScreenCallback mKeyguardScreenCallback;
/**
* Stores some of the structures that Face Unlock will need to access and creates the handler
* will be used to execute messages on the UI thread.
*/
public FaceUnlock(Context context, KeyguardUpdateMonitor updateMonitor,
LockPatternUtils lockPatternUtils, KeyguardScreenCallback keyguardScreenCallback) {
mContext = context;
@@ -86,6 +98,7 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
* methods, we will have to add our other views (background, cancel button) here.
*/
public void initializeView(View biometricUnlockView) {
Log.d(TAG, "initializeView()");
mFaceUnlockView = biometricUnlockView;
show(0);
}
@@ -102,12 +115,13 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
* timeoutMillis is 0, no hide is performed.
*/
public void show(long timeoutMillis) {
removeAreaDisplayMessages();
if (DEBUG) Log.d(TAG, "show()");
removeDisplayMessages();
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.VISIBLE);
}
if (timeoutMillis > 0) {
mHandler.sendEmptyMessageDelayed(MSG_HIDE_AREA_VIEW, timeoutMillis);
mHandler.sendEmptyMessageDelayed(MSG_HIDE_FACE_UNLOCK_VIEW, timeoutMillis);
}
}
@@ -115,16 +129,18 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
* Hides the Face Unlock view.
*/
public void hide() {
if (DEBUG) Log.d(TAG, "hide()");
// Remove messages to prevent a delayed show message from undo-ing the hide
removeAreaDisplayMessages();
mHandler.sendEmptyMessage(MSG_HIDE_AREA_VIEW);
removeDisplayMessages();
mHandler.sendEmptyMessage(MSG_HIDE_FACE_UNLOCK_VIEW);
}
/**
* 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.
* Face Unlock view is displayed to hide the backup lock while the service is starting up.
*/
public boolean start() {
if (DEBUG) Log.d(TAG, "start()");
if (mIsRunning) {
Log.w(TAG, "start() called when already running");
}
@@ -132,14 +148,13 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
// 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);
show(SERVICE_STARTUP_VIEW_TIMEOUT);
if (!mBoundToService) {
if (DEBUG) Log.d(TAG, "before bind to Face Unlock service");
Log.d(TAG, "Binding 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");
@@ -158,11 +173,11 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
* Stops Face Unlock and unbinds from the service.
*/
public boolean stop() {
if (DEBUG) Log.d(TAG, "stop()");
boolean mWasRunning = mIsRunning;
stopUi();
if (mBoundToService) {
if (DEBUG) Log.d(TAG, "before unbind from Face Unlock service");
if (mService != null) {
try {
mService.unregisterCallback(mFaceUnlockCallback);
@@ -170,8 +185,8 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
// Not much we can do
}
}
Log.d(TAG, "Unbinding from Face Unlock service");
mContext.unbindService(mConnection);
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
@@ -187,6 +202,7 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
* Frees up resources used by Face Unlock and stops it if it is still running.
*/
public void cleanUp() {
if (DEBUG) Log.d(TAG, "cleanUp()");
if (mService != null) {
try {
mService.unregisterCallback(mFaceUnlockCallback);
@@ -206,86 +222,224 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
}
/**
* 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.
* Handles messages such that everything happens on the UI thread in a deterministic order.
* Calls from the Face Unlock service come from binder threads. Calls from lockscreen typically
* come from the UI thread. This makes sure there are no race conditions between those calls.
*/
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_SHOW_AREA_VIEW:
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.VISIBLE);
}
break;
case MSG_HIDE_AREA_VIEW:
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.INVISIBLE);
}
break;
default:
Log.w(TAG, "Unhandled message");
return false;
case MSG_SHOW_FACE_UNLOCK_VIEW:
handleShowFaceUnlockView();
break;
case MSG_HIDE_FACE_UNLOCK_VIEW:
handleHideFaceUnlockView();
break;
case MSG_SERVICE_CONNECTED:
handleServiceConnected();
break;
case MSG_SERVICE_DISCONNECTED:
handleServiceDisconnected();
break;
case MSG_UNLOCK:
handleUnlock();
break;
case MSG_CANCEL:
handleCancel();
break;
case MSG_REPORT_FAILED_ATTEMPT:
handleReportFailedAttempt();
break;
case MSG_EXPOSE_FALLBACK:
handleExposeFallback();
break;
case MSG_POKE_WAKELOCK:
handlePokeWakelock();
break;
default:
Log.e(TAG, "Unhandled message");
return false;
}
return true;
}
/**
* Removes show and hide messages from the message queue
* Sets the Face Unlock view to visible, thus covering the backup lock.
*/
private void removeAreaDisplayMessages() {
mHandler.removeMessages(MSG_SHOW_AREA_VIEW);
mHandler.removeMessages(MSG_HIDE_AREA_VIEW);
void handleShowFaceUnlockView() {
if (DEBUG) Log.d(TAG, "handleShowFaceUnlockView()");
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.VISIBLE);
} else {
Log.e(TAG, "mFaceUnlockView is null in handleShowFaceUnlockView()");
}
}
private ServiceConnection mConnection = new ServiceConnection() {
/**
* 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 Face Unlock service");
try {
mService.registerCallback(mFaceUnlockCallback);
} catch (RemoteException e) {
Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString());
mService = null;
mBoundToService = false;
mIsRunning = false;
return;
}
/**
* Sets the Face Unlock view to invisible, thus exposing the backup lock.
*/
void handleHideFaceUnlockView() {
if (DEBUG) Log.d(TAG, "handleHideFaceUnlockView()");
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.INVISIBLE);
} else {
Log.e(TAG, "mFaceUnlockView is null in handleHideFaceUnlockView()");
}
}
if (mFaceUnlockView != null) {
/**
* Tells the service to start its UI via an AIDL interface. Called when the
* onServiceConnected() callback is received.
*/
void handleServiceConnected() {
if (DEBUG) Log.d(TAG, "handleServiceConnected()");
try {
mService.registerCallback(mFaceUnlockCallback);
} catch (RemoteException e) {
Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString());
mService = null;
mBoundToService = false;
mIsRunning = false;
return;
}
if (mFaceUnlockView != null) {
IBinder windowToken = mFaceUnlockView.getWindowToken();
if (windowToken != null) {
int[] position;
position = new int[2];
mFaceUnlockView.getLocationInWindow(position);
startUi(mFaceUnlockView.getWindowToken(), position[0], position[1],
mFaceUnlockView.getWidth(), mFaceUnlockView.getHeight());
startUi(windowToken, position[0], position[1], mFaceUnlockView.getWidth(),
mFaceUnlockView.getHeight());
} else {
Log.e(TAG, "windowToken is null in handleServiceConnected()");
}
}
}
/**
* Called when the onServiceDisconnected() callback is received. This should not happen during
* normal operation. It indicates an error has occurred.
*/
void handleServiceDisconnected() {
Log.e(TAG, "handleServiceDisconnected()");
// TODO: this lock may no longer be needed now that everything is being called from a
// handler
synchronized (mServiceRunningLock) {
mService = null;
mServiceRunning = false;
}
mBoundToService = false;
mIsRunning = false;
}
/**
* Stops the Face Unlock service and tells the device to grant access to the user. Shows the
* Face Unlock view to keep the backup lock covered while the device unlocks.
*/
void handleUnlock() {
if (DEBUG) Log.d(TAG, "handleUnlock()");
removeDisplayMessages();
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.VISIBLE);
} else {
Log.e(TAG, "mFaceUnlockView is null in handleUnlock()");
}
stop();
mKeyguardScreenCallback.keyguardDone(true);
mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
}
/**
* Stops the Face Unlock service and exposes the backup lock. Called when the user presses the
* cancel button to skip Face Unlock or no face is detected.
*/
void handleCancel() {
if (DEBUG) Log.d(TAG, "handleCancel()");
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.INVISIBLE);
} else {
Log.e(TAG, "mFaceUnlockView is null in handleCancel()");
}
stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
/**
* Stops the Face Unlock service and exposes the backup lock, reporting a failed unlock attempt.
* Called when Face Unlock denies access to the user.
*/
void handleReportFailedAttempt() {
if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()");
mUpdateMonitor.reportFailedBiometricUnlockAttempt();
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.INVISIBLE);
} else {
Log.e(TAG, "mFaceUnlockView is null in handleReportFailedAttempt()");
}
stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
/**
* Hides the Face Unlock view to expose the backup lock. Called when the Face Unlock service UI
* is started, indicating there is no need to continue displaying the underlying view because
* the service UI is now covering the backup lock.
*/
void handleExposeFallback() {
if (DEBUG) Log.d(TAG, "handleExposeFallback()");
if (mFaceUnlockView != null) {
mFaceUnlockView.setVisibility(View.INVISIBLE);
} else {
Log.e(TAG, "mFaceUnlockView is null in handleExposeFallback()");
}
}
/**
* Pokes the wakelock to keep the screen alive and active.
*/
void handlePokeWakelock() {
mKeyguardScreenCallback.pokeWakelock();
}
/**
* Removes show and hide messages from the message queue. Called to prevent delayed show/hide
* messages from undoing a new message.
*/
private void removeDisplayMessages() {
mHandler.removeMessages(MSG_SHOW_FACE_UNLOCK_VIEW);
mHandler.removeMessages(MSG_HIDE_FACE_UNLOCK_VIEW);
}
/**
* Implements service connection methods.
*/
private ServiceConnection mConnection = new ServiceConnection() {
/**
* Called when the Face Unlock service connects after calling bind().
*/
@Override
public void onServiceConnected(ComponentName className, IBinder iservice) {
Log.d(TAG, "Connected to Face Unlock service");
mService = IFaceLockInterface.Stub.asInterface(iservice);
mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED);
}
/**
* Cleans up if Face Unlock service unexpectedly disconnects
* Called if the Face Unlock service unexpectedly disconnects. This indicates an error.
*/
@Override
public void onServiceDisconnected(ComponentName className) {
synchronized(mServiceRunningLock) {
mService = null;
mServiceRunning = false;
}
mBoundToService = false;
mIsRunning = false;
Log.w(TAG, "Unexpected disconnect from Face Unlock service");
Log.e(TAG, "Unexpected disconnect from Face Unlock service");
mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED);
}
};
/**
* Tells the Face Unlock service to start displaying its UI and perform recognition
* Tells the Face Unlock service to start displaying its UI and start processing.
*/
private void startUi(IBinder windowToken, int x, int y, int w, int h) {
if (DEBUG) Log.d(TAG, "startUi()");
synchronized (mServiceRunningLock) {
if (!mServiceRunning) {
if (DEBUG) Log.d(TAG, "Starting Face Unlock");
@@ -298,93 +452,86 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
}
mServiceRunning = true;
} else {
if (DEBUG) Log.w(TAG, "startUi() attempted while running");
Log.w(TAG, "startUi() attempted while running");
}
}
}
/**
* Tells the Face Unlock service to stop displaying its UI and stop recognition
* Tells the Face Unlock service to stop displaying its UI and stop processing.
*/
private void stopUi() {
if (DEBUG) Log.d(TAG, "stopUi()");
// 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) {
if (DEBUG) Log.d(TAG, "Stopping Face Unlock");
try {
if (DEBUG) Log.d(TAG, "Stopping Face Unlock");
mService.stopUi();
} catch (RemoteException e) {
Log.e(TAG, "Caught exception stopping Face Unlock: " + e.toString());
}
mServiceRunning = false;
} else {
// This is usually not an error when this happens. Sometimes we will tell it to
// stop multiple times because it's called from both onWindowFocusChanged and
// onDetachedFromWindow.
if (DEBUG) Log.d(TAG, "stopUi() attempted while not running");
}
}
}
/**
* Implements the biometric unlock service callback interface defined in AIDL
* Implements the AIDL biometric unlock service callback interface.
*/
private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() {
/**
* Stops the Face Unlock UI and indicates that the phone should be unlocked
* Called when Face Unlock wants to grant access to the user.
*/
@Override
public void unlock() {
if (DEBUG) Log.d(TAG, "unlock()");
// Keep fallback covered
removeAreaDisplayMessages();
mHandler.sendEmptyMessage(MSG_SHOW_AREA_VIEW);
stop();
mKeyguardScreenCallback.keyguardDone(true);
mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
mHandler.sendEmptyMessage(MSG_UNLOCK);
}
/**
* Stops the Face Unlock UI and exposes the backup method without unlocking
* This means the user has cancelled out
* Called when the user presses cancel to skip Face Unlock or a face cannot be found.
*/
@Override
public void cancel() {
if (DEBUG) Log.d(TAG, "cancel()");
hide(); // Expose fallback
stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
mHandler.sendEmptyMessage(MSG_CANCEL);
}
/**
* Stops the Face Unlock UI and exposes the backup method without unlocking
* This means Face Unlock failed to recognize them
* Called when Face Unlock denies access to the user.
*/
@Override
public void reportFailedAttempt() {
if (DEBUG) Log.d(TAG, "reportFailedAttempt()");
mUpdateMonitor.reportFailedBiometricUnlockAttempt();
hide(); // Expose fallback
stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT);
}
/**
* Removes the black area that covers the backup unlock method
* Called when the Face Unlock service starts displaying the UI, indicating that the backup
* unlock can be exposed because the Face Unlock service is now covering the backup with its
* UI.
**/
@Override
public void exposeFallback() {
if (DEBUG) Log.d(TAG, "exposeFallback()");
hide(); // Expose fallback
mHandler.sendEmptyMessage(MSG_EXPOSE_FALLBACK);
}
/**
* Allows the Face Unlock service to poke the wake lock to keep the lockscreen alive
* Called when Face Unlock wants to keep the screen alive and active.
*/
@Override
public void pokeWakelock() {
if (DEBUG) Log.d(TAG, "pokeWakelock()");
mKeyguardScreenCallback.pokeWakelock();
mHandler.sendEmptyMessage(MSG_POKE_WAKELOCK);
}
};
}