Android8.1 SystemUI 之圖案鎖驗證流程

Keyguard之滑動解鎖流程一文中,咱們已經分析過,不一樣的安全鎖類型是在KeyguardSecurityContainer中使用getSecurityView根據不一樣的securityMode inflate出來,並添加到界面上的。那麼本文咱們就來以圖案鎖爲例分析一下,安全鎖解鎖時的驗證流程吧。java

 

 

圖案解鎖的滑動事件處理

咱們知道,Pattern鎖所使用的layout是case Pattern: return R.layout.keyguard_pattern_view;android

<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,源碼位置是android/frameworks/base/core/java/com/android/internal/widget/LockPatternView.java,是一個系統公共控件,下面咱們就分析一下這個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對應幾個不一樣的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進行局部重繪。
    關於MotionEvent的歷史座標getHistoricalX,getHistoricalY的解釋能夠參考developer文檔-->MotionEventide

關於歷史座標
爲了效率,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<LockPatternView.Cell> pattern),這時候就開始圖案解鎖的驗證了。動畫

  • ACTION_CANCEL:重置pattern狀態,回調mOnPatternListener.onPatternCleared()google

關於ACTION_CANCEL的產生和事件回傳你必定要知道的事
在當前控件(子控件)收到前驅事件(ACTION_MOVE或者ACTION_MOVE)後,它的父控件忽然插手(interceptTouchEvent)截斷事件的傳遞,這時當前控件就會收到ACTION_CANCEL,收到此事件後,無論子控件此時返回true或者false,都認爲這一個動做已完成,不會再回傳到父控件的OnTouchEvent中處理,同時後續事件,會經過dispatchEvent方法直接傳送到父控件這裏來處理。
那麼有的朋友就要問了,不是說若是子控件的OnTouchEvent返回false,代表事件未被處理,是回傳到父控件去處理的嗎? 其實,只有ACTION_DOWN事件才能夠被回傳,ACTION_MOVE和ACTION_UP事件會跟隨ACTION_DOWN事件,即ACTION_DOWN是哪一個控件處理的,後續事件都傳遞到這裏,不會上拋到父控件,ACTION_CANCEL也不能回傳。
結論:ACTION_CANCEL事件是收到前驅事件後,後續事件被父控件攔截的狀況下產生,onTouchEvent的事件回傳到父控件只會發生在ACTION_DOWN事件中spa

圖案解鎖驗證

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();
            // 若是鏈接的點小於4個,做爲無效密碼
            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方法中構造了一個AsyncTask,並start()。
在onPreExecute()中複製了一份pattern,用ArrayList包裝
patternCopy = new ArrayList(pattern);

關於AsyncTask的cancel(boolean mayInterruptIfRunning) 方法
先說結論,單獨使用cancel不會像你想的那樣好好工做
若是你調用了AsyncTask的cancel(false),doInBackground()仍然會執行到方法結束,只是不會去調用onPostExecute()方法。可是實際上這是讓應用程序執行了沒有意義的操做。
那麼是否是咱們調用cancel(true)前面的問題就能解決呢?並不是如此。若是mayInterruptIfRunning設置爲true,會使任務儘早結束,可是若是的doInBackground()有不可打斷的方法會失效。
可見.cancel()是給AsyncTask設置一個"canceled"的狀態,那麼想要終止異步任務,就須要在異步任務當中結束。

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

在doInBackground()中LockPatternUtils登場了,圖案密碼的驗證,就是調用了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首先會把pattern使用patternToString算法轉換成字符串,以後調用checkCredential進行驗證
在LockPatternUtils中還有一個checkPassword方法,對應的是PIN碼/密碼,因此圖案鎖/密碼鎖/PIN碼鎖的驗證流程到這裏開始就一致了。

/**
     * 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,用於密碼驗證的方法是verifyPasswordAndUnlock()
其中,KeyguardSimPinView和KeyguardSimPukView重寫了該方法,因此有本身獨特的驗證方式。
而KeyguardPasswordView和KeyguardPINView都是使用LockPatternUtils的checkPassword進行密碼驗證

checkPassword和checkPassword都會去調用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對象,對本地數據進行封包,進行進程間通訊。到這裏咱們明白了,原來密碼的驗證,最終是在LockSettingService中進行的。

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;
    }

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

總結一下:在繪製密碼後手指擡起的時候,若是已存的有效點數達到4個及以上,就會使用LockPatternChecker.checkPattern方法啓動一個AsyncTask, 並在doInBackground中調用LockPatternUtils.checkPattern進行密碼驗證,此時pattern會被轉化成字符串形式,最終和密碼鎖PIN碼鎖同樣,都是遠程調用到LockPatternService的checkCredential接口進行驗證。在整個操做過程當中,mOnPatternListener被用於通知LockPatternView進行安全鎖提示內容和Pattern狀態的刷新。

checkCredential的具體算法邏輯以及LockPatternUtils和LockSettingsService中還有不少與安全鎖加鎖/解鎖/鎖狀態判斷相關的接口,後面咱們會單獨進行分析:)。

本文章已經獨家受權ApeClub公衆號使用。

相關文章
相關標籤/搜索