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

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

通过前面锁屏加载流程可以知道在KeyguardSecurityContainer中使用getSecurityView()根据不同的securityMode inflate出来,并添加到界面上的。

我们知道,Pattern锁所使用的layout是 R.layout.keyguard_pattern_view;

java 复制代码
<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

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相关函数获得的事件信息,然后在处理当前的事件信息。

java 复制代码
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<LockPatternView.Cell> pattern),这时候就开始图案解锁的验证了。
  • ACTION_CANCEL:重置pattern状态,回调mOnPatternListener.onPatternCleared()
图案解锁验证

src/com/android/keyguard/KeyguardPatternView.java

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[])

java 复制代码
// 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
相关推荐
一笑的小酒馆8 小时前
Android在ksp中简单使用Room
android
我是唐青枫9 小时前
Linux ar 命令使用详解
linux·运维·服务器
mljy.9 小时前
Linux《进程概念(上)》
linux
IEVEl9 小时前
Centos7 开放端口号
linux·网络·centos
meimeiqian10 小时前
flutter android端抓包工具
android·flutter
Android技术之家10 小时前
谷歌决定终止开源Android以及对开发者的影响
android·开源
我要升天!10 小时前
Linux中《环境变量》详细介绍
linux·运维·chrome
MobiCetus11 小时前
有关pip与conda的介绍
linux·windows·python·ubuntu·金融·conda·pip
每次的天空11 小时前
Android Jetpack学习总结(源码级理解)
android·学习·android jetpack
木子庆五11 小时前
Android设计模式之代理模式
android·设计模式·代理模式