在Keyguard之滑動(dòng)解鎖流程一文中裆针,我們已經(jīng)分析過(guò)碍岔,不同的安全鎖類型是在KeyguardSecurityContainer中使用getSecurityView根據(jù)不同的securityMode inflate出來(lái)师逸,并添加到界面上的。那么本文我們就來(lái)以圖案鎖為例分析一下耕陷,安全鎖解鎖時(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)聽(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)使用把夸。