安卓keyguard的occluded属性介绍
keyguard的occluded属性为true时表示当前keyguard被遮挡了,也就是说当前即使手机还未解锁也不显示keyguard,即不显示NotificationShade窗口。例如锁屏相机、音乐app锁屏显示等。
实现Activity可以在锁屏显示的方法是在AndroidManifest.xml中的Activity标签中加上:android:showWhenLocked="true"即可。
安卓系统中有3个位置都有occluded属性,按occluded属性改变的顺序从先到后排序是:KeyguardController、PhoneWindowManager、SystemUI。首先occluded属性首先在system_server进程中的KeyguardController中改变,然后通知PhoneWindowManager,最后通过binder通知systemui进程来显示/隐藏NotificationShade。
KeyguardController的occluded属性改变流程
首先无论进入或退出锁屏显示的Activity都会调用frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java或frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java或frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java的ensureActivitiesVisible方法:
/**
* @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
*/
void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
if (mStackSupervisor.inActivityVisibilityUpdate()) {
// Don't do recursive work.
return;
}
try {
mStackSupervisor.beginActivityVisibilityUpdate();
// First the front stacks. In case any are not fullscreen and are in front of home.
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
final DisplayContent display = getChildAt(displayNdx);
display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
notifyClients);
}
} finally {
mStackSupervisor.endActivityVisibilityUpdate();
}
}
void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
mAtmService.mStackSupervisor.beginActivityVisibilityUpdate();
try {
for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = getStackAt(stackNdx);
stack.ensureActivitiesVisible(starting, configChanges, preserveWindows,
notifyClients);
}
} finally {
mAtmService.mStackSupervisor.endActivityVisibilityUpdate();
}
}
/**
* Ensure visibility with an option to also update the configuration of visible activities.
* @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
* @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
* @param starting The top most activity in the task.
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
* @param notifyClients Flag indicating whether the visibility updates should be sent to the
* clients in {@link mEnsureActivitiesVisibleHelper}.
* @param preserveWindows Flag indicating whether windows should be preserved when updating
* configuration in {@link mEnsureActivitiesVisibleHelper}.
* @param configChanges Parts of the configuration that changed for this activity for evaluating
* if the screen should be frozen as part of
* {@link mEnsureActivitiesVisibleHelper}.
*/
// TODO: Should be re-worked based on the fact that each task as a stack in most cases.
void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
mTopActivityOccludesKeyguard = false;
mTopDismissingKeyguardActivity = null;
mStackSupervisor.beginActivityVisibilityUpdate();
try {
mEnsureActivitiesVisibleHelper.process(
starting, configChanges, preserveWindows, notifyClients);
if (mTranslucentActivityWaiting != null &&
mUndrawnActivitiesBelowTopTranslucent.isEmpty()) {
// Nothing is getting drawn or everything was already visible, don't wait for timeout.
notifyActivityDrawnLocked(null);
}
} finally {
mStackSupervisor.endActivityVisibilityUpdate();
}
}
最后都会调用frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java的endActivityVisibilityUpdate方法:
/** Ends a batch of visibility updates. */
void endActivityVisibilityUpdate() {
mVisibilityTransactionDepth--;
if (mVisibilityTransactionDepth == 0) {
getKeyguardController().visibilitiesUpdated();
}
}
调用了frameworks/base/services/core/java/com/android/server/wm/KeyguardController.java的visibilitiesUpdated方法:
/**
* Makes sure to update lockscreen occluded/dismiss state if needed after completing all
* visibility updates ({@link ActivityStackSupervisor#endActivityVisibilityUpdate}).
*/
void visibilitiesUpdated() {
boolean requestDismissKeyguard = false;
for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
displayNdx >= 0; displayNdx--) {
final DisplayContent display = mRootWindowContainer.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) {
handleDismissKeyguard();
}
}
调用了KeyguardController的内部静态类KeyguardDisplayState的visibilitiesUpdated方法:
/** Represents Keyguard state per individual display. */
private static class KeyguardDisplayState {
private final int mDisplayId;
private boolean mOccluded;
......
void visibilitiesUpdated(KeyguardController controller, DisplayContent 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.topRunningActivity() == 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 |= mService.mRootWindowContainer.getDefaultDisplay().mDisplayContent
.getDisplayPolicy().isShowingDreamLw();
}
if (lastOccluded != mOccluded) {
controller.handleOccludedChanged(mDisplayId);
}
if (lastDismissActivity != mDismissingKeyguardActivity && !mOccluded
&& mDismissingKeyguardActivity != null
&& controller.mWindowManager.isKeyguardSecure(
controller.mService.getCurrentUserId())) {
mRequestDismissKeyguard = true;
}
}
....
}
在这里对成员变量mOccluded进行了更新,完成了KeyguardController的occluded属性改变。
PhoneWindowManager的occluded属性改变流程
然后调用了KeyguardController的handleOccludedChanged方法:
/**
* Called when occluded state changed.
*/
private void handleOccludedChanged(int displayId) {
// TODO(b/113840485): Handle app transition for individual display, and apply occluded
// state change to secondary displays.
// For now, only default display fully supports occluded change. Other displays only
// updates keygaurd sleep token on that display.
if (displayId != DEFAULT_DISPLAY) {
updateKeyguardSleepToken(displayId);
return;
}
mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY));
if (isKeyguardLocked()) {
mService.deferWindowLayout();
try {
mRootWindowContainer.getDefaultDisplay().mDisplayContent
.prepareAppTransition(resolveOccludeTransit(),
false /* alwaysKeepCurrent */, 0 /* flags */,
true /* forceOverride */);
updateKeyguardSleepToken(DEFAULT_DISPLAY);
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
mWindowManager.executeAppTransition();
} finally {
mService.continueWindowLayout();
}
}
dismissDockedStackIfNeeded();
}
调用了frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java的onKeyguardOccludedChangedLw方法:
@Override
public void onKeyguardOccludedChangedLw(boolean occluded) {
if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
mPendingKeyguardOccluded = occluded;
mKeyguardOccludedChanged = true;
} else {
setKeyguardOccludedLw(occluded, false /* force */);
}
}
如果mKeyguardDelegate.isShowing()为true时,这里只会改变mPendingKeyguardOccluded和mKeyguardOccludedChanged的值。需要在后面过渡动画时再调用setKeyguardOccludedLw方法,这个流程如下:
执行过渡动画会调用frameworks/base/services/core/java/com/android/server/wm/AppTransition.java的goodToGo方法:
/**
* @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
* layout pass needs to be done
*/
int goodToGo(int transit, ActivityRecord topOpeningApp, ArraySet<ActivityRecord> openingApps) {
mNextAppTransition = TRANSIT_UNSET;
mNextAppTransitionFlags = 0;
setAppTransitionState(APP_STATE_RUNNING);
final WindowContainer wc =
topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;
final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
int redoLayout = notifyAppTransitionStartingLocked(transit,
topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
? topOpeningAnim.getStatusBarTransitionsStartTime()
: SystemClock.uptimeMillis(),
AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
if (mRemoteAnimationController != null) {
mRemoteAnimationController.goodToGo();
}
return redoLayout;
}
调用了AppTransition的notifyAppTransitionStartingLocked方法:
private int notifyAppTransitionStartingLocked(int transit, long duration,
long statusBarAnimationStartTime, long statusBarAnimationDuration) {
int redoLayout = 0;
for (int i = 0; i < mListeners.size(); i++) {
redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, duration,
statusBarAnimationStartTime, statusBarAnimationDuration);
}
return redoLayout;
}
调用了PhoneWindowManager在init方法中注册的回调onAppTransitionStartingLocked方法:
/** {@inheritDoc} */
@Override
public void init(Context context, IWindowManager windowManager,
WindowManagerFuncs windowManagerFuncs) {
mContext = context;
......
mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@Override
public int onAppTransitionStartingLocked(int transit, long duration,
long statusBarAnimationStartTime, long statusBarAnimationDuration) {
return handleStartTransitionForKeyguardLw(transit, duration);
}
@Override
public void onAppTransitionCancelledLocked(int transit) {
handleStartTransitionForKeyguardLw(transit, 0 /* duration */);
}
});
......
}
调用了PhoneWindowManager的handleStartTransitionForKeyguardLw方法:
private int handleStartTransitionForKeyguardLw(int transit, long duration) {
if (mKeyguardOccludedChanged) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
+ mPendingKeyguardOccluded);
mKeyguardOccludedChanged = false;
if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */)) {
return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
}
}
if (AppTransition.isKeyguardGoingAwayTransit(transit)) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
}
return 0;
}
此时mKeyguardOccludedChanged为true,因此会调用PhoneWindowManager的setKeyguardOccludedLw方法。
回到PhoneWindowManager的onKeyguardOccludedChangedLw方法中,如果mKeyguardDelegate.isShowing()为false时,这里也会调用PhoneWindowManager的setKeyguardOccludedLw方法:
/**
* Updates the occluded state of the Keyguard.
*
* @return Whether the flags have changed and we have to redo the layout.
*/
private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
final boolean wasOccluded = mKeyguardOccluded;
final boolean showing = mKeyguardDelegate.isShowing();
final boolean changed = wasOccluded != isOccluded || force;
if (!isOccluded && changed && showing) {
mKeyguardOccluded = false;
mKeyguardDelegate.setOccluded(false, true /* animate */);
if (mKeyguardCandidate != null) {
if (!mKeyguardDelegate.hasLockscreenWallpaper()) {
mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
}
}
return true;
} else if (isOccluded && changed && showing) {
mKeyguardOccluded = true;
mKeyguardDelegate.setOccluded(true, false /* animate */);
if (mKeyguardCandidate != null) {
mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
}
return true;
} else if (changed) {
mKeyguardOccluded = isOccluded;
mKeyguardDelegate.setOccluded(isOccluded, false /* animate */);
return false;
} else {
return false;
}
}
这里改变了mKeyguardOccluded的值,也就完成了PhoneWidnowManager的occluded属性改变。
SystemUI的occluded属性改变流程
然后调用了frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java的setOccluded方法:
public void setOccluded(boolean isOccluded, boolean animate) {
if (mKeyguardService != null) {
if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
mKeyguardService.setOccluded(isOccluded, animate);
}
mKeyguardState.occluded = isOccluded;
}
调用了frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java的setOccluded方法:
@Override // Binder interface
public void setOccluded(boolean isOccluded, boolean animate) {
try {
mService.setOccluded(isOccluded, animate);
} catch (RemoteException e) {
Slog.w(TAG , "Remote Exception", e);
}
}
通过binder调用了systemui进程中frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java的成员变量mBinder的setOccluded方法:
@Override // Binder interface
public void setOccluded(boolean isOccluded, boolean animate) {
Trace.beginSection("KeyguardService.mBinder#setOccluded");
checkPermission();
mKeyguardViewMediator.setOccluded(isOccluded, animate);
Trace.endSection();
}
调用了frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java的setOccluded方法:
/**
* Notify us when the keyguard is occluded by another window
*/
public void setOccluded(boolean isOccluded, boolean animate) {
Trace.beginSection("KeyguardViewMediator#setOccluded");
if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
mHandler.removeMessages(SET_OCCLUDED);
Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0);
mHandler.sendMessage(msg);
Trace.endSection();
}
调用了KeyguardViewMediator的成员变量mHandler的handleMessage方法:
/**
* This handler will be associated with the policy thread, which will also
* be the UI thread of the keyguard. Since the apis of the policy, and therefore
* this class, can be called by other threads, any action that directly
* interacts with the keyguard ui should be posted to this handler, rather
* than called directly.
*/
private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
......
case SET_OCCLUDED:
Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED");
handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0);
Trace.endSection();
break;
......
}
}
};
调用了KeyguardViewMediator的handleSetOccluded方法:
/**
* Handles SET_OCCLUDED message sent by setOccluded()
*/
private void handleSetOccluded(boolean isOccluded, boolean animate) {
Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
synchronized (KeyguardViewMediator.this) {
if (mHiding && isOccluded) {
// We're in the process of going away but WindowManager wants to show a
// SHOW_WHEN_LOCKED activity instead.
startKeyguardExitAnimation(0, 0);
}
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
mUpdateMonitor.setKeyguardOccluded(isOccluded);
mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
&& mDeviceInteractive);
adjustStatusBarLocked();
}
}
Trace.endSection();
}
这里改变了mOccluded的值,也就完成了SystemUI的occluded属性改变。
总结
occluded属性的改变顺序:KeyguardController → PhoneWindowManager → SystemUI