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

Keyguard之滑動(dòng)解鎖流程一文中裆针,我們已經(jīng)分析過(guò)碍岔,不同的安全鎖類型是在KeyguardSecurityContainer中使用getSecurityView根據(jù)不同的securityMode inflate出來(lái)师逸,并添加到界面上的。那么本文我們就來(lái)以圖案鎖為例分析一下耕陷,安全鎖解鎖時(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)聽(tīng)器惶室,KeyguardPatternView實(shí)現(xiàn)并監(jiān)聽(tīng)了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í)沒(méi)有獲取到命中點(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ì)象中。我們可以通過(guò)getX(int)和getY(int)來(lái)獲得最近發(fā)生的一個(gè)觸摸點(diǎn)事件的坐標(biāo)蔓钟,然后使用getHistorical(int,int)和getHistorical(int,int)來(lái)獲得時(shí)間稍早的觸點(diǎn)事件的坐標(biāo)永票,二者是發(fā)生時(shí)間先后的關(guān)系。所以,我們應(yīng)該先處理通過(guò)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ì)通過(guò)dispatchEvent方法直接傳送到父控件這里來(lái)處理少漆。
那么有的朋友就要問(wèn)了,不是說(shuō)如果子控件的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è)双泪,作為無(wú)效密碼
            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) 方法
先說(shuō)結(jié)論,單獨(dú)使用cancel不會(huì)像你想的那樣好好工作残腌。
如果你調(diào)用了AsyncTask的cancel(false)村斟,doInBackground()仍然會(huì)執(zhí)行到方法結(jié)束贫导,只是不會(huì)去調(diào)用onPostExecute()方法。但是實(shí)際上這是讓應(yīng)用程序執(zhí)行了沒(méi)有意義的操作蟆盹。
那么是不是我們調(diào)用cancel(true)前面的問(wèn)題就能解決呢孩灯?并非如此。如果mayInterruptIfRunning設(shè)置為true逾滥,會(huì)使任務(wù)盡早結(jié)束峰档,但是如果的doInBackground()有不可打斷的方法會(huì)失效。
可見(jiàn).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);

下面我們來(lái)看一下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"));
原來(lái)是獲得了LockSettingService的IPC接口,asInterface獲取到aidl的Stub.Proxy對(duì)象逞敷,對(duì)本地?cái)?shù)據(jù)進(jìn)行封包狂秦,進(jìn)行進(jìn)程間通信。到這里我們明白了推捐,原來(lái)密碼的驗(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è)操作過(guò)程中,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)系作者
  • 序言:七十年代末而线,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子恋日,更是在濱河造成了極大的恐慌膀篮,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岂膳,死亡現(xiàn)場(chǎng)離奇詭異誓竿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)谈截,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門筷屡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人簸喂,你說(shuō)我怎么就攤上這事毙死。” “怎么了喻鳄?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵扼倘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我除呵,道長(zhǎng)再菊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任颜曾,我火速辦了婚禮纠拔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泛豪。我一直安慰自己稠诲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布诡曙。 她就那樣靜靜地躺著吕粹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岗仑。 梳的紋絲不亂的頭發(fā)上匹耕,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音荠雕,去河邊找鬼稳其。 笑死驶赏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的既鞠。 我是一名探鬼主播煤傍,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嘱蛋!你這毒婦竟也來(lái)了蚯姆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤洒敏,失蹤者是張志新(化名)和其女友劉穎龄恋,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凶伙,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡郭毕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了函荣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片显押。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖傻挂,靈堂內(nèi)的尸體忽然破棺而出乘碑,到底是詐尸還是另有隱情,我是刑警寧澤金拒,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布兽肤,位于F島的核電站,受9級(jí)特大地震影響殖蚕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沉迹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一睦疫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鞭呕,春花似錦蛤育、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至腋么,卻和暖如春咕娄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背珊擂。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工圣勒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留费变,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓圣贸,卻偏偏與公主長(zhǎng)得像挚歧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吁峻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350