Android11.0 增加人脸解锁功能

2rP5TI.png

一、Settings 模块修改

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

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

2jaVG8.png

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

这块的逻辑如下,当没有设置图案/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());
    }

2vr4UJ.png

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 使用流程介绍界面

2vsCxP.png

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

2vsaxx.png

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>

2jgmb8.png

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

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人脸识别流程

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cczhengv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值