在上一篇文章里我们分析了手势绘制解锁的整个流程,这次我们来分析一下PIN码解锁时所相关的内容
布局
在我们分析流程之前,我们先来看到KeyguardPINView这个类,他是用于显示解锁的 PIN 键盘,也就是绘制这个界面。在这个类里面有几个ViewGroup,每一个都代表了一排按键
让我们来看下这张图
从最上面输入密码的按键123开始总共有四行,所以我们在这里就有四个ViewGroup
KeyguardPINView
构造
首先我们看他的构造方法,在这里面定义了显示动画以及消失动画的初始化,并且初始化了监听器和一些基本参数
onFinishInflate
在这个方法里面,每个view都会进行初始化,通过findViewById找到自己对应的那个view。然后在类的成员变量中,有一个view的二维数组mViews,这里就是将1234567890以及确定退格打入至布局中。(还有一行紧急呼救)这里有一个重点,我们可以看到底部导航栏的这个回退按键,它也是在这个页面进行初始化的而不是我们平常使用的那一个底部导航栏,以及他的点击事件也是在这里进行实现。(但是修改了之后还是可以进行相同的处理,这里没有搞明白)
这个类大概就这两个方法需要注意,接着我们进行主要的部分
KeyguardPinBasedInputView
与之前那一个类一样,我们还有一个类是跟PIN解锁相关的,那就是KeyguardPinBasedInputView,在KeyguardPINView中,我们只看到了每一行的布局,而在KeyguardPinBasedInputView类中,代码加载了每一个按键的单独布局
@Override
protected void onFinishInflate() {
mPasswordEntry = findViewById(getPasswordTextViewId());
mPasswordEntry.setOnKeyListener(this);
// Set selected property on so the view can send accessibility events.
mPasswordEntry.setSelected(true);
mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() {
@Override
public void onUserActivity() {
onUserInput();
}
});
mOkButton = findViewById(R.id.key_enter);
if (mOkButton != null) {
mOkButton.setOnTouchListener(this);
mOkButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "mOkButton onClick");
if (mPasswordEntry.isEnabled()) {
Log.d(TAG, "mOkButton onClick verifyPasswordAndUnlock");
verifyPasswordAndUnlock();
}
}
});
mOkButton.setOnHoverListener(new LiftToActivateListener(getContext()));
}
mDeleteButton = findViewById(R.id.delete_button);
mDeleteButton.setVisibility(View.VISIBLE);
mDeleteButton.setOnTouchListener(this);
mDeleteButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// check for time-based lockouts
if (mPasswordEntry.isEnabled()) {
mPasswordEntry.deleteLastChar();
}
}
});
mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// check for time-based lockouts
if (mPasswordEntry.isEnabled()) {
resetPasswordText(true /* animate */, true /* announce */);
}
doHapticKeyClick();
return true;
}
});
mButton0 = findViewById(R.id.key0);
mButton1 = findViewById(R.id.key1);
mButton2 = findViewById(R.id.key2);
mButton3 = findViewById(R.id.key3);
mButton4 = findViewById(R.id.key4);
mButton5 = findViewById(R.id.key5);
mButton6 = findViewById(R.id.key6);
mButton7 = findViewById(R.id.key7);
mButton8 = findViewById(R.id.key8);
mButton9 = findViewById(R.id.key9);
mPasswordEntry.requestFocus();
super.onFinishInflate();
}
接下来我们看一看确认按键的事件
首先第一步,他先找到了输入的密码,然后设置监听器,设置选中。
接下来在给OKButton设置监听事件
那么我们可以看到最终触发的方法是verifyPasswordAndUnlock,这才是最重要的组成部分和实现,我们来看一看
protected void verifyPasswordAndUnlock() {
Log.d(TAG, "verifyPasswordAndUnlock mDismissing=" + mDismissing);
//首先验证是否正在解锁,如果是则直接返回
if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
//这里表示获取输入的密码凭证,接着就不再让输入密码了直至验证完成
final LockscreenCredential password = getEnteredCredential();
setPasswordEntryInputEnabled(false);
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
}
final int userId = KeyguardUpdateMonitor.getCurrentUser();
if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
//在这里验证密码之前先确认是否大于预设的最小密码长度,这是为了防止误触导致手机锁定
//默认长度为4
//设置可以继续输入密码,进行检查直接返回false,密码清空接下来返回
// to avoid accidental lockout, only count attempts that are long enough to be a
// real password. This may require some tweaking.
setPasswordEntryInputEnabled(true);
onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
password.zeroize();
return;
}
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
mKeyguardUpdateMonitor.setCredentialAttempted();
mPendingLockCheck = LockPatternChecker.checkCredential(
mLockPatternUtils,
password,
userId,
new LockPatternChecker.OnCheckCallback() {
//检查之前
@Override
public void onEarlyMatched() {
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL);
}
onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPassword */);
password.zeroize();
}
//进行检查
@Override
public void onChecked(boolean matched, int timeoutMs) {
Log.d(TAG, "verifyPasswordAndUnlock onChecked");
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
setPasswordEntryInputEnabled(true);
mPendingLockCheck = null;
if (!matched) {
onPasswordChecked(userId, false /* matched */, timeoutMs,
true /* isValidPassword */);
}
password.zeroize();
}
//如果检查前的回调取消了检测,则调用这个方法。但是与此同时仍然要记下延迟
@Override
public void onCancelled() {
Log.d(TAG, "verifyPasswordAndUnlock onCancelled");
// We already got dismissed with the early matched callback, so we cancelled
// the check. However, we still need to note down the latency.
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
password.zeroize();
}
});
}
与上方的OKButton一样接下来DeleteButton也有着同样的设置。并且我们来关注一下它里面的 deleteLastChar方法
public void deleteLastChar() {
int length = mText.length();
CharSequence textbefore = getTransformedText();
if (length > 0) {
mText = mText.substring(0, length - 1);
CharState charState = mTextChars.get(length - 1);
charState.startRemoveAnimation(0, 0);
sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
}
userActivity();
}
这里的流程其实就是
1.确认密码长度是否大于0,如果大于0则已经输入了密码就接着往下走。
2.将获取到的密码Text字符串分解成一个个的字符,接着将最后一个移除
3.发送事件重新在Text中设置密码