Android10.0 锁屏分析——KeyguardPatter
学习笔记:
首先一起看看下面两张图:
![](https://img.haomeiwen.com/i26463879/6859fb1c1a903b10.png)
![](https://img.haomeiwen.com/i26463879/ab6452b27a866861.png)
通过前面锁屏加载流程可以知道在KeyguardSecurityContainer中使用getSecurityView()根据不同的securityMode inflate出来,并添加到界面上的。
我们知道,Pattern锁所使用的layout是 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>
那么图案解锁的滑动事件处理,就是在LockPatternView,是一个系统公共控件,下面我们就分析一下这个view是如何处理触摸输入的:
// frameworks/base/core/java/com/android/internal/widget/LockPatternView.java
@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();
}
if (PROFILE_DRAWING) {
if (mDrawingProfilingStarted) {
Debug.stopMethodTracing();
mDrawingProfilingStarted = false;
}
}
return true;
}
return false;
}
几种事件类型:
![](https://img.haomeiwen.com/i26463879/fb279933e0529391.png)
不同的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进行局部重绘。
关于历史坐标
为了效率,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()
图案解锁验证
// src/com/android/keyguard/KeyguardPatternView.java
private class UnlockPatternListener implements LockPatternView.OnPatternListener {
...
@Override
public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
if (DEBUG) Log.d(TAG, "onPatternDetected");
mKeyguardUpdateMonitor.setCredentialAttempted();
mLockPatternView.disableInput();
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
}
final int userId = KeyguardUpdateMonitor.getCurrentUser();
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
mLockPatternView.enableInput();
onPatternChecked(userId, false, 0, false /* not valid - too short */);
return;
}
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
mPendingLockCheck = LockPatternChecker.checkCredential(
mLockPatternUtils,
LockscreenCredential.createPattern(pattern), // 这里跟进去,会发现将 pattern转化成了 byte[]
userId,
new LockPatternChecker.OnCheckCallback() {
@Override
public void onEarlyMatched() {
if (DEBUG) Log.d(TAG, "onEarlyMatched");
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL);
}
onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPattern */);
}
@Override
public void onChecked(boolean matched, int timeoutMs) {
if (DEBUG) Log.d(TAG, "onChecked matched:" + matched);
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
mLockPatternView.enableInput();
mPendingLockCheck = null;
if (!matched) {
onPatternChecked(userId, false /* matched */, timeoutMs,
true /* isValidPattern */);
}
}
@Override
public void onCancelled() {
if (DEBUG) Log.d(TAG, "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);
}
}
});
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
mCallback.userActivity();
mCallback.onUserInput();
}
}
...
}
在绘制密码后手指抬起的时候,如果已存的有效点数达到4个及以上,就会使用LockPatternChecker.checkCredential 方法调用 task.execute() 启动一个AsyncTask, 并在doInBackground中调用LockPatternUtils.checkCredential 进行密码验证,此时pattern会被转化成字节形式(LockscreenCredential.createPattern(pattern) 这里跟进去,会发现将 pattern转化成了 byte[])
// LockPatternUtils.java
public static byte[] patternToByteArray(List<LockPatternView.Cell> pattern) {
if (pattern == null) {
return new byte[0];
}
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 res;
}
最终和密码锁PIN码锁一样,都是远程调用到LockSettingsService 的 checkCredential 接口进行验证。
Keyguard接收用户输入的密码会通过Binder到framework层的LockSettingsService,LockSettingsService经过一系列调用会通过getGateKeeperService获取GateKeeperService然后调用verifyChallenge方法将密码继续忘底层传递,framework的调用栈如下:
![](https://img.haomeiwen.com/i26463879/34b0268e71e11d6a.png)
验证流程:
// LockSettingsService.java
private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
@ChallengeType int challengeType, long challenge, int userId,
ICheckCredentialProgressCallback progressCallback,
@Nullable ArrayList<PendingResetLockout> resetLockouts) {
// 省略部分代码......
if (credential == null || credential.isNone()) {
throw new IllegalArgumentException("Credential can't be null or empty");
}
if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
return VerifyCredentialResponse.ERROR;
}
// response是验证响应,spBasedDoVerifyCredential发起验证,返回 response 响应
VerifyCredentialResponse response = null;
response = spBasedDoVerifyCredential(credential, challengeType, challenge,
userId, progressCallback, resetLockouts);
// The user employs synthetic password based credential.
if (response != null) {
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
sendCredentialsOnUnlockIfRequired(credential, userId);
}
return response;
}
// 省略部分代码......
return response;
}
这里先看verifyChallenge方法返回有三个状态,也就是response.getResponseCode():
//密码匹配失败
public static final int RESPONSE_ERROR = -1;
//密码匹配成功
public static final int RESPONSE_OK = 0;
//重试
public static final int RESPONSE_RETRY = 1;
接下来我们看spBasedDoVerifyCredential()方法做基于 sp 的做验证凭证:
// LockSettingsService.java
private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
@ChallengeType int challengeType, long challenge,
int userId, ICheckCredentialProgressCallback progressCallback,
@Nullable ArrayList<PendingResetLockout> resetLockouts) {
// 判断是否具有生物识别
final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);
// 省略部分代码......
final AuthenticationResult authResult;
VerifyCredentialResponse response;
synchronized (mSpManager) {
if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
return null;
}
if (userId == USER_FRP) {
return mSpManager.verifyFrpCredential(getGateKeeperService(),
userCredential, progressCallback);
}
long handle = getSyntheticPasswordHandleLocked(userId);
// 解开基于密码的合成密码
authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
getGateKeeperService(), handle, userCredential, userId, progressCallback);
response = authResult.gkResponse;
// 省略部分代码......
}
// 省略部分代码......
return response;
}
在这方法里首先进行了判断是不是生物解锁,然后在调用合成密码管理器SyntheticPasswordManager里的unwrapPasswordBasedSyntheticPassword()方法进行解密,并将锁屏密码往下传。
// SyntheticPasswordManager.java
public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
long handle, @NonNull LockscreenCredential credential, int userId,
ICheckCredentialProgressCallback progressCallback) {
// 省略部分代码......
else {
byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
GateKeeperResponse response;
try {
Log.d("yexiao","yexiao:",new Throwable());
response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
pwd.passwordHandle, gkPwdToken);
} catch (RemoteException e) {
Slog.e(TAG, "gatekeeper verify failed", e);
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
// 省略部分代码......
}
// 省略部分代码......
return result;
}
unwrapPasswordBasedSyntheticPassword中的gatekeeper是LockSettingsService的getGateKeeperService方法获取的IGateKeeperService Binder代理端:
// LockSettingsService.java
protected synchronized IGateKeeperService getGateKeeperService() {
if (mGateKeeperService != null) {
return mGateKeeperService;
}
final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
if (service != null) {
try {
service.linkToDeath(new GateKeeperDiedRecipient(), 0);
} catch (RemoteException e) {
Slog.w(TAG, " Unable to register death recipient", e);
}
mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
return mGateKeeperService;
}
Slog.e(TAG, "Unable to acquire GateKeeperService");
return null;
}
这里有个问题,我们发现IGateKeeperService的Binder实现端找不到,而且在Framework层也找不到在那里注册的service,为何能getService?
其实IGateKeeperService这个AIDL文件的具体实现类不像传统的Framework Binder服务,它的实现端在native层,我们前面说了GateKeeper的架构,提到GateKeeper是一种C++的Binder服务,与java层接口相对应
我们就先来来看看GateKeeper server端,目录system/core/gatekeeperd下的gatekeeperd.cpp类
// gatekeeperd.cpp
int main(int argc, char* argv[]) {
ALOGI("Starting gatekeeperd...");
if (argc < 2) {
ALOGE("A directory must be specified!");
return 1;
}
if (chdir(argv[1]) == -1) {
ALOGE("chdir: %s: %s", argv[1], strerror(errno));
return 1;
}
android::sp<android::IServiceManager> sm = android::defaultServiceManager();
android::sp<android::GateKeeperProxy> proxy = new android::GateKeeperProxy();
android::status_t ret = sm->addService(
android::String16("android.service.gatekeeper.IGateKeeperService"), proxy);
if (ret != android::OK) {
ALOGE("Couldn't register binder service!");
return -1;
}
/*
* We're the only thread in existence, so we're just going to process
* Binder transaction as a single-threaded program.
*/
android::IPCThreadState::self()->joinThreadPool();
return 0;
}
在main函数中,首先获取BpSeviceManager,然后创建GateKeeperProxy类,在调用addService函数将GateKeeperProxy注册到SeviceManager,名称为"android.service.gatekeeper.IGateKeeperService",前面我们在Framework层通过getService(Context.GATEKEEPER_SERVICE)获取的gatekeeper服务其实获取的就是这个服务,Context中定义的服务名称也是一样的。
至于native层与Java层如何相互调用关联的,这里就省略了。
前面解密过程调的verifyChallenge方法调到了gatekeeperd.cpp中的GateKeeperProxy类的同名verifyChallenge函数,但我们又发现这两个verifyChallenge方法参数并不一致,这无所谓的,Binder调用并不需要client端和server端参数一致,调用方法的匹配是通过Binder code来决定的。
到这里,上层的锁屏密码就已经传递到了natice层,还记得前面说的gatekeeper架构吗,native层过了之后就该通过HIDL忘HAL层发送密码了,来看看GateKeeperProxy中的verifyChallenge具体实现:
// gatekeeperd.cpp
Status verifyChallenge(int32_t uid, int64_t challenge,
const std::vector<uint8_t>& enrolledPasswordHandle,
const std::vector<uint8_t>& providedPassword,
GKResponse* gkResponse) override {
//省略掉一些权限相关检查 ......
//省略一些数据类型转换.....
Return<void> hwRes = hw_device->verify(
hw_uid, challenge, curPwdHandle, enteredPwd,
[&gkResponse](const GatekeeperResponse& rsp) {
if (rsp.code >= GatekeeperStatusCode::STATUS_OK) {
ALOGD("gatekeeperd verify process ok");
*gkResponse = GKResponse::ok(
{rsp.data.begin(), rsp.data.end()},
rsp.code == GatekeeperStatusCode::STATUS_REENROLL /* reenroll */);
} else if (rsp.code == GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) {
ALOGD("gatekeeperd verify process retry timeout");
*gkResponse = GKResponse::retry(rsp.timeout);
} else {
ALOGD("gatekeeperd verify fail,maybe not match");
*gkResponse = GKResponse::error();
}
});
if (!hwRes.isOk()) {
LOG(ERROR) << "verify transaction failed";
return GK_ERROR;
}
// 省略部分代码......
return Status::ok();
}
到这里基本整个解锁流程结束了,native层也不在继续深入分析了。
在整个操作过程中,mOnPatternListener 被用于通知LockPatternView进行安全锁提示内容和Pattern状态的刷新。
我们可以对整个密码匹配的流程进行总结了:
- 上层Keyguard接收用户的密码输入。
- 收到密码后通过Binder将密码传递到LockSettingsService。
- 在LockSettingsService中获取到实现在native层的GateKeeperService,调用其verifyChallenge函数。
- verifyChallenge中调用HIDL服务IGatekeeper的verify函数继续向HAL中发送密码。
- IGatekeeper中获取名为GATEKEEPER_HARDWARE_MODULE_ID的HAL模块,并打开其下的device,调用device的verify函数在TEE硬件中进最终密码匹配。
参考:Android P keyguard 初始化,Pattern解锁等介绍 、SystemUI 之图案锁验证流程、AndroidQ 锁屏密码验证解析