Android Q上三方应用调用disableKeyguard接口带来的问题

概述

今天测试同事报了一个问题,概率性的,不管在什么界面玩手机,玩着玩着,就可能突然跳出来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死亡回调触发的锁屏则修改流程

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值