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

圖案解鎖的滑動(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)使用。