一、Settings 模块修改
从 Q 版本开始 aosp 代码就已经默认支持 Biometric 生物识别相关功能,Settings 中不显示入口菜单是因为判断了
硬件是否支持。可以先看下安全页面中显示的菜单如下。
我的设备默认是支持指纹模块的,所以在安全菜单中显示了这个入口。
这块的逻辑如下,当没有设置图案/pin码/密码中任意一种锁屏方式时,进入指纹菜单中,会显示如图所示必须先设置一种
备用解锁方式才可继续设置指纹操作。之前如果已经设置过三者之一的解锁方式,则进入指纹菜单中则跳过刚刚的页面,
获取当前是否已经录入过指纹,若已录入则显示管理页面,若未录入则显示引导录入界面。
人脸解锁显示逻辑和指纹是一样的,所以我们直接采用系统默认的流程,将跳转页面修改为我们编码的 FaceUnlock
Settings 修改很简单,在原有的锁屏选项中增加或修改人脸解锁项。
在 FaceStatusPreferenceController 中将判断条件修改为查询 prop 属性 ro.faceunlock.support 对应值,
编译时通过宏定义控制写入该值就行。修改完这步,在安全界面中指纹同级菜单下就会显示人脸入口。
alps\vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\biometrics\face\FaceStatusPreferenceController.java
@Override protected 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(); } @Override protected boolean hasEnrolledBiometrics() { return false; //return mFaceManager.hasEnrolledTemplates(getUserId()); }
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
@Override protected 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 start if (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 使用流程介绍界面
com.face.unlock.FaceSetActivity 管理主界面
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
@Override public 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 start if (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{//end getActivity().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"> <path android: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"/> <path android: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"/> <path android: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"/> <path android: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>
连续多次人脸解锁识别失败时,提示语提醒使用其它方式进行解锁。
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 end setTheme(R.style.Theme_SystemUI); //FACE_UNLOCK_SUPPORT start if (FaceUnlockUtil.isFaceUnlockSupport()) { FaceUnlockUtil.getInstance().setFaceContext(getApplicationContext()); } //FACE_UNLOCK_SUPPORT end if (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
@VisibleForTesting protected 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 start if (FaceUnlockUtil.isFaceUnlockSupport()) { filter.addAction(OP_SHOW_LOCKVIEW); filter.addAction(ACTION_SCREEN_UNLOCK); filter.addAction(Intent.ACTION_SCREEN_ON); }//FACE_UNLOCK_SUPPORT end mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, null, UserHandle.ALL); } add FACE_UNLOCK_SUPPORT start private 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 end private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public 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 start public 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 start public 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 start public 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 @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Log.d(TAG, "onAttachedToWindow"); mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_FACE_SHAKE)); } @Override protected 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(){ @Override public 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 end private static int getIconForState(int state) { //FACE_UNLOCK_SUPPORT start boolean isReboot = FaceUnlockUtil.isRebootLockView(); android.util.Log.d("StatusBar", "isReboot="+isReboot); //FACE_UNLOCK_SUPPORT end int 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 start iconRes = FaceUnlockUtil.isNeedShowFaceIcon() ? R.drawable.icon_face : com.android.internal.R.drawable.ic_lock; //FACE_UNLOCK_SUPPORT end break; 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 reboot public 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 view public static void pullUpShowSecurityScreenView() { new Thread() { @Override public 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.sh bash packages/apps/FaceUnlock/FaceUnlockOpen.sh $@ >> $@ else @echo "Target FaceUnlockClose: $@" $(hide) chmod 777 packages/apps/FaceUnlock/FaceUnlockClose.sh bash packages/apps/FaceUnlock/FaceUnlockClose.sh $@ >> $@ endif # add for faceunlock build_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/bash echo "ro.faceunlock.support=0"
alps\packages\apps\FaceUnlock\FaceUnlockOpen.sh
#!/bin/bash echo "ro.faceunlock.support=1"
外加 FaceUnlock.apk
Android Q 上的Biometric生物识别之Face人脸识别流程