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


目录
相关文章
|
7月前
|
XML 缓存 Android开发
Android开发,使用kotlin学习多媒体功能(详细)
Android开发,使用kotlin学习多媒体功能(详细)
162 0
|
4月前
|
API 开发工具 Android开发
视觉智能开放平台产品使用合集之人脸活体检测能力是否支持Android端或者iOS端直接调用
视觉智能开放平台是指提供一系列基于视觉识别技术的API和服务的平台,这些服务通常包括图像识别、人脸识别、物体检测、文字识别、场景理解等。企业或开发者可以通过调用这些API,快速将视觉智能功能集成到自己的应用或服务中,而无需从零开始研发相关算法和技术。以下是一些常见的视觉智能开放平台产品及其应用场景的概览。
|
2月前
|
Android开发
Android开发表情emoji功能开发
本文介绍了一种在Android应用中实现emoji表情功能的方法,通过将图片与表情字符对应,实现在`TextView`中的正常显示。示例代码展示了如何使用自定义适配器加载emoji表情,并在编辑框中输入或删除表情。项目包含完整的源码结构,可作为开发参考。视频演示和源码详情见文章内链接。
74 4
Android开发表情emoji功能开发
|
2月前
|
安全 Android开发 iOS开发
Android vs iOS:探索移动操作系统的设计与功能差异###
【10月更文挑战第20天】 本文深入分析了Android和iOS两个主流移动操作系统在设计哲学、用户体验、技术架构等方面的显著差异。通过对比,揭示了这两种系统各自的独特优势与局限性,并探讨了它们如何塑造了我们的数字生活方式。无论你是开发者还是普通用户,理解这些差异都有助于更好地选择和使用你的移动设备。 ###
55 3
|
4月前
|
编解码 测试技术 Android开发
Android经典实战之用 CameraX 库实现高质量的照片和视频拍摄功能
本文详细介绍了如何利用CameraX库实现高质量的照片及视频拍摄功能,包括添加依赖、初始化、权限请求、配置预览与捕获等关键步骤。此外,还特别针对不同分辨率和帧率的视频拍摄提供了性能优化策略,确保应用既高效又稳定。
416 1
Android经典实战之用 CameraX 库实现高质量的照片和视频拍摄功能
|
3月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
4月前
|
图形学 Android开发
小功能⭐️Unity调用Android常用事件
小功能⭐️Unity调用Android常用事件
|
6月前
|
数据库 Android开发 数据安全/隐私保护
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
270 2
|
6月前
|
Android开发
Android中如何快速的实现RecycleView的拖动重排序功能
使用`ItemTouchHelper`和自定义`Callback`,在`RecyclerView`中实现拖动排序功能。定义`ItemTouchHelperAdapter`接口,`Adapter`实现它以处理`onItemMove`方法。`SimpleItemTouchHelperCallback`设置拖动标志,如`LEFT`或`RIGHT`(水平拖动),并绑定到`RecyclerView`以启用拖动。完成这些步骤后,即可实现拖放排序。关注公众号“AntDream”获取更多内容。
126 3
|
7月前
|
移动开发 监控 Android开发
构建高效Android应用:从内存优化到电池寿命代码之美:从功能实现到艺术创作
【5月更文挑战第28天】 在移动开发领域,特别是针对Android系统,性能优化始终是关键议题之一。本文深入探讨了如何通过细致的内存管理和电池使用策略,提升Android应用的运行效率和用户体验。文章不仅涵盖了现代Android设备上常见的内存泄漏问题,还提出了有效的解决方案,包括代码级优化和使用工具进行诊断。同时,文中也详细阐述了如何通过减少不必要的后台服务、合理管理设备唤醒锁以及优化网络调用等手段延长应用的电池续航时间。这些方法和技术旨在帮助开发者构建更加健壮、高效的Android应用程序。