Android8.1 SystemUI 之圖案鎖驗(yàn)證流程

Keyguard之滑動(dòng)解鎖流程一文中,我們已經(jīng)分析過,不同的安全鎖類型是在KeyguardSecurityContainer中使用getSecurityView根據(jù)不同的securityMode inflate出來,并添加到界面上的。那么本文我們就來以圖案鎖為例分析一下,安全鎖解鎖時(shí)的驗(yàn)證流程吧。

Screenshot_20181116-100802.png

圖案解鎖的滑動(dòng)事件處理

我們知道,Pattern鎖所使用的layout是case Pattern: return 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>

那么圖案解鎖的滑動(dòng)事件處理,就是在LockPatternView,源碼位置是android/frameworks/base/core/java/com/android/internal/widget/LockPatternView.java,是一個(gè)系統(tǒng)公共控件,下面我們就分析一下這個(gè)view是如何處理觸摸輸入的。

    @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();
                }
                ...
                return true;
        }
        return false;
    }

不同的MotionEvent對(duì)應(yīng)幾個(gè)不同的handle方法處理,代碼行數(shù)太多,我們這里大致總結(jié)如下

  • ACTION_DOWN(handleActionDown):根據(jù)觸摸事件的坐標(biāo),使用算法detectAndAddHit(x, y)獲取是否有命中的點(diǎn),如果有,會(huì)調(diào)用addCellToPattern將命中的Cell添加到mPattern中,后即回調(diào)mOnPatternListener.onPatternStart()通知監(jiān)聽器,KeyguardPatternView實(shí)現(xiàn)并監(jiān)聽了OnPatternListener,做了清除安全提示內(nèi)容的動(dòng)作。另外計(jì)算需要重繪區(qū)域,并調(diào)用invalidate進(jìn)行局部重繪。

  • ACTION_MOVE(handleActionMove):在這里 LockPatternView會(huì)對(duì)所有的歷史坐標(biāo)加當(dāng)前事件坐標(biāo)遍歷for (int i = 0; i < historySize + 1; i++),獲取命中點(diǎn),另外如果ACTION_DOWN時(shí)沒有獲取到命中點(diǎn),流程同上面的ACTION_UP,然后也會(huì)回調(diào)mOnPatternListener.onPatternStart()。最后會(huì)把所有motionevent對(duì)應(yīng)的重繪區(qū)域進(jìn)行union,并調(diào)用invalidate進(jìn)行局部重繪。
    關(guān)于MotionEvent的歷史坐標(biāo)getHistoricalX,getHistoricalY的解釋可以參考developer文檔-->MotionEvent

關(guān)于歷史坐標(biāo)
為了效率,Android系統(tǒng)在處理ACTION_MOVE事件時(shí)會(huì)將連續(xù)的幾個(gè)多觸點(diǎn)移動(dòng)事件打包到一個(gè)MotionEvent對(duì)象中。我們可以通過getX(int)和getY(int)來獲得最近發(fā)生的一個(gè)觸摸點(diǎn)事件的坐標(biāo),然后使用getHistorical(int,int)和getHistorical(int,int)來獲得時(shí)間稍早的觸點(diǎn)事件的坐標(biāo),二者是發(fā)生時(shí)間先后的關(guān)系。所以,我們應(yīng)該先處理通過getHistoricalXX相關(guān)函數(shù)獲得的事件信息,然后在處理當(dāng)前的事件信息。

        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不為空的話,會(huì)重置mPatternInProgress,取消動(dòng)畫,然后回調(diào)mOnPatternListener.onPatternDetected(final List<LockPatternView.Cell> pattern),這時(shí)候就開始圖案解鎖的驗(yàn)證了。

  • ACTION_CANCEL:重置pattern狀態(tài),回調(diào)mOnPatternListener.onPatternCleared()

關(guān)于ACTION_CANCEL的產(chǎn)生和事件回傳你一定要知道的事
在當(dāng)前控件(子控件)收到前驅(qū)事件(ACTION_MOVE或者ACTION_MOVE)后,它的父控件突然插手(interceptTouchEvent)截?cái)嗍录膫鬟f,這時(shí)當(dāng)前控件就會(huì)收到ACTION_CANCEL,收到此事件后,不管子控件此時(shí)返回true或者false,都認(rèn)為這一個(gè)動(dòng)作已完成,不會(huì)再回傳到父控件的OnTouchEvent中處理,同時(shí)后續(xù)事件,會(huì)通過dispatchEvent方法直接傳送到父控件這里來處理。
那么有的朋友就要問了,不是說如果子控件的OnTouchEvent返回false,表明事件未被處理,是回傳到父控件去處理的嗎? 其實(shí),只有ACTION_DOWN事件才可以被回傳,ACTION_MOVE和ACTION_UP事件會(huì)跟隨ACTION_DOWN事件,即ACTION_DOWN是哪個(gè)控件處理的,后續(xù)事件都傳遞到這里,不會(huì)上拋到父控件,ACTION_CANCEL也不能回傳。
結(jié)論:ACTION_CANCEL事件是收到前驅(qū)事件后,后續(xù)事件被父控件攔截的情況下產(chǎn)生,onTouchEvent的事件回傳到父控件只會(huì)發(fā)生在ACTION_DOWN事件中

圖案解鎖驗(yàn)證

src/com/android/keyguard/KeyguardPatternView.java

    private class UnlockPatternListener implements LockPatternView.OnPatternListener {
    ...
        @Override
        public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
            // 禁用事件輸入,取消前面的AsyncTask
            mLockPatternView.disableInput();
            if (mPendingLockCheck != null) {
                mPendingLockCheck.cancel(false);
            }

            final int userId = KeyguardUpdateMonitor.getCurrentUser();
            // 如果連接的點(diǎn)小于4個(gè),作為無效密碼
            if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                mLockPatternView.enableInput();
                onPatternChecked(userId, false, 0, false /* not valid - too short */);
                return;
            }

            mPendingLockCheck = LockPatternChecker.checkPattern(
                    mLockPatternUtils,
                    pattern,
                    userId,
                    new LockPatternChecker.OnCheckCallback() {...});
            if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
                mCallback.userActivity();
            }
        }
    ...
    }

checkPattern方法中構(gòu)造了一個(gè)AsyncTask,并start()。
在onPreExecute()中復(fù)制了一份pattern,用ArrayList包裝
patternCopy = new ArrayList(pattern);

關(guān)于AsyncTask的cancel(boolean mayInterruptIfRunning) 方法
先說結(jié)論,單獨(dú)使用cancel不會(huì)像你想的那樣好好工作。
如果你調(diào)用了AsyncTask的cancel(false),doInBackground()仍然會(huì)執(zhí)行到方法結(jié)束,只是不會(huì)去調(diào)用onPostExecute()方法。但是實(shí)際上這是讓應(yīng)用程序執(zhí)行了沒有意義的操作。
那么是不是我們調(diào)用cancel(true)前面的問題就能解決呢?并非如此。如果mayInterruptIfRunning設(shè)置為true,會(huì)使任務(wù)盡早結(jié)束,但是如果的doInBackground()有不可打斷的方法會(huì)失效。
可見.cancel()是給AsyncTask設(shè)置一個(gè)"canceled"的狀態(tài),那么想要終止異步任務(wù),就需要在異步任務(wù)當(dāng)中結(jié)束。

// Task被取消了,馬上退出
if(isCancelled()) return null;
.......
// Task被取消了,馬上退出
if(isCancelled()) return null;
}

在doInBackground()中LockPatternUtils登場(chǎng)了,圖案密碼的驗(yàn)證,就是調(diào)用了LockPatternUtils的checkPattern方法
return utils.checkPattern(patternCopy, userId, callback::onEarlyMatched);

下面我們來看一下checkPattern方法的定義:
android/frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java

    /**
     * Check to see if a pattern matches the saved pattern.  If no pattern exists,
     * always returns true.
     * @param pattern The pattern to check.
     * @return Whether the pattern matches the stored one.
     */
    public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId,
            @Nullable CheckCredentialProgressCallback progressCallback)
            throws RequestThrottledException {
        throwIfCalledOnMainThread();
        return checkCredential(patternToString(pattern), CREDENTIAL_TYPE_PATTERN, userId,
                progressCallback);
    }

LockPatternUtils首先會(huì)把pattern使用patternToString算法轉(zhuǎn)換成字符串,之后調(diào)用checkCredential進(jìn)行驗(yàn)證
在LockPatternUtils中還有一個(gè)checkPassword方法,對(duì)應(yīng)的是PIN碼/密碼,所以圖案鎖/密碼鎖/PIN碼鎖的驗(yàn)證流程到這里開始就一致了。

    /**
     * Serialize a pattern.
     * @param pattern The pattern.
     * @return The pattern in string form.
     */
    public static String patternToString(List<LockPatternView.Cell> pattern) {
        if (pattern == null) {
            return "";
        }
        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 new String(res);
    }

KeyguardPINView/KeyguardPasswordView/KeyguardSimPinView/KeyguardSimPukView
都是繼承自KeyguardAbsKeyInputView,用于密碼驗(yàn)證的方法是verifyPasswordAndUnlock()
其中,KeyguardSimPinView和KeyguardSimPukView重寫了該方法,所以有自己獨(dú)特的驗(yàn)證方式。
而KeyguardPasswordView和KeyguardPINView都是使用LockPatternUtils的checkPassword進(jìn)行密碼驗(yàn)證

checkPassword和checkPassword都會(huì)去調(diào)用checkCredential方法

    private boolean checkCredential(String credential, int type, int userId,
            @Nullable CheckCredentialProgressCallback progressCallback)
            throws RequestThrottledException {
        try {
            VerifyCredentialResponse response = getLockSettings().checkCredential(credential, type,
                    userId, wrapCallback(progressCallback));

            if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                return true;
            } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
                throw new RequestThrottledException(response.getTimeout());
            } else {
                return false;
            }
        } catch (RemoteException re) {
            return false;
        }
    }

這里的getLockSettings()是什么呢?
ILockSettings service = ILockSettings.Stub.asInterface( ServiceManager.getService("lock_settings"));
原來是獲得了LockSettingService的IPC接口,asInterface獲取到aidl的Stub.Proxy對(duì)象,對(duì)本地?cái)?shù)據(jù)進(jìn)行封包,進(jìn)行進(jìn)程間通信。到這里我們明白了,原來密碼的驗(yàn)證,最終是在LockSettingService中進(jìn)行的。

android/frameworks/base/services/core/java/com/android/server/locksettings/LockSettingsService.java

    @Override
    public VerifyCredentialResponse checkCredential(String credential, int type, int userId,
            ICheckCredentialProgressCallback progressCallback) throws RemoteException {
        checkPasswordReadPermission(userId);
        VerifyCredentialResponse response = doVerifyCredential(credential, type, false, 0, userId, progressCallback);
        if ((response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) &&
                            (userId == UserHandle.USER_OWNER)) {
                retainPassword(credential);
        }
        return response;
    }

到這里, 我們的圖案鎖解鎖流程就算是梳理清楚了。

總結(jié)一下:在繪制密碼后手指抬起的時(shí)候,如果已存的有效點(diǎn)數(shù)達(dá)到4個(gè)及以上,就會(huì)使用LockPatternChecker.checkPattern方法啟動(dòng)一個(gè)AsyncTask, 并在doInBackground中調(diào)用LockPatternUtils.checkPattern進(jìn)行密碼驗(yàn)證,此時(shí)pattern會(huì)被轉(zhuǎn)化成字符串形式,最終和密碼鎖PIN碼鎖一樣,都是遠(yuǎn)程調(diào)用到LockPatternService的checkCredential接口進(jìn)行驗(yàn)證。在整個(gè)操作過程中,mOnPatternListener被用于通知LockPatternView進(jìn)行安全鎖提示內(nèi)容和Pattern狀態(tài)的刷新。

checkCredential的具體算法邏輯以及LockPatternUtils和LockSettingsService中還有很多與安全鎖加鎖/解鎖/鎖狀態(tài)判斷相關(guān)的接口,后面我們會(huì)單獨(dú)進(jìn)行分析:)。

本文章已經(jīng)獨(dú)家授權(quán)ApeClub公眾號(hào)使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容