在Android P版本中加入Face Unlock相關功能的講解
隨着時代的發展,指紋已經不能滿足當前人們的需要,特別是在iphone X推出以後,各大手機廠商爭相模仿,都會在手機中加入人臉解鎖功能,我們今天就拿出一例講解一下,如何在Android手機中加入face Unlock功能
一 . Face Unlock主要流程概括
首先,按照用戶的使用角度,先進入Settings中錄入人臉,此時Settings會優先啓動FaceUnlockService將Camera獲取到的人臉數據傳入FaceUnlockService再由FaceUnlockService操縱Faceunlock引擎將Camera數據轉換爲SDK可以識別的數據傳入SDK識別,當SDK返回數據驗證OK的result後,我們將該數據存入本地,並將初始值設定爲已錄入狀態,這樣FaceUnlockService就會在每次滅屏和亮屏的時候去監聽這些值的設定狀態從而決定是否去啓動人臉解鎖功能,說到這裏就不得不說一下FaceUnlockService的啓動流程,因爲FaceUnlockService需要啓動的更早並且需要在任何合適的時機去刷新錄入狀態的設值。
二 . FaceUnlockService啓動流程
1.我們的FaceUnlockService與FUS引擎被集成在SystemUI中,用戶在開機後會調用KeyguardViewMediator方法,我們的FaceUnlockService也就是在setupLocked()綁定開始啓動的,我們在這裏使用bindService方法啓動
private void setupLocked() {
mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
......
bindFaceUnlockService();//啓動FaceUnlockService
......
}
/**
*FaceUnlockService啓動方法
*/
private void bindFaceUnlockService() {
Intent intent = new Intent(mContext, FaceUnlockService.class);
mContext.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected, call stack: "+android.os.Debug.getCallers(10));
mUpdateMonitor.createFaceUnlockManager(IFaceUnlockEngineManager.Stub.asInterface(service));
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected, ComponentName="+name);
}
}, Context.BIND_AUTO_CREATE);
}
2.在FaceUnlockService我們主要做一件事,就是啓動FUS引擎
public class FaceUnlockService extends Service {
public static final String TAG = "FaceUnlockService";
private static final String MEGVII_SOURCE_DIR = "/storage/emulated/0/Android/data/com.android.settings/files/megvii";
private static final int PANORAMA_MGB_RES_ID = R.raw.panorama_mgb;
private static final int MEGVIIFACEPP_MODEL_RES_ID = R.raw.megviifacepp_model;
private static FaceUnlockEngineManagerService sEngineService;
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
// create engine is necessary
getEngineInstance(this);
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return getEngineInstance(this);
}
public static String getMegviiSourceDir() {
int userId = ActivityManager.getCurrentUser();
String megviiSourceDir = MEGVII_SOURCE_DIR;
if (userId != UserHandle.USER_SYSTEM) {
megviiSourceDir = "/storage/emulated/" + userId + "/Android/data/face_data/files/megvii";
}
return megviiSourceDir;
}
private static synchronized FaceUnlockEngineManagerService getEngineInstance(Service service) {
if (sEngineService == null) {
Context appCtx = service.getApplication();
sEngineService = new FaceUnlockEngineManagerService(appCtx,
getMegviiSourceDir(), PANORAMA_MGB_RES_ID, MEGVIIFACEPP_MODEL_RES_ID);
}
return sEngineService;
}
}
3.FUS引擎作爲FACE++ SDK的管理類被啓動後,開始處理各個接口傳遞過來的數據並返回相應的結果, FUS引擎主要實現了IFaceUnlockEngineManager中的接口,所以FUS引擎功能分爲init(),saveFeature,compare, prepare,reset(),release(),deleteFeature等以下幾種,其接口定義主要是根據SDK的使用場景來定義的,因爲SDK在使用前需要進行init初始化,prepare預熱,而後才能進行saveFeature錄入人臉或是compare比對人臉的一些操作,而在錄入和比對或是deleteFeature刪除人臉後需要進行重置SDK reset()的操作,而FUS引擎作爲FACEUNlock的心臟部分,處理着所有的faceunlock數據
interface IFaceUnlockEngineManager {
void init();
void saveFeature(in ParcelFileDescriptor pfd, int dataLength, int width, int height, int angle, boolean useLive, in IFaceUnlockEngineSaveFeatureResult result);
void compare(in ParcelFileDescriptor pfd, int dataLength, int width, int height, int angle, boolean useLive, boolean strictMode, in IFaceUnlockEngineCompareResult result);
void prepare();
void reset();
void release();
/**
* delete a feature
* @param id the id of feature that we want to delete
*/
void deleteFeature(int id);
void setDetectArea(int left, int top, int right, int bottom);
/**
* this method should only be called after Engine state changes to STATE_INITIALIZED
*/
int getFeatureCount();
/**
* get Engine state
* The return state can be one of:
* 0 - STATE_UNINITIALIZED
* 1 - STATE_INITIALIZING
* 2 - STATE_INITIALIZED
* 3 - STATE_INITIALIZATION_FAILED
*/
int getState();
void registerStateListener(in IFaceUnlockEngineStateListener listener);
void unregisterStateListener(in IFaceUnlockEngineStateListener listener);
/**
* get the maximum size of data that can be transported by saveFeature()
*/
//int getEnrollSharedMemorySize();
/**
* get the maximum size of data that can be transported by compare()
*/
//int getAuthenticationSharedMemorySize();
//ParcelFileDescriptor getFileDescriptorForEnroll();
//ParcelFileDescriptor getFileDescriptorForAuthentication();
}
這個就是FaceUnlockService啓動流程以及基本的工作原理,而FUS引擎作爲主幹部分功能承擔着處理錄入比對刪除的三大重要工作,接下來爲了方便理解,按照錄入和比對兩大功能模塊分開講解。
三 . FaceUnlock錄入流程講解
1.人臉錄入入口被集成在Settings中,但是整個錄入過程並不全都在Settings中,正如前面所說,整個FaceUnlockService和FUS引擎都被集成在SystemUI中,而我們的FaceUnlock在Settings的SecuritySettings中。
首先,我們需要找到SecuritySettings對應的xml加入我們需要的Preference
Settings / res/xml/security_dashboard_settings.xml
<Preference
android:key="facelock_settings"
android:title="@string/security_settings_faceid_preference_title"
android:summary="@string/face_not_set_summary"/>
再在SecuritySettings.java中加載出來
Settings / src/com/android/settings/security/SecuritySettings.java
import com.android.settings.faceid.FaceLockPreferenceController;
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle, SecuritySettings host) {
......
securityPreferenceControllers.add(new FaceLockPreferenceController(context));
......
}
2.人臉錄入處理類在FaceIdEnrollPreview.java中
public class FaceIdEnrollPreview extends Activity implements Camera.PreviewCallback
, FaceCircleProgressbar.FaceSuccessListener, FaceCircleProgressbar.ProgressListener
, View.OnClickListener {
......
@Override
public void onPreviewFrame
(final byte[] bytes, final Camera camera) {
Log.d(TAG, "onPreviewFrame");
if (!isDetect) {
isDetect = true;
handler.post(new Runnable() {
@Override
public void run() {
//byte[] data = PdUtils.getPdData(camera);
//if (data == null) {
Log.d(TAG, "handleData1");
handleData(bytes, width, height);
//}
}
});
}
}
private void handleData(byte[] pdData, int width, int height) {
//這裏就運用了aidl原理將視頻流獲取到的數據傳給FUS引擎
mFaceUnlockAdapter.saveFeature(pdData, width, height, angle, true,
new IFaceUnlockEngineSaveFeatureResult.Stub() {
@Override
public void onSaveFeatureCompleted(boolean succeeded, int result, int id) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (succeeded) {
Log.d(TAG, "saveFeature success! return feature id = " + id);
handleRegisterSuccess();
insertFaceName(id);
mFaceUnlockAdapter.backupFaceInfo();
if (lastResult == result) {
successTimes++;
} else {
lastResult = result;
successTimes = 0;
}
if (successTimes >= MAX_SUCCESS_TIME) {
Log.d(TAG, "face register success!");
} else {
isDetect = false;
//faildMsg.setText(R.string.unlock_camera_steady);
}
} else {
successTimes = 0;
Log.d(TAG, "result ======" + result);
isDetect = false;
}
}
});
}
});
}
......
}
FaceUnlockAdapter.java
public class FaceUnlockAdapter {
......
public void saveFeature(byte[] imageData, int width, int height, int angle, boolean useLive, IFaceUnlockEngineSaveFeatureResult result) {
if (!mServiceBound) {
return;
}
int dataLength = imageData.length;
ParcelFileDescriptor pfd = null;
try {
pfd = ParcelFileDescriptor.fromData(imageData, "face_unlock_enroll");
} catch (IOException ex) {
Log.d(TAG, "ParcelFileDescriptor.fromData(), ex: "+ex);
}
try {
mFaceEngine.saveFeature(pfd, dataLength, width, height, angle, useLive, result);
//錄入結果通過參數result的變化而得知
} catch (RemoteException ex) {
Log.d(TAG, "saveFeature: "+ex);
}
}
......
}
四 . FaceUnlock比對流程講解
1.人臉比對入口被集成在SystemUI中,底層算法使用Face++的SDK,首先要將SDK的so庫集成進SystemUI
Android.mk
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := faceunlock:libs/UnlockSdk.aar
include $(BUILD_MULTI_PREBUILT)
include $(CLEAR_VARS)
LOCAL_STATIC_JAVA_LIBRARIES += faceunlock
LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages com.megvii.facepp.sdk
LOCAL_PREBUILT_JNI_LIBS := jni/armeabi-v7a/libMegviiUnlock-jni-1.2.so \
jni/armeabi-v7a/libMegviiUnlock.so \
jni/armeabi-v7a/libFaceDetectCA.so \
jni/armeabi-v7a/libmegface_meglive.so \
jni/armeabi-v7a/libgnustl_shared.so \
jni/armeabi-v7a/libqspower-1.0.0.so \
jni/armeabi-v7a/libCallaSC.so
2.用戶在亮屏後會調用KeyguardUpdateMonitor.java中的handleStartedWakingUp()方法,我們在此方法中去刷新監聽faceunlock的狀態
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
protected void handleStartedWakingUp() {
Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
resetFaceUnlockUpdatedForbidden();
updateFingerprintListeningState();
//我們仿照指紋解鎖監聽同樣去刷新一下人臉解鎖狀態
updateFaceUnlockListeningState();
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onStartedWakingUp();
}
}
Trace.endSection();
}
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
public void updateFaceUnlockListeningState() {
if (mFaceUnlockUpdateForbidden) {
return;
}
boolean shouldListen = shouldListenForFaceUnlock();
Log.d(TAG, "updateFaceUnlockListeningState;shouldListen : "+shouldListen+ " mFaceUnlockRunningState = "+mFaceUnlockRunningState);
if ((mFaceUnlockRunningState == FaceUnlockManager.FACEUNLOCK_STATE_RUNNING ||
mFaceUnlockRunningState == FaceUnlockManager.FACEUNLOCK_STATE_PAUSED ||
mFaceUnlockRunningState == FaceUnlockManager.FACEUNLOCK_STATE_STOPPED_AND_WAIT_FOR_USER_RESTART)
&& !shouldListen) {
stopListeningForFaceUnlock();
} else if (mFaceUnlockRunningState == FaceUnlockManager.FACEUNLOCK_STATE_STOPPED && shouldListen) {
//如果條件合適啓動人臉解鎖
startListeningForFaceUnlock();
}
}
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
private void startListeningForFaceUnlock() {
Log.d(TAG, "----------- startListeningForFaceUnlock");
if (mFaceUnlockManager != null) {
resetFaceUnlockRetryCount();
mFaceUnlockManager.start();
}
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
public void start() {
handler.sendEmptyMessage(FACE_UNLOCK_MSG_HANDLE_START);
}
private void handleStart() {
boolean shouldListenForFaceUnlock = mkeyguardUpdateMonitor.shouldListenForFaceUnlock();
if (isCameraNeedToStop || !shouldListenForFaceUnlock) {
Log.d(TAG, "faceunlock start is not allow, isCameraNeedToStop = " + isCameraNeedToStop
+ " shouldListenForFaceUnlock = " + shouldListenForFaceUnlock);
return;
}
if (!isFaceEngineReady()) {
Log.d(TAG, "start() failed, Face engine is not ready.");
sendPendingStartMessage();
return;
}
if (mState == FACEUNLOCK_STATE_RUNNING) {
return;
}
Log.d(TAG, "start");
if (!getIsDisableFaceUnlock()){
notifyUIThreadCompareMessage(mContext.getResources().
getString(R.string.keyguard_face_unlock_failed_too_many_times_text,
mMillisUntilFinished));
return;
}
if (mTryTimes >= 5) {
mTryTimes = 0;
}
// Camera 0 is not released timely when Screen on quickly
// try to start after 300ms
boolean isCameraInUse = isCameraInUse();
if (mTryTimes < 5 && isCameraInUse) {
Log.d(TAG, "camera is in used");
startDelay(300);
return;
} else if (isCameraInUse) {
Log.d(TAG, "try times: " + mTryTimes + ", camera is in used");
return;
}
if (!startInternal()) {//在這裏進行比對初始化工作
sendPendingStartMessage();
return;
}
notifyUIThreadCompareMessage(mContext.getResources().getString(R.string.keyguard_face_unlock_discerning));
updateState(FACEUNLOCK_STATE_RUNNING);
return;
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
protected boolean startInternal() {
if (isCameraNeedToStop) {
return false;
}
if (!super.startInternal()) {
Log.d(TAG, "super.startInternal()=false, camera=" + camera);
return false;
}
refresh();
// TODO: test, currently we presume that surface is already created.
mSurfaceViewCreated = true;
if (!mSurfaceViewCreated) {
mPendingDectect = true;
} else {
//倘若初始化工作完成就開始比對
startDetect();
try {
mFaceEngine.prepare();
} catch (RemoteException ex) {
Log.d(TAG, "mFaceEngine.prepare(), " + ex);
stop();
}
}
return true;
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
private void startDetect() {
Log.i(TAG, "start detect");
mDetectStartTime = 0;
if (!isTrigger) {
isTrigger = true;
isSuccess = false;
isDetecting = false;
//removeTimeoutRunnable();
startCamera();
long startPreviewBeginTime = System.currentTimeMillis();
startCameraPreview();
long startPreviewEndTime = System.currentTimeMillis();
//updateState(FACEUNLOCK_STATE_RUNNING);
if (DEBUG_FACEUNLOCK) {
if (sharedUtil.getBooleanValueByKey("preview_enable")) {
surface.setAlpha(1.0f);
}
clearProfile();
}
}
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
@Override
public void onPreviewFrame(final byte[] bytes, final Camera camera) {
Log.d(TAG, "onPreviewFrame, isSuccess=" + isSuccess
+ ", isDetecting=" + isDetecting + ", isTrigger=" + isTrigger);
if (isSuccess || isDetecting || !isTrigger || mPaused) return;
isDetecting = true;
if (mDetectStartTime == 0) {
mDetectStartTime = System.currentTimeMillis();
}
//notifyUIThreadCompareMessage(R.string.keyguard_face_unlock_discerning);
handler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Runnable for face sdk compare width = " + width + " height = " + height);
long start = System.currentTimeMillis();
Log.i(TAG, "onPreview ------------ 1, bytes.length=" + bytes.length);
try {
ParcelFileDescriptor pfd = null;
int dataLength = bytes.length;
try {
pfd = ParcelFileDescriptor.fromData(bytes, "face_unlock_auth");
} catch (IOException ex) {
Log.d(TAG, "ParcelFileDescriptor.fromData(), ex:" + ex);
return;
}
Log.i(TAG, "onPreview ------------ 2");
mFaceEngine.compare(pfd, dataLength, width, height, angle, true, strictMode,
new IFaceUnlockEngineCompareResult.Stub() {
@Override
public void onCompareCompleted(boolean succeeded, int result) {
onCompareResultCallback(succeeded, result);
}
});
} catch (RemoteException ex) {
Log.d(TAG, "lite.compare, " + ex);
}
if (DEBUG_FACEUNLOCK) {
String fileName = simpleDateFormat.format(new Date()) + "." + (System
.currentTimeMillis() % 1000);
int pScore = report[3] % 100;
int mScore = report[3] / 100;
Log.d(TAG, "run: pScore" + pScore + "|mScore:" + mScore);
}
}
});
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
private void onCompareResultCallback(boolean succeeded, int result) {
Log.d(TAG, "onCompareResultCallback, succeeded=" + succeeded + ", result=" + result);
if (succeeded) {
notifyWorkerThreadCompareSucceeded();
} else {
int stringResId = 0;
switch (result) {
case Lite.MG_UNLOCK_LIVENESS_WARNING:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_OFFSET_BOTTOM:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_OFFSET_LEFT:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_OFFSET_RIGHT:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_OFFSET_TOP:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_QUALITY:
stringResId = R.string.setting_face_unlock_prompt_entry_error_eight;
break;
case Lite.MG_UNLOCK_FACE_SCALE_TOO_LARGE:
stringResId = R.string.setting_face_unlock_prompt_entry_error_four;
break;
case Lite.MG_UNLOCK_FACE_SCALE_TOO_SMALL:
stringResId = R.string.setting_face_unlock_prompt_entry_error_three;
break;
case Lite.MG_UNLOCK_FAILED:
break;
case Lite.MG_UNLOCK_FACE_NOT_FOUND:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_KEEP:
stringResId = R.string.setting_face_unlock_prompt_entry_error_nine;
break;
case Lite.MG_ATTR_BLUR:
break;
case Lite.MG_ATTR_EYE_OCCLUSION:
break;
case Lite.MG_ATTR_EYE_CLOSE:
stringResId = R.string.setting_face_unlock_prompt_entry_error_five;
break;
case Lite.MG_ATTR_MOUTH_OCCLUSION:
break;
case Lite.MG_UNLOCK_FEATURE_MISS:
break;
case Lite.MG_UNLOCK_FEATURE_VERSION_ERROR:
break;
default:
}
isDetecting = false;
notifyUIThreadCompareMessage(mContext.getResources().getString(stringResId));
}
}
SystemUI / src/com/lenovo/faceunlock/FaceUnlockManager.java
private void notifyWorkerThreadCompareSucceeded() {
handler.sendEmptyMessage(FACE_UNLOCK_MSG_COMPARE_SUCCEEDED);
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
private void onCompareSucceeded() {
Log.d(TAG, "onCompareSucceeded");
removeCountdown();
mTryTimes = 0;
isSuccess = true;
handler.removeCallbacksAndMessages(null);
stopDetect();
//updateState(FACEUNLOCK_STATE_STOPPED);
// Note: Lite.reset() is not thread safe. Currently, we call this api in both onCompareSucceeded()
// and onCompareFailed(), so we only can these two methods in the same thread (UI thread)
try {
mFaceEngine.reset();
} catch (RemoteException ex) {
Log.d(TAG, "mFaceEngine.reset failed, " + ex);
}
mUIThreadHandler.post(new Runnable() {
@Override
public void run() {
//updateState(FACEUNLOCK_STATE_STOPPED);
if (Settings.System.getInt(mContext.getContentResolver(), FACE_UNLOCK_WHEN_SCREEN_ON, 0) == 1) {
notifyUIThreadCompareMessage(mContext.getResources().getString(R.string.keyguard_face_unlock_success_swipe_text));
}
//這裏調用成功的回調接口
mCallback.onAuthenticationSucceeded();
updateState(FACEUNLOCK_STATE_STOPPED);
}
});
if (DEBUG_FACEUNLOCK) {
SharedUtil share = new SharedUtil(mContext);
int count = share.getIntValueByKey(UNLOCK_COUNT_KEY) + 1;
share.saveIntValue(UNLOCK_COUNT_KEY, count);
}
}
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
@Override
public void onAuthenticationSucceeded() {
handleFaceAuthenticated();
}
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
private void onFaceAuthenticated(int userId) {
mUserFaceAuthenticated.put(userId, true);
mFaceAlreadyAuthenticated = true;
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onFaceAuthenticated(userId);
}
}
}
SystemUI / src/com/android/systemui/statusbar/KeyguardIndicationController.java
@Override
public void onFaceAuthenticated(int userId) {
boolean faceUnlockEnabled = Settings.System.getInt(mContext.getContentResolver(), FACE_UNLOCK_SCREEN, 0) == 1;
boolean faceUnlockWhenScreenOn = Settings.System.getInt(mContext.getContentResolver(), FACE_UNLOCK_WHEN_SCREEN_ON, 0) == 1;
if (faceUnlockEnabled) {
mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated(false);
}
if (faceUnlockWhenScreenOn) {
//這裏是主管解鎖的方法
mStatusBarKeyguardViewManager.hideBouncerAndKeyguard();
SystemUI /src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
public void hideBouncerAndKeyguard(){
showBouncerOrKeyguard(true);
}
SystemUI /src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
if (DEBUG) Log.d(TAG, "showBouncerOrKeyguard() is called.");
if (mBouncer.needsFullscreenBouncer() && !mDozing) {
if (DEBUG) {
Log.d(TAG, "needsFullscreenBouncer() is true, show \"Bouncer\" view directly.");
}
// The keyguard might be showing (already). So we need to hide it.
mStatusBar.hideKeyguard();
mBouncer.show(true /* resetSecuritySelection */);
} else {
if (DEBUG) {
Log.d(TAG, "needsFullscreenBouncer() is false,"
+ "show \"Notification Keyguard\" view.");
}
mStatusBar.showKeyguard();
if (hideBouncerWhenShowing) {
hideBouncer(shouldDestroyViewOnReset() /* destroyView */);
mBouncer.prepare();
}
}
updateStates();
}
3.至此FaceUnlock大體功能已經完善,當然了,還需要加入比對錯誤次數等等一系列判斷還有倒計時等等功能,值得一提的事,在與多用戶的交互過程中,我們切換其他用戶會出現打不開Camera的狀況,在我之前的blog中有講到,大家可以返回去看看,解決此問題後還會有SystemUI主用戶一直持有FaceUnlockService的問題,這是因爲比對的service開機在KeyguardViewMediator只啓動了一回,我們需要在切換用戶時解綁,切換用戶後重新綁定該服務就可以解決此問題。
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
private void handleUserSwitching(int userId, IRemoteCallback reply) {
.......
mUserSwitching = true;
unbindFaceUnlockService();
......
}
private void handleUserSwitchComplete(int userId) {
.......
bindFaceUnlockService();
mUserSwitching = false;
......
}