blob: 8ba92b5ae1a348135048d87988b99265fc9bf68b [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_UNSET;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_OCCLUDED_STATES;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_SHOWING;
import static com.android.server.wm.KeyguardOccludedProto.DISPLAY_ID;
import static com.android.server.wm.KeyguardOccludedProto.KEYGUARD_OCCLUDED;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
/**
* Controls Keyguard occluding, dismissing and transitions depending on what kind of activities are
* currently visible.
* <p>
* Note that everything in this class should only be accessed with the AM lock being held.
*/
class KeyguardController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardController" : TAG_ATM;
private final ActivityStackSupervisor mStackSupervisor;
private WindowManagerService mWindowManager;
private boolean mKeyguardShowing;
private boolean mAodShowing;
private boolean mKeyguardGoingAway;
private boolean mDismissalRequested;
private int mBeforeUnoccludeTransit;
private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
private final ActivityTaskManagerService mService;
private RootWindowContainer mRootWindowContainer;
private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
KeyguardController(ActivityTaskManagerService service,
ActivityStackSupervisor stackSupervisor) {
mService = service;
mStackSupervisor = stackSupervisor;
mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl("keyguard");
}
void setWindowManager(WindowManagerService windowManager) {
mWindowManager = windowManager;
mRootWindowContainer = mService.mRootWindowContainer;
}
/**
* @return true if either Keyguard or AOD are showing, not going away, and not being occluded
* on the given display, false otherwise.
*/
boolean isKeyguardOrAodShowing(int displayId) {
return (mKeyguardShowing || mAodShowing) && !mKeyguardGoingAway
&& !isDisplayOccluded(displayId);
}
/**
* @return {@code true} for default display when AOD is showing. Otherwise, same as
* {@link #isKeyguardOrAodShowing(int)}
* TODO(b/125198167): Replace isKeyguardOrAodShowing() by this logic.
*/
boolean isKeyguardUnoccludedOrAodShowing(int displayId) {
if (displayId == DEFAULT_DISPLAY && mAodShowing) {
return true;
}
return isKeyguardOrAodShowing(displayId);
}
/**
* @return true if Keyguard is showing, not going away, and not being occluded on the given
* display, false otherwise
*/
boolean isKeyguardShowing(int displayId) {
return mKeyguardShowing && !mKeyguardGoingAway && !isDisplayOccluded(displayId);
}
/**
* @return true if Keyguard is either showing or occluded, but not going away
*/
boolean isKeyguardLocked() {
return mKeyguardShowing && !mKeyguardGoingAway;
}
/**
* @return {@code true} if the keyguard is going away, {@code false} otherwise.
*/
boolean isKeyguardGoingAway() {
// Also check keyguard showing in case value is stale.
return mKeyguardGoingAway && mKeyguardShowing;
}
/**
* Update the Keyguard showing state.
*/
void setKeyguardShown(boolean keyguardShowing, boolean aodShowing) {
final boolean aodChanged = aodShowing != mAodShowing;
// If keyguard is going away, but SystemUI aborted the transition, need to reset state.
// Do not reset keyguardChanged status if this is aodChanged.
final boolean keyguardChanged = (keyguardShowing != mKeyguardShowing)
|| (mKeyguardGoingAway && keyguardShowing && !aodChanged);
if (!keyguardChanged && !aodChanged) {
return;
}
EventLogTags.writeWmSetKeyguardShown(
keyguardShowing ? 1 : 0,
aodShowing ? 1 : 0,
mKeyguardGoingAway ? 1 : 0,
"setKeyguardShown");
mKeyguardShowing = keyguardShowing;
mAodShowing = aodShowing;
mWindowManager.setAodShowing(aodShowing);
if (keyguardChanged) {
// Irrelevant to AOD.
dismissDockedStackIfNeeded();
setKeyguardGoingAway(false);
if (keyguardShowing) {
mDismissalRequested = false;
}
}
// TODO(b/113840485): Check usage for non-default display
mWindowManager.setKeyguardOrAodShowingOnDefaultDisplay(
isKeyguardOrAodShowing(DEFAULT_DISPLAY));
// Update the sleep token first such that ensureActivitiesVisible has correct sleep token
// state when evaluating visibilities.
updateKeyguardSleepToken();
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
}
/**
* Called when Keyguard is going away.
*
* @param flags See {@link WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
* etc.
*/
void keyguardGoingAway(int flags) {
if (!mKeyguardShowing) {
return;
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "keyguardGoingAway");
mService.deferWindowLayout();
try {
setKeyguardGoingAway(true);
EventLogTags.writeWmSetKeyguardShown(
1 /* keyguardShowing */,
mAodShowing ? 1 : 0,
1 /* keyguardGoingAway */,
"keyguardGoingAway");
mRootWindowContainer.getDefaultDisplay().mDisplayContent
.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY,
false /* alwaysKeepCurrent */, convertTransitFlags(flags),
false /* forceOverride */);
updateKeyguardSleepToken();
// Some stack visibility might change (e.g. docked stack)
mRootWindowContainer.resumeFocusedStacksTopActivities();
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
mRootWindowContainer.addStartingWindowsForVisibleActivities();
mWindowManager.executeAppTransition();
} finally {
mService.continueWindowLayout();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback, CharSequence message) {
final ActivityRecord activityRecord = ActivityRecord.forTokenLocked(token);
if (activityRecord == null || !activityRecord.visibleIgnoringKeyguard) {
failCallback(callback);
return;
}
Slog.i(TAG, "Activity requesting to dismiss Keyguard: " + activityRecord);
// If the client has requested to dismiss the keyguard and the Activity has the flag to
// turn the screen on, wakeup the screen if it's the top Activity.
if (activityRecord.getTurnScreenOnFlag() && activityRecord.isTopRunningActivity()) {
mStackSupervisor.wakeUp("dismissKeyguard");
}
mWindowManager.dismissKeyguard(callback, message);
}
private void setKeyguardGoingAway(boolean keyguardGoingAway) {
mKeyguardGoingAway = keyguardGoingAway;
mWindowManager.setKeyguardGoingAway(keyguardGoingAway);
}
private void failCallback(IKeyguardDismissCallback callback) {
try {
callback.onDismissError();
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call callback", e);
}
}
private int convertTransitFlags(int keyguardGoingAwayFlags) {
int result = 0;
if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_TO_SHADE) != 0) {
result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
}
if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS) != 0) {
result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
}
if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER) != 0) {
result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
}
if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS) != 0) {
result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
}
return result;
}
/**
* @return True if we may show an activity while Keyguard is showing because we are in the
* process of dismissing it anyways, false otherwise.
*/
boolean canShowActivityWhileKeyguardShowing(ActivityRecord r, boolean dismissKeyguard) {
// Allow to show it when we are about to dismiss Keyguard. This isn't allowed if r is
// already the dismissing activity, in which case we don't allow it to repeatedly dismiss
// Keyguard.
return dismissKeyguard && canDismissKeyguard() && !mAodShowing
&& (mDismissalRequested
|| (r.canShowWhenLocked()
&& getDisplay(r.getDisplayId()).mDismissingKeyguardActivity != r));
}
/**
* @return True if we may show an activity while Keyguard is occluded, false otherwise.
*/
boolean canShowWhileOccluded(boolean dismissKeyguard, boolean showWhenLocked) {
return showWhenLocked || dismissKeyguard
&& !mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
}
/**
* 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();
}
}
/**
* 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();
}
/**
* Called when somebody wants to dismiss the Keyguard via the flag.
*/
private void handleDismissKeyguard() {
// We only allow dismissing Keyguard via the flag when Keyguard is secure for legacy
// reasons, because that's how apps used to dismiss Keyguard in the secure case. In the
// insecure case, we actually show it on top of the lockscreen. See #canShowWhileOccluded.
if (!mWindowManager.isKeyguardSecure(mService.getCurrentUserId())) {
return;
}
mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
mDismissalRequested = true;
// If we are about to unocclude the Keyguard, but we can dismiss it without security,
// we immediately dismiss the Keyguard so the activity gets shown without a flicker.
final DisplayContent dc =
mRootWindowContainer.getDefaultDisplay().mDisplayContent;
if (mKeyguardShowing && canDismissKeyguard()
&& dc.mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) {
dc.prepareAppTransition(mBeforeUnoccludeTransit, false /* alwaysKeepCurrent */,
0 /* flags */, true /* forceOverride */);
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
mWindowManager.executeAppTransition();
}
}
private boolean isDisplayOccluded(int displayId) {
return getDisplay(displayId).mOccluded;
}
/**
* @return true if Keyguard can be currently dismissed without entering credentials.
*/
boolean canDismissKeyguard() {
return mWindowManager.mPolicy.isKeyguardTrustedLw()
|| !mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
}
private int resolveOccludeTransit() {
final DisplayContent dc = mRootWindowContainer.getDefaultDisplay().mDisplayContent;
if (mBeforeUnoccludeTransit != TRANSIT_UNSET
&& dc.mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE
// TODO(b/113840485): Handle app transition for individual display.
&& isDisplayOccluded(DEFAULT_DISPLAY)) {
// Reuse old transit in case we are occluding Keyguard again, meaning that we never
// actually occclude/unocclude Keyguard, but just run a normal transition.
return mBeforeUnoccludeTransit;
// TODO(b/113840485): Handle app transition for individual display.
} else if (!isDisplayOccluded(DEFAULT_DISPLAY)) {
// Save transit in case we dismiss/occlude Keyguard shortly after.
mBeforeUnoccludeTransit = dc.mAppTransition.getAppTransition();
return TRANSIT_KEYGUARD_UNOCCLUDE;
} else {
return TRANSIT_KEYGUARD_OCCLUDE;
}
}
private void dismissDockedStackIfNeeded() {
// TODO(b/113840485): Handle docked stack for individual display.
if (mKeyguardShowing && isDisplayOccluded(DEFAULT_DISPLAY)) {
// The lock screen is currently showing, but is occluded by a window that can
// show on top of the lock screen. In this can we want to dismiss the docked
// stack since it will be complicated/risky to try to put the activity on top
// of the lock screen in the right fullscreen configuration.
final TaskDisplayArea taskDisplayArea = mRootWindowContainer
.getDefaultTaskDisplayArea();
if (!taskDisplayArea.isSplitScreenModeActivated()) {
return;
}
taskDisplayArea.onSplitScreenModeDismissed();
}
}
private void updateKeyguardSleepToken() {
for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
displayNdx >= 0; displayNdx--) {
final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
updateKeyguardSleepToken(display.mDisplayId);
}
}
private void updateKeyguardSleepToken(int displayId) {
final KeyguardDisplayState state = getDisplay(displayId);
if (isKeyguardUnoccludedOrAodShowing(displayId)) {
state.mSleepTokenAcquirer.acquire(displayId);
} else if (!isKeyguardUnoccludedOrAodShowing(displayId)) {
state.mSleepTokenAcquirer.release(displayId);
}
}
private KeyguardDisplayState getDisplay(int displayId) {
KeyguardDisplayState state = mDisplayStates.get(displayId);
if (state == null) {
state = new KeyguardDisplayState(mService, displayId, mSleepTokenAcquirer);
mDisplayStates.append(displayId, state);
}
return state;
}
void onDisplayRemoved(int displayId) {
final KeyguardDisplayState state = mDisplayStates.get(displayId);
if (state != null) {
state.onRemoved();
mDisplayStates.remove(displayId);
}
}
/** Represents Keyguard state per individual display. */
private static class KeyguardDisplayState {
private final int mDisplayId;
private boolean mOccluded;
private ActivityRecord mDismissingKeyguardActivity;
private boolean mRequestDismissKeyguard;
private final ActivityTaskManagerService mService;
private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
KeyguardDisplayState(ActivityTaskManagerService service, int displayId,
ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) {
mService = service;
mDisplayId = displayId;
mSleepTokenAcquirer = acquirer;
}
void onRemoved() {
mDismissingKeyguardActivity = null;
mSleepTokenAcquirer.release(mDisplayId);
}
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;
}
}
/**
* Gets the stack used to check the occluded state.
* <p>
* Only the top non-pinned activity of the focusable stack on each display can control its
* occlusion state.
*/
private ActivityStack getStackForControllingOccluding(DisplayContent display) {
for (int tdaNdx = display.getTaskDisplayAreaCount() - 1; tdaNdx >= 0; --tdaNdx) {
final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(tdaNdx);
for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
final ActivityStack stack = taskDisplayArea.getStackAt(sNdx);
if (stack != null && stack.isFocusableAndVisible()
&& !stack.inPinnedWindowingMode()) {
return stack;
}
}
}
return null;
}
void dumpStatus(PrintWriter pw, String prefix) {
final StringBuilder sb = new StringBuilder();
sb.append(prefix);
sb.append(" Occluded=").append(mOccluded)
.append(" DismissingKeyguardActivity=")
.append(mDismissingKeyguardActivity)
.append(" at display=")
.append(mDisplayId);
pw.println(sb.toString());
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(DISPLAY_ID, mDisplayId);
proto.write(KEYGUARD_OCCLUDED, mOccluded);
proto.end(token);
}
}
void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "KeyguardController:");
pw.println(prefix + " mKeyguardShowing=" + mKeyguardShowing);
pw.println(prefix + " mAodShowing=" + mAodShowing);
pw.println(prefix + " mKeyguardGoingAway=" + mKeyguardGoingAway);
dumpDisplayStates(pw, prefix);
pw.println(prefix + " mDismissalRequested=" + mDismissalRequested);
pw.println();
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(AOD_SHOWING, mAodShowing);
proto.write(KEYGUARD_SHOWING, mKeyguardShowing);
writeDisplayStatesToProto(proto, KEYGUARD_OCCLUDED_STATES);
proto.end(token);
}
private void dumpDisplayStates(PrintWriter pw, String prefix) {
for (int i = 0; i < mDisplayStates.size(); i++) {
mDisplayStates.valueAt(i).dumpStatus(pw, prefix);
}
}
private void writeDisplayStatesToProto(ProtoOutputStream proto, long fieldId) {
for (int i = 0; i < mDisplayStates.size(); i++) {
mDisplayStates.valueAt(i).dumpDebug(proto, fieldId);
}
}
}