• Android10.0 锁屏分析-KeyguardPatternView图案锁分析


    首先一起看看下面这张图:

    通过前面锁屏加载流程可以知道在KeyguardSecurityContainer中使用getSecurityView()根据不同的securityMode inflate出来,并添加到界面上的。
    我们知道,Pattern锁所使用的layout是 R.layout.keyguard_pattern_view;

    <com.android.keyguard.KeyguardPatternView ...>
    ...
                <com.android.internal.widget.LockPatternView
                    android:id="@+id/lockPatternView"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="1"
                    android:layout_marginEnd="8dip"
                    android:layout_marginBottom="4dip"
                    android:layout_marginStart="8dip"
                    android:layout_gravity="center_horizontal"
                    android:gravity="center"
                    android:clipChildren="false"
                    android:clipToPadding="false" />
    ...
        </FrameLayout>
    </com.android.keyguard.KeyguardPatternView>
    

    那么图案解锁的滑动事件处理,就是在LockPatternView,是一个系统公共控件,下面我们就分析一下这个view是如何处理触摸输入的:
    frameworks/base/core/java/com/android/internal/widget/LockPatternView.java

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (!mInputEnabled || !isEnabled()) {
                return false;
            }
            switch(event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    handleActionDown(event);
                    return true;
                case MotionEvent.ACTION_UP:
                    handleActionUp();
                    return true;
                case MotionEvent.ACTION_MOVE:
                    handleActionMove(event);
                    return true;
                case MotionEvent.ACTION_CANCEL:
                    if (mPatternInProgress) {
                        setPatternInProgress(false);
                        resetPattern();
                        notifyPatternCleared();
                    }
                    if (PROFILE_DRAWING) {
                        if (mDrawingProfilingStarted) {
                            Debug.stopMethodTracing();
                            mDrawingProfilingStarted = false;
                        }
                    }
                    return true;
            }
            return false;
        }
    

    几种事件类型:

    事件简介
    ACTION_DOWN手指初次触摸到屏幕时触发
    ACTION_MOVE手指在屏幕上滑动时触发,会多次触发
    ACTION_UP手指离开屏幕时触发
    ACTION_CANCEL事件被上层拦截时触发
    ACTION_OUTSIDE手指不在控件区域时触发

    不同的MotionEvent对应几个不同的handle方法处理,代码行数太多,我们这里大致总结如下:

    • ACTION_DOWN(handleActionDown):根据触摸事件的坐标,使用算法detectAndAddHit(x, y)获取是否有命中的点,如果有,会调用addCellToPattern将命中的Cell添加到mPattern中,后即回调mOnPatternListener.onPatternStart()通知监听器,KeyguardPatternView实现并监听了OnPatternListener,做了清除安全提示内容的动作。另外计算需要重绘区域,并调用invalidate进行局部重绘。
    • ACTION_MOVE(handleActionMove):在这里 LockPatternView会对所有的历史坐标加当前事件坐标遍历for (int i = 0; i < historySize + 1; i++),获取命中点,另外如果ACTION_DOWN时没有获取到命中点,流程同上面的ACTION_UP,然后也会回调mOnPatternListener.onPatternStart()。最后会把所有motionevent对应的重绘区域进行union,并调用invalidate进行局部重绘。

    关于历史坐标
      为了效率,Android系统在处理ACTION_MOVE事件时会将连续的几个多触点移动事件打包到一个MotionEvent对象中。我们可以通过getX(int)和getY(int)来获得最近发生的一个触摸点事件的坐标,然后使用getHistorical(int,int)和getHistorical(int,int)来获得时间稍早的触点事件的坐标,二者是发生时间先后的关系。所以,我们应该先处理通过getHistoricalXX相关函数获得的事件信息,然后在处理当前的事件信息。

    for (int i = 0; i < historySize + 1; i++) {
        final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
        final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
     ...
    }
    
    • ACTION_UP(handleActionUp):如果mPattern不为空的话,会重置mPatternInProgress,取消动画,然后回调mOnPatternListener.onPatternDetected(final List pattern),这时候就开始图案解锁的验证了。
    • ACTION_CANCEL:重置pattern状态,回调mOnPatternListener.onPatternCleared()
    图案解锁验证

    src/com/android/keyguard/KeyguardPatternView.java

        private class UnlockPatternListener implements LockPatternView.OnPatternListener {
        ...
            @Override
            public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
               if (DEBUG) Log.d(TAG, "onPatternDetected");
                mKeyguardUpdateMonitor.setCredentialAttempted();
                mLockPatternView.disableInput();
                if (mPendingLockCheck != null) {
                    mPendingLockCheck.cancel(false);
                }
                final int userId = KeyguardUpdateMonitor.getCurrentUser();
                if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                    mLockPatternView.enableInput();
                    onPatternChecked(userId, false, 0, false /* not valid - too short */);
                    return;
                }
                if (LatencyTracker.isEnabled(mContext)) {
                    LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
                    LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
                }
                mPendingLockCheck = LockPatternChecker.checkCredential(
                        mLockPatternUtils,
                        LockscreenCredential.createPattern(pattern),    // 这里跟进去,会发现将 pattern转化成了 byte[]
                        userId,
                        new LockPatternChecker.OnCheckCallback() {
                            @Override
                            public void onEarlyMatched() {
                                if (DEBUG) Log.d(TAG, "onEarlyMatched");
                                if (LatencyTracker.isEnabled(mContext)) {
                                    LatencyTracker.getInstance(mContext).onActionEnd(
                                            ACTION_CHECK_CREDENTIAL);
                                }
                                onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
                                        true /* isValidPattern */);
                            }
                            @Override
                            public void onChecked(boolean matched, int timeoutMs) {
                                if (DEBUG) Log.d(TAG, "onChecked matched:" + matched);
                                if (LatencyTracker.isEnabled(mContext)) {
                                    LatencyTracker.getInstance(mContext).onActionEnd(
                                            ACTION_CHECK_CREDENTIAL_UNLOCKED);
                                }
                                mLockPatternView.enableInput();
                                mPendingLockCheck = null;
                                if (!matched) {
                                    onPatternChecked(userId, false /* matched */, timeoutMs,
                                            true /* isValidPattern */);
                                }
                            }
                            @Override
                            public void onCancelled() {
                               if (DEBUG) Log.d(TAG, "onCancelled");
                                // We already got dismissed with the early matched callback, so we
                                // cancelled the check. However, we still need to note down the latency.
                                if (LatencyTracker.isEnabled(mContext)) {
                                    LatencyTracker.getInstance(mContext).onActionEnd(
                                            ACTION_CHECK_CREDENTIAL_UNLOCKED);
                                }
                            }
                        });
                if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
                    mCallback.userActivity();
                    mCallback.onUserInput();
                }
            }
        ...
        }
    

    在绘制密码后手指抬起的时候,如果已存的有效点数达到4个及以上,就会使用LockPatternChecker.checkCredential 方法调用 task.execute() 启动一个AsyncTask, 并在doInBackground中调用LockPatternUtils.checkCredential 进行密码验证,此时pattern会被转化成字节形式(LockscreenCredential.createPattern(pattern) 这里跟进去,会发现将 pattern转化成了 byte[])

    // LockPatternUtils.java
    public static byte[] patternToByteArray(List<LockPatternView.Cell> pattern) {
        if (pattern == null) {
            return new byte[0];
        }
        final int patternSize = pattern.size();
        byte[] res = new byte[patternSize];
        for (int i = 0; i < patternSize; i++) {
            LockPatternView.Cell cell = pattern.get(i);
            res[i] = (byte) (cell.getRow() * 3 + cell.getColumn() + '1');
        }
        return res;
    }
    

    最终和密码锁PIN码锁一样,都是远程调用到LockSettingsService 的 checkCredential 接口进行验证。
    Keyguard接收用户输入的密码会通过Binder到framework层的LockSettingsService,LockSettingsService经过一系列调用会通过getGateKeeperService获取GateKeeperService然后调用verifyChallenge方法将密码继续忘底层传递,framework的调用栈如下:

    java.lang.Throwab
    comandroid.locksettings.1locksettings.LockSettingsService. spBasedDoVerifyCredential(LockSettingsService. java:2724)COT3767075server.ocksettingsLockSettingsService.doVerifyCredentia1(LockSettingsSeryice.java:2020comandroservercom android.server.locksettings.LockSettingsService.doVerifyCredential(LockSettingsService. jaya:1999atcom android.server.locksettings.LockSettingsService. checkCredential(LockSettingsService. java:1972)com android.internal.widget.ILockSettings$Stub.onTransact(ILockSettings.java:542atatexecTransactInternal(Binder.java:1159)execTransact(Binder.java:1123)at
     SyntheticPasswordllanager. unwrapPasswordBasedSyntheticPassword(SyntheticPasswordllanager. java: 1016
    android.os.Binderandroid.os.Binder
    
  • 相关阅读:
    【Java】Java 11 新特性概览
    【python海洋专题二十五】给南海年平均海流+scale
    Java笔记——控制台模拟“双色球”福利彩票游戏
    Python分享之对象的属性
    torch.nn.DataParallel类
    学神经网络需要什么基础,神经网络需要什么基础
    Hadoop(HDFS)
    Apifox和Eolink两个测试工具谁最实用?
    (完整版)《光学教程》(姚启钧)课后习题解答
    【Cocos Creator 3.5实现赛车游戏】10.实现汽车节点的运动逻辑
  • 原文地址:https://blog.csdn.net/u010345983/article/details/134877199