Lock free app animations (2/n): Use SurfaceAninimator

This is the main CL that switches over from using the legacy
animation system to using the new SurfaceAnimator for app
window animations. Also moves applyAnimationLocked to
AppWindowToken.

AppWindowAnimator still has a bunch of state that needs to be moved
into AppWindowToken in future CL's.

Test: go/wm-smoke
Bug: 64674361
Change-Id: Ifc83cbac281ce0654ecd8a1c2ca9c24a4d87c1d1
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 80c302b..da144e6 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -20,6 +20,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -54,19 +55,22 @@
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.animation.Animation;
 import android.view.IApplicationToken;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.view.animation.Transformation;
 
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.input.InputApplicationHandle;
@@ -189,6 +193,21 @@
      */
     private boolean mCanTurnScreenOn = true;
 
+    /**
+     * If we are running an animation, this determines the transition type. Must be one of
+     * AppTransition.TRANSIT_* constants.
+     */
+    private int mTransit;
+
+    /**
+     * If we are running an animation, this determines the flags during this animation. Must be a
+     * bitwise combination of AppTransition.TRANSIT_FLAG_* constants.
+     */
+    private int mTransitFlags;
+
+    /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
+    private boolean mLastSurfaceShowing;
+
     AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
             DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
             boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
@@ -206,7 +225,7 @@
         mRotationAnimationHint = rotationAnimationHint;
 
         // Application tokens start out hidden.
-        hidden = true;
+        setHidden(true);
         hiddenRequested = true;
     }
 
@@ -218,7 +237,7 @@
         mVoiceInteraction = voiceInteraction;
         mFillsParent = fillsParent;
         mInputApplicationHandle = new InputApplicationHandle(this);
-        mAppAnimator = new AppWindowAnimator(this, service);
+        mAppAnimator = new AppWindowAnimator();
     }
 
     void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
@@ -262,7 +281,7 @@
         boolean nowGone = mReportedVisibilityResults.nowGone;
 
         boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;
-        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !hidden;
+        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !isHidden();
         if (!nowGone) {
             // If the app is not yet gone, then it can only become visible/drawn.
             if (!nowDrawn) {
@@ -325,19 +344,16 @@
         // transition animation
         // * or this is an opening app and windows are being replaced.
         boolean visibilityChanged = false;
-        if (hidden == visible || (hidden && mIsExiting) || (visible && waitingForReplacement())) {
+        if (isHidden() == visible || (isHidden() && mIsExiting) || (visible && waitingForReplacement())) {
             final AccessibilityController accessibilityController = mService.mAccessibilityController;
             boolean changed = false;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
-                    "Changing app " + this + " hidden=" + hidden + " performLayout=" + performLayout);
+                    "Changing app " + this + " hidden=" + isHidden() + " performLayout=" + performLayout);
 
             boolean runningAppAnimation = false;
 
-            if (mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
-                mAppAnimator.setNullAnimation();
-            }
             if (transit != AppTransition.TRANSIT_UNSET) {
-                if (mService.applyAnimationLocked(this, lp, transit, visible, isVoiceInteraction)) {
+                if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) {
                     delayed = runningAppAnimation = true;
                 }
                 final WindowState window = findMainWindow();
@@ -355,7 +371,8 @@
                 changed |= win.onAppVisibilityChanged(visible, runningAppAnimation);
             }
 
-            hidden = hiddenRequested = !visible;
+            setHidden(!visible);
+            hiddenRequested = !visible;
             visibilityChanged = true;
             if (!visible) {
                 stopFreezingScreen(true, true);
@@ -373,7 +390,7 @@
             }
 
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this
-                    + ": hidden=" + hidden + " hiddenRequested=" + hiddenRequested);
+                    + ": hidden=" + isHidden() + " hiddenRequested=" + hiddenRequested);
 
             if (changed) {
                 mService.mInputMonitor.setUpdateInputWindowsNeededLw();
@@ -386,7 +403,7 @@
             }
         }
 
-        if (mAppAnimator.animation != null) {
+        if (isReallyAnimating()) {
             delayed = true;
         }
 
@@ -414,7 +431,7 @@
             // no animation but there will still be a transition set.
             // We still need to delay hiding the surface such that it
             // can be synchronized with showing the next surface in the transition.
-            if (hidden && !delayed && !mService.mAppTransition.isTransitionSet()) {
+            if (isHidden() && !delayed && !mService.mAppTransition.isTransitionSet()) {
                 SurfaceControl.openTransaction();
                 for (int i = mChildren.size() - 1; i >= 0; i--) {
                     mChildren.get(i).mWinAnimator.hide("immediately hidden");
@@ -487,7 +504,7 @@
     boolean isVisible() {
         // If the app token isn't hidden then it is considered visible and there is no need to check
         // its children windows to see if they are visible.
-        return !hidden;
+        return !isHidden();
     }
 
     @Override
@@ -533,7 +550,7 @@
         }
 
         if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app " + this + " delayed=" + delayed
-                + " animation=" + mAppAnimator.animation + " animating=" + mAppAnimator.animating);
+                + " animation=" + getAnimation() + " animating=" + isSelfAnimating());
 
         if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
                 + this + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
@@ -545,7 +562,7 @@
         // If this window was animating, then we need to ensure that the app transition notifies
         // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
         // add to that list now
-        if (mAppAnimator.animating) {
+        if (isSelfAnimating()) {
             mService.mNoAnimationNotifyOnTransitionFinished.add(token);
         }
 
@@ -561,8 +578,7 @@
         } else {
             // Make sure there is no animation running on this token, so any windows associated
             // with it will be removed as soon as their animations are complete
-            mAppAnimator.clearAnimation();
-            mAppAnimator.animating = false;
+            cancelAnimation();
             if (stack != null) {
                 stack.mExitingAppTokens.remove(this);
             }
@@ -710,7 +726,7 @@
                 // We set the hidden state to false for the token from a transferred starting window.
                 // We now reset it back to true since the starting window was the last window in the
                 // token.
-                hidden = true;
+                setHidden(true);
             }
         } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
             // If this is the last window except for a starting transition window,
@@ -756,14 +772,6 @@
             final WindowState w = mChildren.get(i);
             w.setWillReplaceWindow(animate);
         }
-        if (animate) {
-            // Set-up dummy animation so we can start treating windows associated with this
-            // token like they are in transition before the new app window is ready for us to
-            // run the real transition animation.
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
-                    "setWillReplaceWindow() Setting dummy animation on: " + this);
-            mAppAnimator.setDummyAnimation();
-        }
     }
 
     void setWillReplaceChildWindows() {
@@ -1007,7 +1015,7 @@
 
     void startFreezingScreen() {
         if (DEBUG_ORIENTATION) logWithStack(TAG, "Set freezing of " + appToken + ": hidden="
-                + hidden + " freezing=" + mAppAnimator.freezingScreen + " hiddenRequested="
+                + isHidden() + " freezing=" + mAppAnimator.freezingScreen + " hiddenRequested="
                 + hiddenRequested);
         if (!hiddenRequested) {
             if (!mAppAnimator.freezingScreen) {
@@ -1111,14 +1119,14 @@
                 if (fromToken.firstWindowDrawn) {
                     firstWindowDrawn = true;
                 }
-                if (!fromToken.hidden) {
-                    hidden = false;
+                if (!fromToken.isHidden()) {
+                    setHidden(false);
                     hiddenRequested = false;
                     mHiddenSetFromTransferredStartingWindow = true;
                 }
                 setClientHidden(fromToken.mClientHidden);
-                fromToken.mAppAnimator.transferCurrentAnimation(
-                        mAppAnimator, tStartingWindow.mWinAnimator);
+
+                // TODO: Transfer animation
 
                 mService.updateFocusedWindowLocked(
                         UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
@@ -1142,17 +1150,8 @@
             return true;
         }
 
-        final AppWindowAnimator tAppAnimator = fromToken.mAppAnimator;
-        final AppWindowAnimator wAppAnimator = mAppAnimator;
-        if (tAppAnimator.thumbnail != null) {
-            // The old token is animating with a thumbnail, transfer that to the new token.
-            if (wAppAnimator.thumbnail != null) {
-                wAppAnimator.thumbnail.destroy();
-            }
-            wAppAnimator.thumbnail = tAppAnimator.thumbnail;
-            wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
-            tAppAnimator.thumbnail = null;
-        }
+        // TODO: Transfer thumbnail
+
         return false;
     }
 
@@ -1207,7 +1206,7 @@
 
         // The token has now changed state to having all windows shown...  what to do, what to do?
         if (mAppAnimator.freezingScreen) {
-            mAppAnimator.showAllWindowsLocked();
+            showAllWindowsLocked();
             stopFreezingScreen(false, true);
             if (DEBUG_ORIENTATION) Slog.i(TAG,
                     "Setting mOrientationChangeComplete=true because wtoken " + this
@@ -1220,7 +1219,7 @@
 
             // We can now show all of the drawn windows!
             if (!mService.mOpeningApps.contains(this)) {
-                mAppAnimator.showAllWindowsLocked();
+                showAllWindowsLocked();
             }
         }
     }
@@ -1310,13 +1309,13 @@
         if (!allDrawn && w.mightAffectAllDrawn()) {
             if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
                 Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
-                        + ", isAnimationSet=" + winAnimator.isAnimationSet());
+                        + ", isAnimationSet=" + isSelfAnimating());
                 if (!w.isDrawnLw()) {
                     Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
                             + " pv=" + w.mPolicyVisibility
                             + " mDrawState=" + winAnimator.drawStateToString()
                             + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested
-                            + " a=" + winAnimator.isAnimationSet());
+                            + " a=" + isSelfAnimating());
                 }
             }
 
@@ -1346,21 +1345,6 @@
     }
 
     @Override
-    void stepAppWindowsAnimation(long currentTime) {
-        mAppAnimator.wasAnimating = mAppAnimator.animating;
-        if (mAppAnimator.stepAnimationLocked(currentTime)) {
-            mAppAnimator.animating = true;
-            mService.mAnimator.setAnimating(true);
-            mService.mAnimator.mAppWindowAnimating = true;
-        } else if (mAppAnimator.wasAnimating) {
-            // stopped animating, do one more pass through the layout
-            setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
-                    DEBUG_LAYOUT_REPEATS ? "appToken " + this + " done" : null);
-            if (DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + this);
-        }
-    }
-
-    @Override
     boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
         // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
         // before the non-exiting app tokens. So, we skip the exiting app tokens here.
@@ -1515,14 +1499,179 @@
         return mAppAnimator.animLayerAdjustment;
     }
 
-    @Override
-    boolean isSelfAnimating() {
-        return mAppAnimator.isAnimating();
+    boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
+            boolean isVoiceInteraction) {
+
+        if (mService.mDisableTransitionAnimation) {
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                Slog.v(TAG_WM, "applyAnimation: transition animation is disabled. atoken=" + this);
+            }
+            cancelAnimation();
+            return false;
+        }
+
+        // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
+        // to animate and it can cause strange artifacts when we unfreeze the display if some
+        // different animation is running.
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
+        if (okToAnimate()) {
+            final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
+            if (a != null) {
+                final AnimationAdapter adapter = new LocalAnimationAdapter(
+                        new WindowAnimationSpec(a, new Point()), mService.mSurfaceAnimationRunner);
+                startAnimation(getPendingTransaction(), adapter, !isVisible());
+                if (a.getZAdjustment() == Animation.ZORDER_TOP) {
+                    mAppAnimator.animLayerAdjustment = 1;
+                    getDisplayContent().assignWindowLayers(false /* setLayoutNeeded */);
+                }
+                mTransit = transit;
+                mTransitFlags = mService.mAppTransition.getTransitFlags();
+                // TODO: Skip first frame and app stack clip mode.
+            }
+        } else {
+            cancelAnimation();
+        }
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+        return isReallyAnimating();
+    }
+
+    private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+            boolean isVoiceInteraction) {
+        final DisplayContent displayContent = getTask().getDisplayContent();
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final int width = displayInfo.appWidth;
+        final int height = displayInfo.appHeight;
+        if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG_WM,
+                "applyAnimation: atoken=" + this);
+
+        // Determine the visible rect to calculate the thumbnail clip
+        final WindowState win = findMainWindow();
+        final Rect frame = new Rect(0, 0, width, height);
+        final Rect displayFrame = new Rect(0, 0,
+                displayInfo.logicalWidth, displayInfo.logicalHeight);
+        final Rect insets = new Rect();
+        final Rect stableInsets = new Rect();
+        Rect surfaceInsets = null;
+        final boolean freeform = win != null && win.inFreeformWindowingMode();
+        if (win != null) {
+            // Containing frame will usually cover the whole screen, including dialog windows.
+            // For freeform workspace windows it will not cover the whole screen and it also
+            // won't exactly match the final freeform window frame (e.g. when overlapping with
+            // the status bar). In that case we need to use the final frame.
+            if (freeform) {
+                frame.set(win.mFrame);
+            } else {
+                frame.set(win.mContainingFrame);
+            }
+            surfaceInsets = win.getAttrs().surfaceInsets;
+            insets.set(win.mContentInsets);
+            stableInsets.set(win.mStableInsets);
+        }
+
+        if (mLaunchTaskBehind) {
+            // Differentiate the two animations. This one which is briefly on the screen
+            // gets the !enter animation, and the other activity which remains on the
+            // screen gets the enter animation. Both appear in the mOpeningApps set.
+            enter = false;
+        }
+        if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "Loading animation for app transition."
+                + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
+                + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
+        final Configuration displayConfig = displayContent.getConfiguration();
+        final Animation a = mService.mAppTransition.loadAnimation(lp, transit, enter,
+                displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
+                surfaceInsets, stableInsets, isVoiceInteraction, freeform, getTask().mTaskId);
+        if (a != null) {
+            if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+            final int containingWidth = frame.width();
+            final int containingHeight = frame.height();
+            a.initialize(containingWidth, containingHeight, width, height);
+            a.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+        }
+        return a;
+    }
+
+    /**
+     * This must be called while inside a transaction.
+     */
+    void showAllWindowsLocked() {
+        forAllWindows(windowState -> {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + windowState);
+            windowState.performShowLocked();
+        }, false /* traverseTopToBottom */);
     }
 
     @Override
-    void dump(PrintWriter pw, String prefix) {
-        super.dump(pw, prefix);
+    protected void onAnimationFinished() {
+        super.onAnimationFinished();
+
+        mTransit = TRANSIT_UNSET;
+        mTransitFlags = 0;
+        mAppAnimator.animLayerAdjustment = 0;
+
+        setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
+                "AppWindowToken");
+
+        if (mService.mInputMethodTarget != null && mService.mInputMethodTarget.mAppToken == this) {
+            getDisplayContent().computeImeTarget(true /* updateImeTarget */);
+        }
+
+        if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + this
+                + ": reportedVisible=" + reportedVisible
+                + " okToDisplay=" + okToDisplay()
+                + " okToAnimate=" + okToAnimate()
+                + " startingDisplayed=" + startingDisplayed);
+
+        // WindowState.onExitAnimationDone might modify the children list, so make a copy and then
+        // traverse the copy.
+        final ArrayList<WindowState> children = new ArrayList<>(mChildren);
+        children.forEach(WindowState::onExitAnimationDone);
+
+        mService.mAppTransition.notifyAppTransitionFinishedLocked(token);
+        scheduleAnimation();
+    }
+
+    @Override
+    boolean isAppAnimating() {
+        return isSelfAnimating();
+    }
+
+    @Override
+    boolean isSelfAnimating() {
+        // If we are about to start a transition, we also need to be considered animating.
+        return isWaitingForTransitionStart() || isReallyAnimating();
+    }
+
+    /**
+     * @return True if and only if we are actually running an animation. Note that
+     *         {@link #isSelfAnimating} also returns true if we are waiting for an animation to
+     *         start.
+     */
+    private boolean isReallyAnimating() {
+        return super.isSelfAnimating();
+    }
+
+    boolean isWaitingForTransitionStart() {
+        return mService.mAppTransition.isTransitionSet()
+                && (mService.mOpeningApps.contains(this) || mService.mClosingApps.contains(this));
+    }
+
+    public int getTransit() {
+        return mTransit;
+    }
+
+    int getTransitFlags() {
+        return mTransitFlags;
+    }
+
+    void clearThumbnail() {
+        // TODO
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         if (appToken != null) {
             pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction);
         }
@@ -1580,9 +1729,27 @@
         if (mRemovingFromDisplay) {
             pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
         }
-        if (mAppAnimator.isAnimating()) {
-            mAppAnimator.dump(pw, prefix + "  ");
+    }
+
+    @Override
+    void setHidden(boolean hidden) {
+        super.setHidden(hidden);
+        scheduleAnimation();
+    }
+
+    @Override
+    void prepareSurfaces() {
+        // isSelfAnimating also returns true when we are about to start a transition, so we need
+        // to check super here.
+        final boolean reallyAnimating = super.isSelfAnimating();
+        final boolean show = !isHidden() || reallyAnimating;
+        if (show && !mLastSurfaceShowing) {
+            mPendingTransaction.show(mSurfaceControl);
+        } else if (!show && mLastSurfaceShowing) {
+            mPendingTransaction.hide(mSurfaceControl);
         }
+        mLastSurfaceShowing = show;
+        super.prepareSurfaces();
     }
 
     @CallSuper