Android11.0 增加人脸解锁功能-编程之家

一、Settings 模块修改

从 Q 版本开始 aosp 代码就已经默认支持 Biometric 生物识别相关功能,Settings 中不显示入口菜单是因为判断了

硬件是否支持。可以先看下安全页面中显示的菜单如下。

Android11.0 增加人脸解锁功能-编程之家

我的设备默认是支持指纹模块的,所以在安全菜单中显示了这个入口。

这块的逻辑如下,当没有设置图案/pin码/密码中任意一种锁屏方式时,进入指纹菜单中,会显示如图所示必须先设置一种

备用解锁方式才可继续设置指纹操作。之前如果已经设置过三者之一的解锁方式,则进入指纹菜单中则跳过刚刚的页面,

获取当前是否已经录入过指纹,若已录入则显示管理页面,若未录入则显示引导录入界面。

人脸解锁显示逻辑和指纹是一样的,所以我们直接采用系统默认的流程,将跳转页面修改为我们编码的 FaceUnlock

Settings 修改很简单,在原有的锁屏选项中增加或修改人脸解锁项。

FaceStatusPreferenceController 中将判断条件修改为查询 prop 属性 ro.faceunlock.support 对应值,

编译时通过宏定义控制写入该值就行。修改完这步,在安全界面中指纹同级菜单下就会显示人脸入口。

alps\vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\biometrics\face\FaceStatusPreferenceController.java

@Overrideprotected boolean isDeviceSupported() {//add FACE_UNLOCK_SUPPORT String support = android.os.SystemProperties.get("ro.faceunlock.support", "0");return "1".equals(support);//end//return mFaceManager != null && mFaceManager.isHardwareDetected();}@Overrideprotected boolean hasEnrolledBiometrics() {return false;//return mFaceManager.hasEnrolledTemplates(getUserId());}

Android11.0 增加人脸解锁功能-编程之家

BiometricEnrollIntroduction 中增加跳转 FaceUnlock 逻辑,

persist.facelock.has_face 为 FaceUnlock 约定字段,当成功录入人脸数据后置为1。

当未录入人脸数据时,说明未启用人脸解锁功能,进入 FaceUnlock 中使用流程介绍界面(com.face.unlock.FaceRegIntroActivity)

存在人脸数据时,则进入 FaceUnlock 管理主界面(com.face.unlock.FaceSetActivity)

alps\vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\biometrics\BiometricEnrollIntroduction.java

 @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) {if (resultCode == RESULT_FINISHED || resultCode == RESULT_SKIP|| resultCode == RESULT_TIMEOUT) {setResult(resultCode, data);finish();return;}} else if (requestCode == CHOOSE_LOCK_GENERIC_REQUEST) {if (resultCode == RESULT_FINISHED) {updatePasswordQuality();mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);mConfirmingCredentials = false;return;} else {setResult(resultCode, data);finish();}} else if (requestCode == CONFIRM_REQUEST) {mConfirmingCredentials = false;if (resultCode == RESULT_OK && data != null) {mToken = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);//for (byte t : mToken) {//android.util.Log.e("lock", "byte="+t);//}//add FACE_UNLOCK_SUPPORT startif (mToken.length > 2) {if (mToken[0] == 0x00 && mToken[1] == 0x00) {int faceNum = android.provider.Settings.System.getInt(getContentResolver(),"persist.facelock.has_face",0);Intent faceIntent = new Intent().setComponent(new android.content.ComponentName("com.face.unlock",(faceNum == 1) ? "com.face.unlock.FaceSetActivity" : "com.face.unlock.FaceRegIntroActivity")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(faceIntent);finish();}}//end} else {setResult(resultCode, data);finish();}} else if (requestCode == LEARN_MORE_REQUEST) {overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out);}super.onActivityResult(requestCode, resultCode, data);}

com.face.unlock.FaceRegIntroActivity 使用流程介绍界面

Android11.0 增加人脸解锁功能-编程之家

com.face.unlock.FaceSetActivity 管理主界面

Android11.0 增加人脸解锁功能-编程之家

ChooseLockGeneric 中当验证其它解锁方式成功后,查询是否录入人脸数据。

不存在人脸数据,则进入 FaceUnlock 中使用流程介绍界面(com.face.unlock.FaceRegIntroActivity)

存在人脸数据时,则进入 FaceUnlock 管理主界面(com.face.unlock.FaceSetActivity)

alps\vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\password\ChooseLockGeneric.java

@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);mWaitingForConfirmation = false;if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) {Log.d("lock", "CONFIRM_EXISTING_REQUEST");mPasswordConfirmed = true;mUserPassword = data != null? data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD): null;updatePreferencesOrFinish(false /* isRecreatingActivity */);if (mForChangeCredRequiredForBoot) {if (mUserPassword != null && !mUserPassword.isNone()) {maybeEnableEncryption(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId), false);} else {finish();}}} else if (requestCode == CHOOSE_LOCK_REQUEST|| requestCode == ENABLE_ENCRYPTION_REQUEST) {Log.d("lock", "CHOOSE_LOCK_REQUEST  mForFace="+mForFace +" mForFingerprint="+mForFingerprint);if (resultCode != RESULT_CANCELED || mForChangeCredRequiredForBoot) {//FACE_UNLOCK_SUPPORT startif (mForFace) {android.provider.Settings.System.putInt(getContentResolver(), "persist.facelock.enable", 1);Intent faceIntent = new Intent().setComponent(new android.content.ComponentName("com.face.unlock","com.face.unlock.FaceRegIntroActivity")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(faceIntent);}else{//endgetActivity().setResult(resultCode, data);}finish();Log.d("lock", "step 1");} else {// If PASSWORD_TYPE_KEY is set, this activity is used as a trampoline to start// the actual password enrollment. If the result is canceled, which means the// user pressed back, finish the activity with result canceled.int quality = getIntent().getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1);Log.d("lock", "step 2 quality=="+quality);if (quality != -1) {getActivity().setResult(RESULT_CANCELED, data);finish();Log.d("lock", "step 3");}}} else if (requestCode == CHOOSE_LOCK_BEFORE_BIOMETRIC_REQUEST&& resultCode == BiometricEnrollBase.RESULT_FINISHED) {Intent intent = getBiometricEnrollIntent(getActivity());if (data != null) {intent.putExtras(data.getExtras());}// Forward the target user id to fingerprint setup page.intent.putExtra(Intent.EXTRA_USER_ID, mUserId);startActivity(intent);finish();} else if (requestCode == SKIP_FINGERPRINT_REQUEST) {if (resultCode != RESULT_CANCELED) {getActivity().setResult(resultCode == RESULT_FINISHED ? RESULT_OK : resultCode, data);finish();}} else if (requestCode == SearchFeatureProvider.REQUEST_CODE) {return;} else {getActivity().setResult(Activity.RESULT_CANCELED);finish();}if (requestCode == Activity.RESULT_CANCELED && mForChangeCredRequiredForBoot) {finish();}}

二、SystemUI 模块修改

解锁相关接口都在 SystemUI 中实现,为了简单快速实现功能,这里采用广播的方式来调用接口,当然正统方法是通过 aidl 等方式来通信。

icon_face shape 资源文件,用于标识当前已经启用人脸解锁功能,且当识别失败时可以做个属性动画简单晃动一下交互。

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\res\drawable\icon_face.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="32dp"android:height="32dp"android:viewportWidth="1024"android:viewportHeight="1024"><pathandroid:pathData="M903.6,317.7c-21.9,-51.8 -53.3,-98.3 -93.2,-138.2 -39.9,-40 -86.4,-71.3 -138.2,-93.2C618.6,63.6 561.7,52.1 503,52.1S387.3,63.6 333.8,86.2c-51.8,21.9 -98.3,53.3 -138.2,93.2 -40,39.9 -71.3,86.4 -93.2,138.2 -22.7,53.6 -34.2,110.6 -34.2,169.2S79.7,602.4 102.4,656c21.9,51.8 53.3,98.3 93.2,138.2 39.9,40 86.4,71.3 138.2,93.2 53.6,22.7 110.5,34.2 169.2,34.2 58.6,0 115.6,-11.5 169.2,-34.1 51.8,-21.9 98.2,-53.3 138.2,-93.2s71.3,-86.4 93.2,-138.2c22.7,-53.6 34.2,-110.6 34.2,-169.2 0,-58.6 -11.5,-115.6 -34.2,-169.2zM863.1,639c-19.7,46.5 -47.9,88.3 -83.8,124.2 -35.9,35.9 -77.7,64.1 -124.2,83.8 -48.2,20.4 -99.3,30.7 -152.1,30.7S399.1,867.4 350.9,847c-46.5,-19.7 -88.3,-47.9 -124.2,-83.8s-64.1,-77.7 -83.8,-124.2c-20.4,-48.2 -30.7,-99.3 -30.7,-152.1s10.3,-103.9 30.7,-152.1c19.7,-46.5 47.9,-88.3 83.8,-124.2 35.9,-35.9 77.7,-64.1 124.2,-83.8 48.2,-20.4 99.3,-30.7 152.1,-30.7 52.7,0 103.9,10.3 152.1,30.7 46.5,19.7 88.3,47.9 124.2,83.8 35.9,35.9 64.1,77.7 83.8,124.2 20.4,48.2 30.7,99.3 30.7,152.1S883.5,590.8 863.1,639z"android:fillColor="#ffffff"/><pathandroid:pathData="M345.7,388.6m-39.3,0a39.3,39.3 0,1 0,78.6 0,39.3 39.3,0 1,0 -78.6,0Z"android:fillColor="#ffffff"/><pathandroid:pathData="M660.2,388.6m-39.3,0a39.3,39.3 0,1 0,78.6 0,39.3 39.3,0 1,0 -78.6,0Z"android:fillColor="#ffffff"/><pathandroid:pathData="M705.4,595.4c-9.1,-8.1 -23,-7.3 -31.1,1.7 -21.4,23.9 -47,42.8 -76,56.1 -30,13.8 -62.1,20.8 -95.4,20.8s-65.4,-7 -95.4,-20.8c-28.9,-13.3 -54.5,-32.2 -76,-56.1 -8.1,-9.1 -22.1,-9.8 -31.1,-1.7 -9.1,8.1 -9.8,22.1 -1.7,31.1 25.6,28.4 56,50.9 90.5,66.7C425.1,709.7 463.3,718 503,718c39.6,0 77.9,-8.3 113.6,-24.8 34.5,-15.8 65,-38.2 90.5,-66.7 8.1,-9.1 7.3,-23 -1.7,-31.1z"android:fillColor="#ffffff"/>
</vector>

Android11.0 增加人脸解锁功能-编程之家

连续多次人脸解锁识别失败时,提示语提醒使用其它方式进行解锁。

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\res\values\strings.xml

<string name="kg_wrong_face">Face unlock failed, please use pattern or password to unlock</string>

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\res\values-zh-rCN\strings.xml

<string name="kg_wrong_face" msgid="2873443755036646812">"人脸解锁失败,请用图案或密码解锁"</string>

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\res-keyguard\values-zh-rCN\strings.xml

<string name="kg_wrong_face" msgid="2873443755036646812">"人脸解锁失败,请用图案或密码解锁"</string>

通过 SystemUIApplication 给 FaceUnlockUtil context 赋值

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\SystemUIApplication.java

//FACE_UNLOCK_SUPPORT start
import com.android.systemui.statusbar.phone.FaceUnlockUtil;
//FACE_UNLOCK_SUPPORT endsetTheme(R.style.Theme_SystemUI);//FACE_UNLOCK_SUPPORT startif (FaceUnlockUtil.isFaceUnlockSupport()) {FaceUnlockUtil.getInstance().setFaceContext(getApplicationContext());}//FACE_UNLOCK_SUPPORT endif (Process.myUserHandle().equals(UserHandle.SYSTEM)) {}

StatusBar 中增加广播监听,亮屏、拉起其它解锁方式页面、解锁。

收到亮屏广播,查询是否启用人脸解锁和已经成功录入人脸数据,均符合拉起 FaceUnlock 识别界面 com.face.unlock.UnlockActivity

收到拉起其它解锁方式页面广播,通过模拟点击上滑事件上拉屏幕。

收到解锁广播,调用 doUnlock

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java

    @VisibleForTestingprotected void registerBroadcastReceiver() {IntentFilter filter = new IntentFilter();filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);filter.addAction(Intent.ACTION_SCREEN_OFF);filter.addAction(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);//FACE_UNLOCK_SUPPORT startif (FaceUnlockUtil.isFaceUnlockSupport()) {filter.addAction(OP_SHOW_LOCKVIEW);filter.addAction(ACTION_SCREEN_UNLOCK);filter.addAction(Intent.ACTION_SCREEN_ON);}//FACE_UNLOCK_SUPPORT endmBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, null, UserHandle.ALL);}add FACE_UNLOCK_SUPPORT startprivate static final String OP_SHOW_LOCKVIEW = "cn.face.action.screen.showlockview";private static final String ACTION_SCREEN_UNLOCK = "cn.face.action.screen.unlock";//add FACE_UNLOCK_SUPPORT endprivate final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if (DEBUG) Log.v(TAG, "onReceive: " + intent);String action = intent.getAction();if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {KeyboardShortcuts.dismiss();if (mRemoteInputManager.getController() != null) {mRemoteInputManager.getController().closeRemoteInputs();}if (mBubbleController.isStackExpanded()) {mBubbleController.collapseStack();}if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {int flags = CommandQueue.FLAG_EXCLUDE_NONE;String reason = intent.getStringExtra("reason");if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;}mShadeController.animateCollapsePanels(flags);}}else if (Intent.ACTION_SCREEN_OFF.equals(action)) {if (mNotificationShadeWindowController != null) {mNotificationShadeWindowController.setNotTouchable(false);}if (mBubbleController.isStackExpanded()) {mBubbleController.collapseStack();}finishBarAnimations();resetUserExpandedStates();}else if (DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG.equals(action)) {mQSPanel.showDeviceMonitoringDialog();//add FACE_UNLOCK_SUPPORT start}else if (Intent.ACTION_SCREEN_ON.equals(action)) {Log.d("StatusBar", "receive screen on");//boolean isLocked =  mKeyguardManager.isKeyguardLocked();boolean isLocked =  isKeyguardSecure();boolean isFaceEnable =  FaceUnlockUtil.isNeedShowFaceIcon();Log.e("StatusBar", "isLocked="+isLocked+" isFaceEnable="+isFaceEnable);if (isLocked && isFaceEnable) {Log.d("StatusBar", "let's start face check");FaceUnlockUtil.getInstance().startFaceUnlockFun(context);}}else if (OP_SHOW_LOCKVIEW.equals(action)) {if (mStatusBarKeyguardViewManager != null) {Log.d("StatusBar", "let's show security lock view");FaceUnlockUtil.pullUpShowSecurityScreenView();mStatusBarKeyguardViewManager.getBouncer().showFaceErrorText();}}else if (ACTION_SCREEN_UNLOCK.equals(action)) {if (mStatusBarKeyguardViewManager != null) {Log.d("KeyguardViewBase", "let's start unlock");mStatusBarKeyguardViewManager.getBouncer().doUnlock();}//add FACE_UNLOCK_SUPPORT end}}};

KeyguardBouncer 中增加方法供 StatusBar 调用。

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\KeyguardBouncer.java

    //FACE_UNLOCK_SUPPORT startpublic void showSecurityScreenView(){mKeyguardView.dismiss(false, KeyguardUpdateMonitor.getCurrentUser(), false);}public void showFaceErrorText(){showMessage(mContext.getResources().getString(R.string.kg_wrong_face), ColorStateList.valueOf(android.graphics.Color.WHITE));}public void doUnlock(){mKeyguardView.doUnlock();}//FACE_UNLOCK_SUPPORT end

KeyguardHostView 中新增 doUnlock,最终调用到 mSecurityContainer 中

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\keyguard\KeyguardHostView.java

  // FACE_UNLOCK_SUPPORT startpublic void doUnlock(){if (mSecurityContainer != null) {Log.d(TAG, "face ok. doUnlock");mSecurityContainer.doUnlock();}}//end

KeyguardSecurityContainer 中实现真正 doUnlock,解锁其实就是通过 showNextSecurityScreenOrFinish()

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\keyguard\KeyguardSecurityContainer.java

//FACE_UNLOCK_SUPPORT start
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import com.android.systemui.statusbar.phone.FaceUnlockUtil;
//FACE_UNLOCK_SUPPORT end//FACE_UNLOCK_SUPPORT startpublic void doUnlock(){Log.i("KeyguardViewBase", "showNextSecurityScreenOrFinish(true)");final int userId = KeyguardUpdateMonitor.getCurrentUser();showNextSecurityScreenOrFinish(true, userId, true);}//FACE_UNLOCK_SUPPORT end    

LockIcon 中处理当启用人脸解锁时,将默认小锁图标替换为人脸图标,增加识别失败广播监听,手动广播后执行属性动画左右摇晃 icon。

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\LockIcon.java

import android.util.Log;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;public LockIcon(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;}//FACE_UNLOCK_SUPPORT start@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();Log.d(TAG, "onAttachedToWindow");mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_FACE_SHAKE));}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();Log.d(TAG, "onDetachedFromWindow");mContext.unregisterReceiver(mReceiver);}private static final String ACTION_FACE_SHAKE = "cn.face.action.screen.faceerror";private BroadcastReceiver mReceiver = new BroadcastReceiver(){@Overridepublic void onReceive(Context context, Intent intent){String action = intent.getAction();Log.d(TAG, "action="+action);if(ACTION_FACE_SHAKE.equals(action)) {shakeViewAnimation();}}};private void shakeViewAnimation(){TranslateAnimation transAnim = new TranslateAnimation(0, -10, 0, 0);transAnim.setRepeatCount(2);transAnim.setRepeatMode(Animation.REVERSE);transAnim.setInterpolator(new AccelerateDecelerateInterpolator());transAnim.setDuration(100);transAnim.setFillAfter(false);startAnimation(transAnim);Log.d(TAG, "shakeViewAnimation done");}//FACE_UNLOCK_SUPPORT endprivate static int getIconForState(int state) {//FACE_UNLOCK_SUPPORT startboolean  isReboot = FaceUnlockUtil.isRebootLockView();android.util.Log.d("StatusBar", "isReboot="+isReboot);//FACE_UNLOCK_SUPPORT endint iconRes;switch (state) {case STATE_LOCKED:// Scanning animation is a pulsing padlock. This means that the resting state is// just a padlock.case STATE_SCANNING_FACE:// Error animation also starts and ands on the padlock.case STATE_BIOMETRICS_ERROR://iconRes = com.android.internal.R.drawable.ic_lock;//FACE_UNLOCK_SUPPORT starticonRes = FaceUnlockUtil.isNeedShowFaceIcon() ? R.drawable.icon_face : com.android.internal.R.drawable.ic_lock;//FACE_UNLOCK_SUPPORT endbreak;case STATE_LOCK_OPEN:iconRes = com.android.internal.R.drawable.ic_lock_open;break;default:throw new IllegalArgumentException();}return iconRes;}

FaceUnlockUtil 工具类,获取人脸解锁是否启用,重启后不能使用人脸解锁功能,开始进入 FaceUnlock 识别检测界面,

识别失败自动拉起其它解锁方式。

alps\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\FaceUnlockUtil.java

package com.android.systemui.statusbar.phone;import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.graphics.drawable.AnimationDrawable;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.os.UserManager;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import android.widget.ImageView;import android.app.Instrumentation;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;public class FaceUnlockUtil {private static final boolean DEBUG = true;public final static String TAG = "FaceUnlockUtil";public final static String FACE_UNLOCK_VERSION = "v3.1-20181120";private static FaceUnlockUtil mInstance;private static Context mContext;private static UserManager mUserManager;private static Instrumentation mInstrumentation;private static StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;private FaceUnlockUtil(){}public static FaceUnlockUtil getInstance(){if(mInstance==null){mInstance=new FaceUnlockUtil();}return mInstance;}public static boolean isFaceUnlockSupport() {String support = SystemProperties.get("ro.faceunlock.support", "1");return "1".equals(support);}public void setFaceContext(Context context) {if (mContext == null && context != null) {if (DEBUG) Log.d(TAG, "setContext");mContext = context;Settings.System.putString(mContext.getContentResolver(), "base_ver", FACE_UNLOCK_VERSION);mUserManager = mContext.getSystemService(UserManager.class);mInstrumentation = new Instrumentation();}}public static boolean isFaceAppOpen() {if (mContext != null) {return (Settings.System.getInt(mContext.getContentResolver(),"persist.facelock.enable", 0) == 1);}return false;}//first don't show faceunlock when phone rebootpublic static boolean isRebootLockView() {if (isFaceUnlockSupport()) {int userId = KeyguardUpdateMonitor.getCurrentUser();return !mUserManager.isUserUnlocked(userId);//copy from statusbar\KeyguardIndicationController.java 382}return false;}public static boolean isNeedShowFaceIcon(){return !isRebootLockView() && isFaceAppOpen();}public void startFaceUnlockFun(Context context){try{Intent faceIntent = new Intent().setComponent(new ComponentName("com.face.unlock","com.face.unlock.UnlockActivity")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS| Intent.FLAG_ACTIVITY_CLEAR_TOP);context.startActivity(faceIntent);}catch(Exception e){e.printStackTrace();}}//faceunlock error need auto pullup show other unlock viewpublic static void pullUpShowSecurityScreenView() {new Thread() {@Overridepublic void run() {super.run();Point from = new Point(650, 620);Point to = new Point(650, 300);sendPointerEvent(MotionEvent.ACTION_DOWN, from);MovePointerEvent(from, to, 40, 40);sendPointerEvent(MotionEvent.ACTION_UP, to);Log.e("StatusBar", "pullUpShowSecurityScreenView");}}.start();}private static void sendPointerEvent(int action, Point point) {MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),SystemClock.uptimeMillis(), action, point.x, point.y, 0);mInstrumentation.sendPointerSync(event);event.recycle();}private static void MovePointerEvent(Point from, Point to, int distance, int step) {int nextMoveValue = getNextMoveValue(from.y, to.y, distance, step);Log.d("StatusBar", "nextMoveValue=" + nextMoveValue);if (nextMoveValue > to.y) {Point movePoint = new Point(to.x, nextMoveValue);sendPointerEvent(MotionEvent.ACTION_MOVE, movePoint);MovePointerEvent(movePoint, to, distance, step);}}private static int getNextMoveValue(int oldValue, int targetValue, int distance, int step) {if (targetValue - oldValue > distance) {return oldValue + step;} else if (targetValue - oldValue < -distance) {return oldValue - step;} else {return targetValue;}}}

三、FaceUnlock 编码

人脸识别SDK可自行接入豆荚、旷世、商汤、虹软等,这里非商用推荐虹软的,一个账号对应 5K 免费 license。

商用的可自行咨询其它商务。 这里把 FaceUnlock 中的几个关键点说一下

1、Settings.System.putxxx 方法调用存储约定启用人脸、人脸数目等数据,apk 需要使用系统签名和 android.uid.system

2、人脸识别检测页面需要做成透明Activity,在收到亮屏广播后立刻被拉起。

<style name="Transluent" parent="Theme.AppCompat.Light.NoActionBar"><item name="android:windowBackground">@color/transparent</item><item name="android:windowNoTitle">true</item><item name="android:windowIsTranslucent">true</item><!-- 去除Activity顶部黑线 --><item name="android:windowContentOverlay">@null</item><!-- 系统状态栏背景设置透明 --><item name="android:windowDrawsSystemBarBackgrounds" tools:targetApi="lollipop">@color/transparent</item></style>

3、实现暗光环境下自动补光功能,需要有光线传感器支持,当环境亮度低于设定标准值时,通过提升屏幕亮度来达到自动

补光效果。

alps\build\make\core\Makefile

# add for faceunlock
ifeq ($(strip $(FACE_UNLOCK_SUPPORT)), yes)@echo "Target FaceUnlockOpen: $@"$(hide) chmod 777 packages/apps/FaceUnlock/FaceUnlockOpen.shbash packages/apps/FaceUnlock/FaceUnlockOpen.sh $@ >> $@
else@echo "Target FaceUnlockClose: $@"$(hide) chmod 777 packages/apps/FaceUnlock/FaceUnlockClose.shbash packages/apps/FaceUnlock/FaceUnlockClose.sh $@ >> $@
endif
# add for faceunlockbuild_desc :=INSTALLED_RECOVERYIMAGE_TARGET :=
ifdef BUILDING_RECOVERY_IMAGE
ifneq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img
endif
endif

alps\build\make\target\product\handheld_system.mk

PRODUCT_PACKAGES += \FaceUnlock \

alps\device\mediateksample\I7170\ProjectConfig.mk


FACE_UNLOCK_SUPPORT = yes

alps\packages\apps\FaceUnlock\Android.mk

ifeq ($(strip $(FACE_UNLOCK_SUPPORT)), yes)
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Module name should match apk name to be installed
LOCAL_MODULE := FaceUnlock
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
include $(BUILD_PREBUILT)
endif

alps\packages\apps\FaceUnlock\FaceUnlockClose.sh

#!/bin/bashecho "ro.faceunlock.support=0"

alps\packages\apps\FaceUnlock\FaceUnlockOpen.sh

#!/bin/bashecho "ro.faceunlock.support=1"

外加 FaceUnlock.apk

Android Q 上的Biometric生物识别之Face人脸识别流程