Andoird 10平台,设置锁屏密码(pin/passowrd/pattern)->双击power键启动camera->拍照->点击左下角预览->进入google photo->(KO,此时无法弹出bouncer界面输密码,而是直接返回锁屏界面)
还有个比较奇怪的现象是当photos启动之后主activity未被销毁即没有点击back就不会复现此问题了,当主activity销毁后就会复现
首先进行交叉验证确认是camera,google photos,还是平台问题,将公司camera装到google参考机也能复现此问题,确认可能是Android平台问题,需要着手分析源码
Keyguard这一块的主要还是由framework来控制的,PhoneWindowManager回调KeyguardViewMediator的各种方法,KeyguardViewMediator再来控制KeyguardBouncer,所以此问题原因还是在framework那边有什么条件没有满足bouncer弹出来,没有走到Keyguard这边的流程。
Keyguard和framework的交互主要由KeyguardController来控制
此方法中的requestDismissKeyguard值是直接影响是否走到处理Keyguard流程的开关
/frameworks/base/services/core/java/com/android/server/wm/KeyguardController.java
private void visibilitiesUpdated() {
boolean requestDismissKeyguard = false;
for (int displayNdx = mRootActivityContainer.getChildCount() - 1;
displayNdx >= 0; displayNdx--) {
final ActivityDisplay display = mRootActivityContainer.getChildAt(displayNdx);
final KeyguardDisplayState state = getDisplay(display.mDisplayId);
state.visibilitiesUpdated(this, display);
requestDismissKeyguard |= state.mRequestDismissKeyguard;
}
// Dismissing Keyguard happens globally using the information from all displays.
if (requestDismissKeyguard) {
//Keyguard dismiss的核心方法
handleDismissKeyguard();
}
}
此方法里满足了一定条件则会将mRequestDismissKeyguard置为true,所以bouncer不弹出来肯定是这里的某个条件没有满足,mDismissingKeyguardActivity等于当前处于Top的想要dismiss keyguard的Activity,lastDismissActivity用来保存mDismissingKeyguardActivity的值
void visibilitiesUpdated(KeyguardController controller, ActivityDisplay display) {
final boolean lastOccluded = mOccluded;
final ActivityRecord lastDismissActivity = mDismissingKeyguardActivity;
mRequestDismissKeyguard = false;
mOccluded = false;
mDismissingKeyguardActivity = null;
final ActivityStack stack = getStackForControllingOccluding(display);
if (stack != null) {
final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null
&& stack.topRunningActivityLocked() == topDismissing
&& controller.canShowWhileOccluded(
true /* dismissKeyguard */,
false /* showWhenLocked */));
if (stack.getTopDismissingKeyguardActivity() != null) {
mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
}
// FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
if (mDisplayId != DEFAULT_DISPLAY) {
mOccluded |= stack.canShowWithInsecureKeyguard()
&& controller.canDismissKeyguard();
}
}
// TODO(b/123372519): isShowingDream can only works on default display.
if (mDisplayId == DEFAULT_DISPLAY) {
mOccluded |= controller.mWindowManager.isShowingDream();
}
if (lastOccluded != mOccluded) {
controller.handleOccludedChanged(mDisplayId);
}
if (lastDismissActivity != mDismissingKeyguardActivity && !mOccluded
&& mDismissingKeyguardActivity != null
&& controller.mWindowManager.isKeyguardSecure(
controller.mService.getCurrentUserId())) {
mRequestDismissKeyguard = true;
}
}
看一下这几个条件判断
条件一,lastDismissActivity != mDismissingKeyguardActivity,这个条件很容易满足,lastDismissActivity既然是保存mDismissingKeyguardActivity的状态,那他们容易不等
条件二,!mOccluded 此条件比较可能不满足
条件三,mDismissingKeyguardActivity != null,此条件也很容易满足
条件四,controller.mWindowManager.isKeyguardSecure(
controller.mService.getCurrentUserId())代表当前设备是否处于保护模式即是否设置了密码
这几个条件唯一可能的就是条件二,通过log调试也是发现进google photos时不弹bouncer主要就是 !mOccluded 不满足
mOccluded这个值的控制条件也比较多,通过log调试发现主要是stack.topActivityOccludesKeyguard()控制了mOccluded
mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null
&& stack.topRunningActivityLocked() == topDismissing
&& controller.canShowWhileOccluded(
true /* dismissKeyguard */,
false /* showWhenLocked */));
/frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java
找mTopActivityOccludesKeyguard赋值的代码
/**
* @return true if the top visible activity wants to occlude the Keyguard, false otherwise
*/
boolean topActivityOccludesKeyguard() {
return mTopActivityOccludesKeyguard;
}
看注释的意思是只有当前top activity可以决定是否关闭Keyguard,锁屏启动应用一般有两种,第一种不需要解锁直接显示在锁屏之上,这种不需要弹出bouncer让用户解锁,因为它们可以直接显示了,类似闹钟,来电界面,另一种则需要先解锁再显示,到这里可以断定启动Photos用的第一种启动,但是由于Photos作为普通activity显然windows层级太低只能显示在keyguard底下,接着需要继续调查Photos为何会用第一种方式启动
boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible, boolean isTop) {
......
boolean showWhenLocked = false;
......
showWhenLocked = r.canShowWhenLocked();
// Only the top activity may control occluded, as we can't occlude the Keyguard if the
// top app doesn't want to occlude it.
if (isTop) {
mTopActivityOccludesKeyguard |= showWhenLocked;
}
......
}
/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
boolean canShowWhenLocked() {
if (!inPinnedWindowingMode() && (mShowWhenLocked
|| (mAppWindowToken != null && mAppWindowToken.containsShowWhenLockedWindow()))) {
return true;
} else if (mInheritShownWhenLocked) {
ActivityRecord r = getActivityBelow();
return r != null && !r.inPinnedWindowingMode() && (r.mShowWhenLocked
|| (r.mAppWindowToken != null
&& r.mAppWindowToken.containsShowWhenLockedWindow()));
} else {
return false;
}
}
showWhenLocked也是由多个值控制的,继续跟mAppWindowToken.containsShowWhenLockedWindow()
此方法返回值根据activity是否设置FLAG_SHOW_WHEN_LOCKED来控制的,
/frameworks/base/services/core/java/com/android/server/wm/AppWindowToken.java
boolean containsShowWhenLockedWindow() {
......
for (int i = mChildren.size() - 1; i >= 0; i--) {
if ((mChildren.get(i).mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0) {
return true;
}
}
return false;
}
FLAG_SHOW_WHEN_LOCKED定义在WindowManager中,含义是是否能够显示在锁屏之上,到这里大概明白了,google photos设置了FLAG_SHOW_WHEN_LOCKED,为什么要设置这个flag,应用设置的还是系统设置的,为什么首次启动的photos会有这个flag,不是首次就没有
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
准备添加log调试,直接打印mChildren.get(i)的值,
首次启动photos的log:
com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:31:35.063 825 2735 D djtang : mChildren.get(i) = :Window{dc012d7 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:31:35.063 825 2735 D djtang : mChildren.get(i) = :Window{dc012d7 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:31:35.481 825 2593 D djtang : mChildren.get(i) = :Window{dc012d7 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:31:35.481 825 2593 D djtang : mChildren.get(i) = :Window{dc012d7 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:31:35.481 825 2593 D djtang : mChildren.get(i) = :Window{dc012d7 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:31:35.481 825 2593 D djtang : mChildren.get(i) = :Window{dc012d7 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:31:35.616 825 1089 D djtang : mChildren.get(i) = :Window{5c6f6ae u0 Splash Screen com.google.android.apps.photos}
12-17 08:31:35.616 825 1089 D djtang : mChildren.get(i) = :Window{5c6f6ae u0 Splash Screen com.google.android.apps.photos}
12-17 08:31:35.617 825 1089 D djtang : mChildren.get(i) = :Window{5c6f6ae u0 Splash Screen com.google.android.apps.photos}
12-17 08:31:35.617 825 1089 D djtang : mChildren.get(i) = :Window{5c6f6ae u0 Splash Screen com.google.android.apps.photos}
12-17 08:31:35.617 825 1089 D djtang : mChildren.get(i) = :Window{5c6f6ae u0 Splash Screen com.google.android.apps.photos}
12-17 08:31:35.632 825 2735 D djtang : mChildren.get(i) = :Window{5c6f6ae u0 Splash Screen com.google.android.apps.photos}
12-17 08:31:35.632 825 2735 D djtang : mChildren.get(i) = :Window{5c6f6ae u0 Splash Screen com.google.android.apps.photos}
非首次启动photos的log:
12-17 08:34:25.176 825 2206 D djtang : mChildren.get(i) = :Window{7965666 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:34:25.176 825 2206 D djtang : mChildren.get(i) = :Window{7965666 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:34:25.195 825 2206 D djtang : mChildren.get(i) = :Window{7965666 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:34:25.195 825 2206 D djtang : mChildren.get(i) = :Window{7965666 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:34:25.195 825 2206 D djtang : mChildren.get(i) = :Window{7965666 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:34:25.195 825 2206 D djtang : mChildren.get(i) = :Window{7965666 u0 com.tcl.camera/com.android.camera.SecureCameraActivity}
12-17 08:34:29.747 825 2206 D djtang : mChildren.get(i) = :Window{ae03481 u0 com.google.android.apps.photos/com.google.android.apps.photos.pager.HostPhotoPagerActivity}
12-17 08:34:29.748 825 2206 D djtang : mChildren.get(i) = :Window{ae03481 u0 com.google.android.apps.photos/com.google.android.apps.photos.pager.HostPhotoPagerActivity}
12-17 08:34:29.748 825 2206 D djtang : mChildren.get(i) = :Window{ae03481 u0 com.google.android.apps.photos/com.google.android.apps.photos.pager.HostPhotoPagerActivity}
12-17 08:34:29.783 825 2208 D djtang : mChildren.get(i) = :Window{ae03481 u0 com.google.android.apps.photos/com.google.android.apps.photos.pager.HostPhotoPagerActivity}
12-17 08:34:29.783 825 2208 D djtang : mChildren.get(i) = :Window{ae03481 u0 com.google.android.apps.photos/com.google.android.apps.photos.pager.HostPhotoPagerActivity}
差别是首次启动的photos打印的windowState为Window{5c6f6ae u0 Splash Screen com.google.android.apps.photos},非首次启动的photos打印的windowState为Window{ae03481 u0 com.google.android.apps.photos/com.google.android.apps.photos.pager.HostPhotoPagerActivity}
/frameworks/base/services/core/java/com/android/server/wm/WindowState.java
@Override
public String toString() {
final CharSequence title = getWindowTag();
if (mStringNameCache == null || mLastTitle != title || mWasExiting != mAnimatingExit) {
mLastTitle = title;
mWasExiting = mAnimatingExit;
mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
+ " u" + UserHandle.getUserId(mOwnerUid)
+ " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}");
}
return mStringNameCache;
}
CharSequence getWindowTag() {
CharSequence tag = mAttrs.getTitle();
if (tag == null || tag.length() <= 0) {
tag = mAttrs.packageName;
}
return tag;
}
打印的是getTitle,其实非首次启动的photos的打印比较正常,包名加activity名字,关键是首次启动的photos有个"Splash Screen",log中发现其他应用首次启动的WindowState也为"Splash Screen" + 包名,如:
Window{df6f4ad u0 Splash Screen com.android.settings}
在代码中全局搜索"Splash Screen",
找到了,addSplashScreen中给首次启动的应用主activity设置了title为"Splash Screen " + packageName,并且在mKeyguardOccluded为ture时给应用设置flag为FLAG_SHOW_WHEN_LOCKED
/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
@Override
public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
int logo, int windowFlags, Configuration overrideConfig, int displayId) {
......
if (displayId == DEFAULT_DISPLAY && mKeyguardOccluded) {
windowFlags |= FLAG_SHOW_WHEN_LOCKED;
}
......
params.setTitle("Splash Screen " + packageName);
......
}
找到在哪里设置就好修改了,当我们需要某个应用显示在锁屏之上时我们不必设置此flag