blob: 8d13ac2dbf0507812ec0371ed7c718e13b63781e [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.keyguard;
import com.android.internal.policy.IFaceLockCallback;
import com.android.internal.policy.IFaceLockInterface;
import com.android.internal.widget.LockPatternUtils;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "FULLockscreen";
private static final String FACE_LOCK_PACKAGE = "com.android.facelock";
private final Context mContext;
private final LockPatternUtils mLockPatternUtils;
// 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_SERVICE_CONNECTED = 0;
private final int MSG_SERVICE_DISCONNECTED = 1;
private final int MSG_UNLOCK = 2;
private final int MSG_CANCEL = 3;
private final int MSG_REPORT_FAILED_ATTEMPT = 4;
private final int MSG_POKE_WAKELOCK = 5;
// 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 volatile boolean mIsRunning = false;
KeyguardSecurityCallback 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) {
mContext = context;
mLockPatternUtils = new LockPatternUtils(context);
mHandler = new Handler(this);
}
public void setKeyguardCallback(KeyguardSecurityCallback keyguardScreenCallback) {
mKeyguardScreenCallback = keyguardScreenCallback;
}
/**
* 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) {
Log.d(TAG, "initializeView()");
mFaceUnlockView = biometricUnlockView;
}
/**
* Indicates whether Face Unlock is currently running.
*/
public boolean isRunning() {
return mIsRunning;
}
/**
* Dismisses face unlock and goes to the backup lock
*/
public void stopAndShowBackup() {
if (DEBUG) Log.d(TAG, "stopAndShowBackup()");
mHandler.sendEmptyMessage(MSG_CANCEL);
}
/**
* Binds to the Face Unlock service. Face Unlock will be started when the bind completes. The
* Face Unlock view is displayed to hide the backup lock while the service is starting up.
* Called on the UI thread.
*/
public boolean start() {
if (DEBUG) Log.d(TAG, "start()");
if (mHandler.getLooper() != Looper.myLooper()) {
Log.e(TAG, "start() called off of the UI thread");
}
if (mIsRunning) {
Log.w(TAG, "start() called when already running");
}
if (!mBoundToService) {
Log.d(TAG, "Binding to Face Unlock service for user="
+ mLockPatternUtils.getCurrentUser());
mContext.bindServiceAsUser(
new Intent(IFaceLockInterface.class.getName()).setPackage(FACE_LOCK_PACKAGE),
mConnection,
Context.BIND_AUTO_CREATE,
new UserHandle(mLockPatternUtils.getCurrentUser()));
mBoundToService = true;
} else {
Log.w(TAG, "Attempt to bind to Face Unlock when already bound");
}
mIsRunning = true;
return true;
}
/**
* Stops Face Unlock and unbinds from the service. Called on the UI thread.
*/
public boolean stop() {
if (DEBUG) Log.d(TAG, "stop()");
if (mHandler.getLooper() != Looper.myLooper()) {
Log.e(TAG, "stop() called from non-UI thread");
}
// Clearing any old service connected messages.
mHandler.removeMessages(MSG_SERVICE_CONNECTED);
boolean mWasRunning = mIsRunning;
stopUi();
if (mBoundToService) {
if (mService != null) {
try {
mService.unregisterCallback(mFaceUnlockCallback);
} catch (RemoteException e) {
// Not much we can do
}
}
Log.d(TAG, "Unbinding from Face Unlock service");
mContext.unbindService(mConnection);
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 Face Unlock when not bound");
}
mIsRunning = false;
return mWasRunning;
}
/**
* 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);
} catch (RemoteException e) {
// Not much we can do
}
stopUi();
mService = null;
}
}
/**
* Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK.
*/
public int getQuality() {
return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
}
/**
* 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.
*/
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_SERVICE_CONNECTED:
handleServiceConnected();
break;
case MSG_SERVICE_DISCONNECTED:
handleServiceDisconnected();
break;
case MSG_UNLOCK:
handleUnlock(msg.arg1);
break;
case MSG_CANCEL:
handleCancel();
break;
case MSG_REPORT_FAILED_ATTEMPT:
handleReportFailedAttempt();
break;
case MSG_POKE_WAKELOCK:
handlePokeWakelock(msg.arg1);
break;
default:
Log.e(TAG, "Unhandled message");
return false;
}
return true;
}
/**
* Tells the service to start its UI via an AIDL interface. Called when the
* onServiceConnected() callback is received.
*/
void handleServiceConnected() {
Log.d(TAG, "handleServiceConnected()");
// It is possible that an unbind has occurred in the time between the bind and when this
// function is reached. If an unbind has already occurred, proceeding on to call startUi()
// can result in a fatal error. Note that the onServiceConnected() callback is
// asynchronous, so this possibility would still exist if we executed this directly in
// onServiceConnected() rather than using a handler.
if (!mBoundToService) {
Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound");
return;
}
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) {
// 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.userActivity();
int[] position;
position = new int[2];
mFaceUnlockView.getLocationInWindow(position);
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.
*/
void handleUnlock(int authenticatedUserId) {
if (DEBUG) Log.d(TAG, "handleUnlock()");
stop();
int currentUserId = mLockPatternUtils.getCurrentUser();
if (authenticatedUserId == currentUserId) {
if (DEBUG) Log.d(TAG, "Unlocking for user " + authenticatedUserId);
mKeyguardScreenCallback.reportUnlockAttempt(true);
mKeyguardScreenCallback.dismiss(true);
} else {
Log.d(TAG, "Ignoring unlock for authenticated user (" + authenticatedUserId +
") because the current user is " + currentUserId);
}
}
/**
* Stops the Face Unlock service and goes to the backup lock.
*/
void handleCancel() {
if (DEBUG) Log.d(TAG, "handleCancel()");
// We are going to the backup method, so we don't want to see Face Unlock again until the
// next time the user visits keyguard.
KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(false);
mKeyguardScreenCallback.showBackupSecurity();
stop();
mKeyguardScreenCallback.userActivity();
}
/**
* Increments the number of failed Face Unlock attempts.
*/
void handleReportFailedAttempt() {
if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()");
// We are going to the backup method, so we don't want to see Face Unlock again until the
// next time the user visits keyguard.
KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(false);
mKeyguardScreenCallback.reportUnlockAttempt(false);
}
/**
* If the screen is on, pokes the wakelock to keep the screen alive and active for a specific
* amount of time.
*/
void handlePokeWakelock(int millis) {
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
if (powerManager.isScreenOn()) {
mKeyguardScreenCallback.userActivity();
}
}
/**
* Implements service connection methods.
*/
private ServiceConnection mConnection = new ServiceConnection() {
/**
* Called when the Face Unlock service connects after calling bind().
*/
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);
}
/**
* Called if the Face Unlock service unexpectedly disconnects. This indicates an error.
*/
public void onServiceDisconnected(ComponentName className) {
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 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) {
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 Face Unlock: " + e.toString());
return;
}
mServiceRunning = true;
} else {
Log.w(TAG, "startUi() attempted while running");
}
}
}
/**
* 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) {
Log.d(TAG, "Stopping Face Unlock");
try {
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 AIDL biometric unlock service callback interface.
*/
private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() {
/**
* Called when Face Unlock wants to grant access to the user.
*/
public void unlock() {
if (DEBUG) Log.d(TAG, "unlock()");
Message message = mHandler.obtainMessage(MSG_UNLOCK, UserHandle.getCallingUserId(), -1);
mHandler.sendMessage(message);
}
/**
* Called when Face Unlock wants to go to the backup.
*/
public void cancel() {
if (DEBUG) Log.d(TAG, "cancel()");
mHandler.sendEmptyMessage(MSG_CANCEL);
}
/**
* Called when Face Unlock wants to increment the number of failed attempts.
*/
public void reportFailedAttempt() {
if (DEBUG) Log.d(TAG, "reportFailedAttempt()");
mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT);
}
/**
* Called when Face Unlock wants to keep the screen alive and active for a specific amount
* of time.
*/
public void pokeWakelock(int millis) {
if (DEBUG) Log.d(TAG, "pokeWakelock() for " + millis + "ms");
Message message = mHandler.obtainMessage(MSG_POKE_WAKELOCK, millis, -1);
mHandler.sendMessage(message);
}
};
}