- Only call userActivity in down touch event - Use normal timeout when QS is open - Clean up old code regarding user activity in Keyguard Change-Id: I968eeed33fef44def37b68eb1ddc63f1a531ab35
461 lines
17 KiB
Java
461 lines
17 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
|
|
};
|
|
}
|