概述
今天测试同事报了一个问题,概率性的,不管在什么界面玩手机,玩着玩着,就可能突然跳出来keyguard给把手机锁屏了,拿到手机看了下,测试机没有设置pin/password/pattern,首先猜测是某个三方应用将锁屏禁用掉了,
google提供了禁用锁屏的API,但是google已经不建议用了,未来或许会删除
KeyguardManager keyguardManager = (KeyguardManager)this.getSystemService(Activity.KEYGUARD_SERVICE);
final KeyguardManager.KeyguardLock lock = keyguardManager.newKeyguardLock(KEYGUARD_SERVICE);
lock.disableKeyguard();
验证
既然怀疑是三方应用禁掉的锁屏,那么接着通过dump命令验证,
KeyguardViewMediator中mExternallyEnabled值代表是否有外部禁用keyguard
adb shell dumpsys activity service com.android.systemui|grep -i --color "mExternallyEnabled"
果然mExternallyEnabled为false,可以肯定是外部某个应用将锁屏禁掉了,但是Android Q之前的项目重来没发现过这个问题,看来是Android Q的bug,拿到google参考机进行验证,自己写一个禁用锁屏的APP,安装到参考机,灭屏确认锁屏被禁掉,然后杀掉此应用,果然手机被立马锁上了,在反向验证Android P手机,没有这个问题,应该是Android Q google某一笔patch导致,先说结论再分析代码,问题patch如下:
commit 1c8e3c0bd314b29cca8b92655c87d847da266683
Author: Adrian Roos <roosa@google.com>
Date: Tue Nov 20 20:07:55 2018 +0100
KeyguardDisableHandler: make properly user aware
Also fixes an issue where the disable handler was not
properly updated after adding a secure lockscreen.
Also fixes an issue where the disable handler was not
properly updated after the device policy changes for
users other than USER_SYSTEM.
Also prevents adding new privileged usages of the API.
Also removes a workaround that prevented Keyguard from
re-engaging if it timed out while the it was disabled.
The workaround is no longer necessary because the in-call
UI is now using the SHOW_WHEN_LOCKED API instead of disabling
the keyguard.
Change-Id: Ib2644252b3806de4cb09f03991d789b83e924a11
Fixes: 119322269
Test: atest KeyguardDisableHandlerTest CtsActivityManagerDeviceTestCases:KeyguardTests
当mExternallyEnabled为false即三方应用将锁屏禁掉后会将mNeedToReshowWhenReenabled赋值为true,代表立即重置锁屏
private void doKeyguardLocked(Bundle options) {
......
if (!mExternallyEnabled) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
mNeedToReshowWhenReenabled = true;
return;
}
......
}
重置锁屏的代码在这个方法中,setKeyguardEnabled这个方法会调用则是因为注册了binder死亡回调,此死亡回调最终会调到SystemUI的这个方法然后满足条件就立即锁屏
public void setKeyguardEnabled(boolean enabled) {
......
mExternallyEnabled = enabled;
if(!enabled && mShowing){
...
}else if (enabled && mNeedToReshowWhenReenabled) {
...
showLocked();
}
......
}
disableKeyguard
接着进行源码分析,从google提供的API disableKeyguard开始,通过binder调到WMS的disableKeyguard方法
/frameworks/base/core/java/android/app/KeyguardManager.java
public void disableKeyguard() {
try {
//mToken代表对应的应用,mTag是创建KeyguardManager是
//传的”keyguard“
mWM.disableKeyguard(mToken, mTag, mContext.getUserId());
} catch (RemoteException ex) {
}
}
WMS.disableKeyguard
/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public void disableKeyguard(IBinder token, String tag, int userId) {
......
try {
mKeyguardDisableHandler.disableKeyguard(token, tag, callingUid, userId);
} finally {
Binder.restoreCallingIdentity(origIdentity);
}
}
mKeyguardDisableHandler.disableKeyguard
/frameworks/base/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
void disableKeyguard(IBinder token, String tag, int callingUid, int userId) {
UserTokenWatcher watcherForCaller = watcherForCallingUid(token, callingUid);
watcherForCaller.acquire(token, tag, mInjector.getProfileParentId(userId));
}
acquire
mWatchers的容量为1,这里面只会有一个禁用了锁屏的应用,接着调用InnerTokenWatcher的acquire方法
/frameworks/base/services/core/java/com/android/server/utils/UserTokenWatcher.java
private final SparseArray<TokenWatcher> mWatchers = new SparseArray<>(1);
public void acquire(IBinder token, String tag, @UserIdInt int userId) {
synchronized (mWatchers) {
TokenWatcher watcher = mWatchers.get(userId);
if (watcher == null) {
watcher = new InnerTokenWatcher(userId, mHandler, mTag);
mWatchers.put(userId, watcher);
}
watcher.acquire(token, tag);
}
}
InnerTokenWatcher.acquire
InnerTokenWatcher继承TokenWatcher,要注意acquire和acquired这是两个方法,InnerTokenWatcher重写了acquired但是没重写acquire,所以看父类TokenWatcher的acquire方法
private final class InnerTokenWatcher extends TokenWatcher {
private final int mUserId;
private InnerTokenWatcher(int userId, Handler handler, String tag) {
super(handler, tag);
this.mUserId = userId;
}
@Override
public void acquired() {
// We MUST NOT hold any locks while invoking the callbacks.
mCallback.acquired(mUserId);
}
@Override
public void released() {
// We MUST NOT hold any locks while invoking the callbacks.
mCallback.released(mUserId);
synchronized (mWatchers) {
final TokenWatcher watcher = mWatchers.get(mUserId);
if (watcher != null && !watcher.isAcquired()) {
mWatchers.remove(mUserId);
}
}
}
}
TokenWatcher.acquire
为什么需要两次判断,因为对于SystemUI来说不管有多少个应用调了disableKeyguard,其实都只需要一次禁用就行了,而不同应用调了disableKeyguard虽然不会继续走下去但还是需要注册死亡回调的
/frameworks/base/core/java/android/os/TokenWatcher.java
public void acquire(IBinder token, String tag)
{
synchronized (mTokens) {
//保证同一个应用disableKeyguard流程只调用一次
if (mTokens.containsKey(token)) {
return;
}
int oldSize = mTokens.size();
Death d = new Death(token, tag);
try {
//注册binder死亡回调
token.linkToDeath(d, 0);
} catch (RemoteException e) {
return;
}
mTokens.put(token, d);
//再次保证只能有一个应用调用disableKeyguard流程
if (oldSize == 0 && !mAcquired) {
sendNotificationLocked(true);
mAcquired = true;
}
}
}
linkToDeath
binder死亡回调了调用了cleanup方法,后面再说
private class Death implements IBinder.DeathRecipient
{
IBinder token;
String tag;
Death(IBinder token, String tag)
{
this.token = token;
this.tag = tag;
}
public void binderDied()
{
cleanup(token, false);
}
protected void finalize() throws Throwable
{
try {
if (token != null) {
Log.w(mTag, "cleaning up leaked reference: " + tag);
release(token);
}
}
finally {
super.finalize();
}
}
}
sendNotificationLocked
此方法里主要是通过handler post了一个runnable
private void sendNotificationLocked(boolean on)
{
int value = on ? 1 : 0;
//保证mNotificationTask只执行一次,注意这里on为true,value为1
if (mNotificationQueue == -1) {
// empty
mNotificationQueue = value;
mHandler.post(mNotificationTask);
}
else if (mNotificationQueue != value) {
// it's a pair, so cancel it
mNotificationQueue = -1;
mHandler.removeCallbacks(mNotificationTask);
}
// else, same so do nothing -- maybe we should warn?
}
private Runnable mNotificationTask = new Runnable() {
public void run()
{
int value;
synchronized (mTokens) {
value = mNotificationQueue;
mNotificationQueue = -1;
}
//value为1
if (value == 1) {
acquired();
}
else if (value == 0) {
released();
}
}
};
InnerTokenWatcher.acquired
前面说过子类实现了acquired方法
@Override
public void acquired() {
// We MUST NOT hold any locks while invoking the callbacks.
mCallback.acquired(mUserId);
}
UserTokenWatcher的Callback
public interface Callback {
/**
* Reports that the first token has been acquired for the given user.
*/
void acquired(@UserIdInt int userId);
/**
* Reports that the last token has been release for the given user.
*/
void released(@UserIdInt int userId);
}
mCallback由KeyguardDisableHandler实现
我们看KeyguardDisableHandler实现的Callback中acquired和released都是调用的updateKeyguardEnabled方法,并且这两个方法并没有传boolean值,那怎么知道是禁用锁屏还是恢复锁屏,接着看后面代码
/frameworks/base/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
// Callback happens on mHandler thread.
private final UserTokenWatcher.Callback mCallback = new UserTokenWatcher.Callback() {
@Override
public void acquired(int userId) {
updateKeyguardEnabled(userId);
}
@Override
public void released(int userId) {
updateKeyguardEnabled(userId);
}
};
updateKeyguardEnabled
void updateKeyguardEnabled(int userId) {
synchronized (this) {
updateKeyguardEnabledLocked(userId);
}
}
updateKeyguardEnabledLocked
private void updateKeyguardEnabledLocked(int userId) {
if (mCurrentUser == userId || userId == UserHandle.USER_ALL) {
mInjector.enableKeyguard(shouldKeyguardBeEnabled(mCurrentUser));
}
}
先看下shouldKeyguardBeEnabled
shouldKeyguardBeEnabled
这个方法的返回值就是disableKeyguard还是enableKeyguard的值,主要根据UserTokenWatcher.isAcquired方法来的,最终返回的是TokenWatcher的mAcquired的值取反
private boolean shouldKeyguardBeEnabled(int userId) {
final boolean dpmRequiresPassword = mInjector.dpmRequiresPassword(mCurrentUser);
final boolean keyguardSecure = mInjector.isKeyguardSecure(mCurrentUser);
final boolean allowedFromApps = !dpmRequiresPassword && !keyguardSecure;
// The system can disable the keyguard for lock task mode even if the keyguard is secure,
// because it enforces its own security guarantees.
final boolean allowedFromSystem = !dpmRequiresPassword;
final boolean shouldBeDisabled = allowedFromApps && mAppTokenWatcher.isAcquired(userId)
|| allowedFromSystem && mSystemTokenWatcher.isAcquired(userId);
return !shouldBeDisabled;
}
TokenWatcher.acquire
在TokenWatcher的acquire将mAcquired赋值为true,在TokenWatcher的release中调用cleanup将mAcquired赋值为false
public void acquire(IBinder token, String tag)
{
synchronized (mTokens) {
if (mTokens.containsKey(token)) {
return;
}
// explicitly checked to avoid bogus sendNotification calls because
// of the WeakHashMap and the GC
int oldSize = mTokens.size();
Death d = new Death(token, tag);
try {
token.linkToDeath(d, 0);
} catch (RemoteException e) {
return;
}
mTokens.put(token, d);
if (oldSize == 0 && !mAcquired) {
sendNotificationLocked(true);
mAcquired = true;
}
}
}
public void release(IBinder token)
{
cleanup(token, true);
}
public void cleanup(IBinder token, boolean unlink)
{
synchronized (mTokens) {
Death d = mTokens.remove(token);
if (unlink && d != null) {
d.token.unlinkToDeath(d, 0);
d.token = null;
}
if (mTokens.size() == 0 && mAcquired) {
sendNotificationLocked(false);
mAcquired = false;
}
}
}
mInjector
Injector是定义在KeyguardDisableHandler内部的接口
interface Injector {
boolean dpmRequiresPassword(int userId);
boolean isKeyguardSecure(int userId);
int getProfileParentId(int userId);
void enableKeyguard(boolean enabled);
}
实现在创建KeyguardDisableHandler的时候
static KeyguardDisableHandler create(Context context, WindowManagerPolicy policy,
Handler handler) {
final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
return new KeyguardDisableHandler(new Injector() {
......
@Override
public void enableKeyguard(boolean enabled) {
policy.enableKeyguard(enabled);
}
}, handler);
}
policy.enableKeyguard
PhoneWindowManager实现了WindowManagerPolicy接口,实现了enableKeyguard方法
/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
@Override
public void enableKeyguard(boolean enabled) {
if (mKeyguardDelegate != null) {
mKeyguardDelegate.setKeyguardEnabled(enabled);
}
}
setKeyguardEnabled
/frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
public void setKeyguardEnabled(boolean enabled) {
if (mKeyguardService != null) {
mKeyguardService.setKeyguardEnabled(enabled);
}
mKeyguardState.enabled = enabled;
}
setKeyguardEnabled
通过binder调用到SystemUI中KeyguardViewMediator.setKeyguardEnabled方法
/frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
public void setKeyguardEnabled(boolean enabled) {
checkPermission();
mKeyguardViewMediator.setKeyguardEnabled(enabled);
}
setKeyguardEnabled
之前分析得出最终传递到SystemUI的mExternallyEnabled值就为false
/frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
public void setKeyguardEnabled(boolean enabled) {
synchronized (this) {
mExternallyEnabled = enabled;
......
//省略大段代码
......
}
doKeyguardLocked
锁屏的核心入口方法,mExternallyEnabled为false锁屏就被禁掉了,同时将mNeedToReshowWhenReenabled值赋值为true
private void doKeyguardLocked(Bundle options) {
.....
if (!mExternallyEnabled) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
mNeedToReshowWhenReenabled = true;
return;
}
......
}
记住disableKeyguard的流程,到了SystemUI这边修改了两个值mExternallyEnabled为false,mNeedToReshowWhenReenabled为true
接着看概率性的,不管在什么界面玩手机,玩着玩着,就可能突然跳出来keyguard给把手机锁屏了,到这里我们已经知道了是因为调用disableKeyguard方法的应用被杀,从binder死亡回调到SystemUI这边,我们看下具体代码
private class Death implements IBinder.DeathRecipient
{
IBinder token;
String tag;
Death(IBinder token, String tag)
{
this.token = token;
this.tag = tag;
}
public void binderDied()
{
cleanup(token, false);
}
protected void finalize() throws Throwable
{
try {
if (token != null) {
Log.w(mTag, "cleaning up leaked reference: " + tag);
release(token);
}
}
finally {
super.finalize();
}
}
}
TokenWatcher.cleanup
如果此时手机中安装了多个应用都调用了disableKeyguard接口,当一个应用死掉了不会恢复锁屏方式
public void cleanup(IBinder token, boolean unlink)
{
synchronized (mTokens) {
Death d = mTokens.remove(token);
if (unlink && d != null) {
d.token.unlinkToDeath(d, 0);
d.token = null;
}
//当系统只有一个应用调用disableKeyguard接口
//并且mAcquired为ture代表keyguard已经被禁掉了
//才会走此流程
if (mTokens.size() == 0 && mAcquired) {
//传的是false,acquire中传的true
sendNotificationLocked(false);
mAcquired = false;
}
}
}
sendNotificationLocked
private void sendNotificationLocked(boolean on)
{
//on = false,value = 0
int value = on ? 1 : 0;
if (mNotificationQueue == -1) {
// empty
mNotificationQueue = value;
mHandler.post(mNotificationTask);
}
else if (mNotificationQueue != value) {
// it's a pair, so cancel it
mNotificationQueue = -1;
mHandler.removeCallbacks(mNotificationTask);
}
// else, same so do nothing -- maybe we should warn?
}
mNotificationTask
private Runnable mNotificationTask = new Runnable() {
public void run()
{
int value;
synchronized (mTokens) {
value = mNotificationQueue;
mNotificationQueue = -1;
}
if (value == 1) {
acquired();
}
else if (value == 0) {
released();
}
}
};
released
子类released方法
@Override
public void released() {
// We MUST NOT hold any locks while invoking the callbacks.
mCallback.released(mUserId);
synchronized (mWatchers) {
final TokenWatcher watcher = mWatchers.get(mUserId);
if (watcher != null && !watcher.isAcquired()) {
mWatchers.remove(mUserId);
}
}
}
到后面其实和acquired流程完全一样:
KeyguardDisableHandler.updateKeyguardEnabled ->
KeyguardDisableHandler.updateKeyguardEnabledLocked ->
KeyguardDisableHandler.enableKeyguard ->
PhoneWindowManager.enableKeyguard ->
KeyguardServiceDelegate.setKeyguardEnabled ->
KeyguardService.setKeyguardEnabled ->
KeyguardViewMediator.setKeyguardEnabled
(到SystemUI中设置mExternallyEnabled的值为true)
看下KeyguardViewMediator中的setKeyguardEnabled方法
此时enabled为true,mNeedToReshowWhenReenabled因为之前disableKeyguard也被设置为了true,因此会走showLock流程,所以出现了这个问题
public void setKeyguardEnabled(boolean enabled) {
...
mExternallyEnabled = enabled;
if (!enabled && mShowing) {
...
}else if(enabled && mNeedToReshowWhenReenabled){
....
showLocked(null);
....
}
}
google在Android Q上新增了这个patch并且会用cts进行测试,所以如果想回退google此patch来修复这个功能应该是无效的了,可采用规避方法,当由binder死亡回调触发的锁屏则修改流程