The big keyguard transition refactor (1/n)

The heart of this change are two things:
1) Instead of using the force hide mechanism to hide windows behind
Keyguard, we actually make the activities invisible in activity manager.
2) When Keyguard is going away, we change the visibilities in activity
manager and run an app transition.

At the very core we move the responsibility of hiding activities to
ActivityStack, which checks whether Keyguard is showing, and then
hides all non-show-when-locked activities. For that, we need to check
whether any window of an activity has SHOW_WHEN_LOCKED set. We
introduce a callback from WM -> AM in case these Keyguard flags have
changed.

Furthermore, we decide whether to occlude Keyguard in KeyguardController,
which just checks whether the top activity has SHOW_WHEN_LOCKED set. When
this state changes, we prepare an occlude/unocclude app transition, and
in PWM we just inform the Keyguard about the animation so SysUI can play
along this animations in a mostly synchronized manner.

Since we now use an app transition when unlocking the phone, we get
lockscreen launch animations for free - window manager automatically
waits until the activity is drawn, or directly executes the transition
if there is nothing to animate. Thus, we can remove all the infrastructure
around "waitingForActivityDrawn".

The logic to show/hide non-app windows is moved to policy, and we add the
ability to run animations on non-app windows when executing an app
transition.

Test:
1) runtest frameworks-services -c com.android.server.wm.AppTransitionTests
2) Manually test unlocking Keyguard:
2a) Without security
2b) With security
2c) With security but trusted
2d) Portrait while activity behind is in landscape
3) Test launching things from Keyguard
3a) Without security
3b) With security
3c) Launch camera without security
3d) Launch camera with security
3e) Launch camera with securtiy and trusted
3f) Launch voice affordance
4) Set no notifications on lockscreen, drag down, make sure you get
the correct animation
5) Test clicking "emergency" on bouncer
5b) Test "Emergency info" on emergency dialer
5c) Test clicking edit button on emergency info, should show pattern on
Keyguard

Bug: 32057734
Change-Id: Icada03cca74d6a612c1f988845f4d4f601087558
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
new file mode 100644
index 0000000..54aa115
--- /dev/null
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -0,0 +1,253 @@
+/*
+ * 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.am;
+
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY;
+import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_OCCLUDE;
+import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
+
+import com.android.server.wm.WindowManagerService;
+
+import java.util.ArrayList;
+
+/**
+ * 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 final ActivityManagerService mService;
+    private final ActivityStackSupervisor mStackSupervisor;
+    private WindowManagerService mWindowManager;
+    private boolean mKeyguardGoingAway;
+    private boolean mKeyguardShowing;
+    private boolean mOccluded;
+    private ActivityRecord mDismissingKeyguardActivity;
+    private int mBeforeUnoccludeTransit;
+    private int mVisibilityTransactionDepth;
+
+    KeyguardController(ActivityManagerService service,
+            ActivityStackSupervisor stackSupervisor) {
+        mService = service;
+        mStackSupervisor = stackSupervisor;
+    }
+
+    void setWindowManager(WindowManagerService windowManager) {
+        mWindowManager = windowManager;
+    }
+
+    /**
+     * @return true if Keyguard is showing, not going away, and not being occluded, false otherwise
+     */
+    boolean isKeyguardShowing() {
+        return mKeyguardShowing && !mKeyguardGoingAway && !mOccluded;
+    }
+
+    /**
+     * @return true if Keyguard is either showing or occluded, but not going away
+     */
+    boolean isKeyguardLocked() {
+        return mKeyguardShowing && !mKeyguardGoingAway;
+    }
+
+    /**
+     * Update the Keyguard showing state.
+     */
+    void setKeyguardShown(boolean showing) {
+        if (showing == mKeyguardShowing) {
+            return;
+        }
+        mKeyguardShowing = showing;
+        if (showing) {
+            mKeyguardGoingAway = false;
+
+            // Allow an activity to redismiss Keyguard.
+            mDismissingKeyguardActivity = null;
+        }
+        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+        mService.updateSleepIfNeededLocked();
+    }
+
+    /**
+     * Called when Keyguard is going away.
+     *
+     * @param flags See {@link android.view.WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
+     *              etc.
+     */
+    void keyguardGoingAway(int flags) {
+        if (mKeyguardShowing) {
+            mKeyguardGoingAway = true;
+            mWindowManager.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY,
+                    false /* alwaysKeepCurrent */, convertTransitFlags(flags),
+                    false /* forceOverride */);
+            mWindowManager.keyguardGoingAway(flags);
+            mService.updateSleepIfNeededLocked();
+
+            // Some stack visibility might change (e.g. docked stack)
+            mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+            mWindowManager.executeAppTransition();
+            mService.applyVrModeIfNeededLocked(mStackSupervisor.getResumedActivityLocked(),
+                    true /* enable */);
+        }
+    }
+
+    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;
+        }
+        return result;
+    }
+
+    /**
+     * Starts a batch of visibility updates.
+     */
+    void beginActivityVisibilityUpdate() {
+        mVisibilityTransactionDepth++;
+    }
+
+    /**
+     * Ends a batch of visibility updates. After all batches are done, this method makes sure to
+     * update lockscreen occluded/dismiss state if needed.
+     */
+    void endActivityVisibilityUpdate() {
+        mVisibilityTransactionDepth--;
+        if (mVisibilityTransactionDepth == 0) {
+            visibilitiesUpdated();
+        }
+    }
+
+    private void visibilitiesUpdated() {
+        final boolean lastOccluded = mOccluded;
+        final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity;
+        mOccluded = false;
+        mDismissingKeyguardActivity = null;
+        final ArrayList<ActivityStack> stacks = mStackSupervisor.getStacksOnDefaultDisplay();
+        final int topStackNdx = stacks.size() - 1;
+        for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = stacks.get(stackNdx);
+
+            // Only the very top activity may control occluded state
+            if (stackNdx == topStackNdx) {
+                mOccluded = stack.topActivityOccludesKeyguard();
+            }
+            if (mDismissingKeyguardActivity == null
+                    && stack.getTopDismissingKeyguardActivity() != null) {
+                mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+            }
+        }
+        if (mOccluded != lastOccluded) {
+            handleOccludedChanged();
+        }
+        if (mDismissingKeyguardActivity != lastDismissingKeyguardActivity) {
+            handleDismissKeyguard();
+        }
+    }
+
+    /**
+     * Called when occluded state changed.
+     */
+    private void handleOccludedChanged() {
+        mWindowManager.onKeyguardOccludedChanged(mOccluded);
+        if (isKeyguardLocked()) {
+            mWindowManager.deferSurfaceLayout();
+            try {
+                mWindowManager.prepareAppTransition(resolveOccludeTransit(),
+                        false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */);
+                mService.updateSleepIfNeededLocked();
+                mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+                mWindowManager.executeAppTransition();
+            } finally {
+                mWindowManager.continueSurfaceLayout();
+            }
+        }
+        dismissDockedStackIfNeeded();
+    }
+
+    /**
+     * Called when somebody might want to dismiss the Keyguard.
+     */
+    private void handleDismissKeyguard() {
+        if (mDismissingKeyguardActivity != null) {
+            mWindowManager.dismissKeyguard();
+
+            // 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.
+            if (mKeyguardShowing && canDismissKeyguard()
+                    && mWindowManager.getPendingAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) {
+                mWindowManager.prepareAppTransition(mBeforeUnoccludeTransit,
+                        false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */);
+                mKeyguardGoingAway = true;
+                mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+                mWindowManager.executeAppTransition();
+            }
+        }
+    }
+
+    /**
+     * @return true if Keyguard can be currently dismissed without entering credentials.
+     */
+    private boolean canDismissKeyguard() {
+        return mWindowManager.isKeyguardTrusted() || !mWindowManager.isKeyguardSecure();
+    }
+
+    private int resolveOccludeTransit() {
+        if (mBeforeUnoccludeTransit != TRANSIT_UNSET
+                && mWindowManager.getPendingAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE
+                && mOccluded) {
+
+            // 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;
+        } else if (!mOccluded) {
+
+            // Save transit in case we dismiss/occlude Keyguard shortly after.
+            mBeforeUnoccludeTransit = mWindowManager.getPendingAppTransition();
+            return TRANSIT_KEYGUARD_UNOCCLUDE;
+        } else {
+            return TRANSIT_KEYGUARD_OCCLUDE;
+        }
+    }
+
+    private void dismissDockedStackIfNeeded() {
+        if (mKeyguardShowing && mOccluded) {
+            // 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.
+            mStackSupervisor.moveTasksToFullscreenStackLocked(DOCKED_STACK_ID,
+                    mStackSupervisor.mFocusedStack.getStackId() == DOCKED_STACK_ID);
+        }
+    }
+}