不積跬步無以至千里
本文講解前三種解鎖的實現,指紋由下一篇來講解。
主要需要有兩點
1、每次都有5次解鎖。
2、隨着嘗試次數的增加,時間增長。
3、關機重啓後仍然能計算時間。(指紋解鎖不需要)
前摘
修改這4種解鎖方式是:Pin碼解鎖、密碼解鎖、圖案解鎖和指紋解鎖,修改解鎖失敗的等待時間?做過SystemUI相關模塊都知道解鎖這一塊的驗證密碼等等操作一般是通過JNI通過Native做的驗證操作,數據獲取一般也是涉及到C的邏輯。由於考慮其實系統其他的界面功能上也會有解鎖的這幾種方式,例如:Settings裏面的設置解鎖方式那裏,因此修改鎖屏上的解鎖等待時間,要修改到共有接口,因此這個功能的添加,並不是這麼簡單的去修改SystemUI的,但是在網上搜索相關修改等待時間的blog,並沒有,因此在這把修改的步驟記錄下來。
一、 定位修改的起始點
首先定位需要修改的地方,通過monitor工具,獲取如下
就可以獲取某個控件的id,然後鎖定layout佈局了,然後就可以找到相關的代碼了。
二、分析整體的修改邏輯
以此類推,因此我們發現要修改的這幾種解鎖方式主要分爲兩大類,兩大類爲:1、PIN碼、密碼和圖案 2、指紋解鎖
分爲兩大類的主要原因是因爲指紋解鎖的主要邏輯並不在Keyguard裏面而是SystemUI裏面,因爲在7.0的代碼上Keyguard和SystemUI是分開的,8.0則合到了一起。
三、具體修改的實現
修改PIN碼、密碼和圖案解鎖
通過上邊的工具定位代碼得知,相關類如下:
如圖,其實相關類爲KeyguardPINView、KeyguardPassworkView和KeyguardPatternView
這幾個就是PIN碼、密碼和圖案對應的自定義View,而其中的點擊並操作的邏輯也是在這裏面,感覺根據MVC的設計原則,感覺有點不符合這點啊哈哈哈。。。結果View和Model放到一起了。
其實爲什麼PIN碼和密碼放到一起呢?因爲他們有個共同的父類KeyguardAbsKeyInputView.java,關於解鎖邏輯的顯示控制也在父類裏,因此PIN碼和密碼解鎖在這個父類裏做操作就可以了。
其中代碼如下:
mPendingLockCheck = LockPatternChecker.checkPassword(
mLockPatternUtils,
entry,
userId,
new LockPatternChecker.OnCheckCallback() {
@Override
public void onEarlyMatched() {
android.util.Log.i("wangdy","KeyguardAbs.onEarlyMatfched");
onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPassword */);
}
@Override
public void onChecked(boolean matched, int timeoutMs) {
android.util.Log.i("wangdy","KeyguardAbs.onChecked.matched:"+matched+",timeoutMs:"+timeoutMs);
setPasswordEntryInputEnabled(true);
mPendingLockCheck = null;
if (!matched) {
onPasswordChecked(userId, false /* matched */, timeoutMs,
true /* isValidPassword */);
}
}
});
}
其實關於鎖屏PIN碼、密碼和圖案解鎖都是依靠這個LockPatternChecker.checkPassword方法去驗證密碼,然後利用LockPatternChecker.OnCheckCallback這個回調來返回驗證結果,這個驗證結果的回調也是通過JNI一系列步驟完成的。
先看其中的兩個回調方法
1、onChecked 這個回調方法是當驗證失敗的時候纔會回調,這裏會返回matched,這裏會返回false,timeoutMs這裏就是時間間隔。
2、onEarlyMatched 這個回調方法是當驗證成功的時候纔會回調。
發現問題:
其實剛開始做這個功能的時候並沒有考慮做全局的,就想在這裏做一個時間的攔截,然後自己把這個時間間隔改了,就能完成需求,其實後來測試並不行,所以這裏就牽扯出了發現的幾個問題。
經測試發現,如下問題:
1、系統默認返回都是30×1000
2、默認鎖屏第三次嘗試解鎖時候,並不是5次。
3、就算你強制能讓第三次嘗試的還能輸入密碼嘗試解鎖,但是系統下邊是鎖定的,並不能進行正常的解鎖嘗試(即輸入的正常密碼,並不能解鎖成功,返回仍然是失敗)
因此,根據需求要做成全局的,就找到根源把這個返回時間修改成自己想要的這樣,所有實現這個回調的應用都能獲取到新制定規則的時間(這裏是指Settings(其實每個應用)那邊其實也是實現這邊的回調接口,然後系統返回這個時間都能接受到最新的時間規則)。
這樣就一直追,追到了這個類gatekeeper.cpp。(其實做系統開發,修改或者添加功能,就是找到一個突破口,一直追追)其實真正的返回時間的是在這裏寫好的。如下:
/system/gatekeeper/gatekeeper.cpp 的ComputeRetryTimeout函數
原生代碼:
/*
* Calculates the timeout in milliseconds as a function of the failure
* counter 'x' as follows:
*
* [0. 5) -> 0
* 5 -> 30
* [6, 10) -> 0
* [11, 30) -> 30
* [30, 140) -> 30 * (2^((x - 30)/10))
* [140, inf) -> 1 day
*
*/
uint32_t GateKeeper::ComputeRetryTimeout(const failure_record_t *record) {
static const int failure_timeout_ms = 30000;
if (record->failure_counter == 0) return 0;
if (record->failure_counter > 0 && record->failure_counter <= 10) {
if (record->failure_counter % 5 == 0) {
return failure_timeout_ms;
} else {
return 0;
}
} else if (record->failure_counter < 30) {
return failure_timeout_ms;
} else if (record->failure_counter < 140) {
return failure_timeout_ms << ((record->failure_counter - 30) / 10);
}
return DAY_IN_MS;
}
根據上邊可以看出,30000這個數字很敏感嘛,系統一直返回的就是這個30000ms,終於找到了,是一個固定值。其實細看這個方法可以看出來,它是通過記錄失敗的次數然後進行了判斷,返回時間間隔。
總結上邊代碼:
1、當失敗0次,返回0ms
2、當失敗大於0,失敗小於10次,就是當前兩次時,每次失敗5次後,返回30000ms,意思是這總前兩次都可以嘗試5次機會
3、當失敗次數小於30次,就是當總次數是10次到30次時,返回30000ms,意思是第二次以後爲每次就只有一次嘗試機會解鎖,就要等待時間。直到總次數爲30次時。
4、當總次數大於30次到140次,就是需要左移運算出毫秒數。
這裏有兩種次數,一種是每次解鎖都有5次嘗試機會(但是隻有前兩次),後邊每次都要等待時間,這裏就是前邊我們遇到的問題。
因此修改這個方法的規則,解決問題
修改後代碼:
uint32_t GateKeeper::ComputeRetryTimeout(const failure_record_t *record) {
static const int failure_timeout_ms = 60*1000;
if (record->failure_counter == 0) return 0;
if (record->failure_counter > 0) {
if (record->failure_counter % 5 == 0) {
if(record->failure_counter/5 == 1){
return 2*failure_timeout_ms;
}else if(record->failure_counter/5 == 2){
return 5*failure_timeout_ms;
}else if(record->failure_counter/5 == 3){
return 15*failure_timeout_ms;
}else if(record->failure_counter/5 == 4){
return 30*failure_timeout_ms;
}else if(record->failure_counter/5 == 5){
return 60*failure_timeout_ms;
}else if(record->failure_counter/5 >= 6){
return 120*failure_timeout_ms;
}
} else {
return 0;
}
}
return 2*failure_timeout_ms;
}
這樣,我們就修改了它原生的規則。實現時間的動態改變,隨着嘗試次數的增加。
寫到這已經滿足了需求上的前兩條需求了,現在就要滿足實現第三條關機重啓仍然能計算時間。
根據邏輯如下代碼:
private void onPasswordChecked(int userId, boolean matched, int timeoutMs,
boolean isValidPassword) {
boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
if (matched) {
mCallback.reportUnlockAttempt(userId, true, 0);
if (dismissKeyguard) {
mDismissing = true;
mCallback.dismiss(true);
}
} else {
if (isValidPassword) {
mCallback.reportUnlockAttempt(userId, false, timeoutMs);
if (timeoutMs > 0) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
userId, timeoutMs);
// long deadline = //mLockPatternUtils.setLockoutAttemptDeadlineBySystemCurrentTime(
// userId, timeoutMs);
android.util.Log.i("wangdy","KeyguardAbs.onPasswordChecked.timeoutMs:"+timeoutMs+",deadline:"+deadline);
handleAttemptLockout(deadline);
}
}
其實這裏就是上邊說的回調接口調用的方法,來處理驗證密碼失敗或者成功的邏輯,其中上邊邏輯處理成功就是matched爲true的時候,失敗爲false的時候,因此看false這邊通過這句代碼
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
userId, timeoutMs);
設置了時間,然後返回一個終止時間(就是計時結束的時間戳),然後去實現倒計時處理的。
來看一下,這個方法是怎麼記錄時間戳的?
/frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java
/**
* Set and store the lockout deadline, meaning the user can't attempt his/her unlock
* pattern until the deadline has passed.
* @return the chosen deadline.
*/
public long setLockoutAttemptDeadline(int userId, int timeoutMs) {
final long deadline = SystemClock.elapsedRealtime() + timeoutMs;
setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline, userId);
setLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, timeoutMs, userId);
return deadline;
}
由代碼得知,其實參數是時間間隔,然後通過SystemClock.elapsedRealtime()這個方法獲取開機時間加上時間間隔,然後得到計時結束的時間戳存儲起來。
其實每次亮屏後會出現倒計時的界面是重新獲取了計時結束的時間戳,如下代碼:
@Override
public void onResume(int reason) {
reset();
}
@Override
public void reset() {
// start fresh
mDismissing = false;
resetPasswordText(false /* animate */, false /* announce */);
// if the user is currently locked out, enforce it.
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
KeyguardUpdateMonitor.getCurrentUser());
if (shouldLockout(deadline)) {
handleAttemptLockout(deadline);
} else {
resetState();
}
}
其中的mLockPatternUtils.getLockoutAttemptDeadlineBySystemCurrentTime(KeyguardUpdateMonitor.getCurrentUser())方法就是獲取的計時結束時間戳,通過判斷是否時間戳是否失效,設置界面倒計時是否顯示,所以再看一下獲取的計時結束時間戳的方法如下
/**
* @return The elapsed time in millis in the future when the user is allowed to
* attempt to enter his/her lock pattern, or 0 if the user is welcome to
* enter a pattern.
*/
public long getLockoutAttemptDeadline(int userId) {
long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L, userId);
final long timeoutMs = getLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, 0L, userId);
final long now = SystemClock.elapsedRealtime();
if (deadline < now && deadline != 0) {
// timeout expired
setLong(LOCKOUT_ATTEMPT_DEADLINE, 0, userId);
setLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, 0, userId);
return 0L;
}
if (deadline > (now + timeoutMs)) {
// device was rebooted, set new deadline
deadline = now + timeoutMs;
setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline, userId);
}
return deadline;
}
通過獲取存儲的計時結束時間戳,然後對比現在的時間,因爲SystemClock.elapsedRealtime()獲取的是開機到現在的時間,所以只要關機重啓,就會走下邊的判斷會根據最新的時間加上時間間隔,存儲起來作爲開機後的計時結束時間戳。因此就看到開後會重新等待30秒的現象,因此我們在這裏想要實現效果修改邏輯。
添加代碼接口,一個set,一個get,如下代碼:
/**
* Set and store the lockout deadline by SystemCurrentTime, meaning the user can't attempt his/her unlock
* pattern until the deadline has passed.
* @return the chosen deadline.
*/
public long setLockoutAttemptDeadlineBySystemCurrentTime(int userId, int timeoutMs) {
final long deadline = System.currentTimeMillis() + timeoutMs;
setLong(LOCKOUT_ATTEMPT_DEADLINE_CURRENTTIME, deadline, userId);
setLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, timeoutMs, userId);
return deadline;
}
/**
* @return The SystemCurrentTime in millis in the future when the user is allowed to
* attempt to enter his/her lock pattern, or 0 if the user is welcome to
* enter a pattern.
*/
public long getLockoutAttemptDeadlineBySystemCurrentTime(int userId) {
long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE_CURRENTTIME, 0L, userId);
final long now = System.currentTimeMillis();
if (deadline < now && deadline != 0) {
// timeout expired
setLong(LOCKOUT_ATTEMPT_DEADLINE_CURRENTTIME, 0, userId);
setLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, 0, userId);
return 0L;
}
return deadline;
}
這樣把對應的方法set和get方法替換了(KeyguardAbsKeyInputView.java這是PIN碼和密碼解鎖,KeyguardPatternView.java這裏是圖案的),就可以了。這樣就滿足了PIN碼、密碼和圖案解鎖的需求。
由於指紋解鎖和前三種解鎖流程不一樣,下一篇再寫。Over