Android11.0 增加人脸解锁功能

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


目录
相关文章
|
1月前
|
XML 缓存 Android开发
Android开发,使用kotlin学习多媒体功能(详细)
Android开发,使用kotlin学习多媒体功能(详细)
111 0
|
1月前
|
安全 Linux Android开发
Android 安全功能
Android 安全功能
53 0
|
7天前
|
数据库 Android开发 数据安全/隐私保护
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
51 2
|
9天前
|
Android开发
Android中如何快速的实现RecycleView的拖动重排序功能
使用`ItemTouchHelper`和自定义`Callback`,在`RecyclerView`中实现拖动排序功能。定义`ItemTouchHelperAdapter`接口,`Adapter`实现它以处理`onItemMove`方法。`SimpleItemTouchHelperCallback`设置拖动标志,如`LEFT`或`RIGHT`(水平拖动),并绑定到`RecyclerView`以启用拖动。完成这些步骤后,即可实现拖放排序。关注公众号“AntDream”获取更多内容。
15 3
|
30天前
|
移动开发 监控 Android开发
构建高效Android应用:从内存优化到电池寿命代码之美:从功能实现到艺术创作
【5月更文挑战第28天】 在移动开发领域,特别是针对Android系统,性能优化始终是关键议题之一。本文深入探讨了如何通过细致的内存管理和电池使用策略,提升Android应用的运行效率和用户体验。文章不仅涵盖了现代Android设备上常见的内存泄漏问题,还提出了有效的解决方案,包括代码级优化和使用工具进行诊断。同时,文中也详细阐述了如何通过减少不必要的后台服务、合理管理设备唤醒锁以及优化网络调用等手段延长应用的电池续航时间。这些方法和技术旨在帮助开发者构建更加健壮、高效的Android应用程序。
|
9天前
|
存储 数据库 Android开发
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
13 0
|
1月前
|
Android开发 数据安全/隐私保护 iOS开发
ios和安卓测试包发布网站http://fir.im的注册与常用功能
ios和安卓测试包发布网站http://fir.im的注册与常用功能
25 0
ios和安卓测试包发布网站http://fir.im的注册与常用功能
|
1月前
|
机器学习/深度学习 人工智能 TensorFlow
安卓中的人工智能:集成机器学习功能
【4月更文挑战第14天】在数字化时代,人工智能与机器学习正驱动安卓平台的技术革新。谷歌的ML Kit和TensorFlow Lite为开发者提供了便捷的集成工具,使得应用能实现图像识别、文本转换等功能,提升用户体验。尽管面临数据隐私和安全性的挑战,但随着技术进步,更强大的AI功能将预示着移动端的未来,为开发者创造更多创新机遇。
|
1月前
|
Android开发
Android SystemUI去掉拖动亮度条QSPanel界面隐藏功能
Android SystemUI去掉拖动亮度条QSPanel界面隐藏功能
34 0
|
1月前
|
存储 数据库 Android开发
Android实现手机内存存储功能
Android实现手机内存存储功能
35 2