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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9752a57..076c0e4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2126,14 +2126,14 @@
         mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
             @Override
             public int onAppTransitionStartingLocked(int transit, IBinder openToken,
-                    IBinder closeToken,
-                    Animation openAnimation, Animation closeAnimation) {
-                return handleStartTransitionForKeyguardLw(transit, openAnimation);
+                    IBinder closeToken, long duration, long statusBarAnimationStartTime,
+                    long statusBarAnimationDuration) {
+                return handleStartTransitionForKeyguardLw(transit, duration);
             }
 
             @Override
             public void onAppTransitionCancelledLocked(int transit) {
-                handleStartTransitionForKeyguardLw(transit, null /* transit */);
+                handleStartTransitionForKeyguardLw(transit, 0 /* duration */);
             }
         });
         mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
@@ -3989,7 +3989,7 @@
         }
     }
 
-    private int handleStartTransitionForKeyguardLw(int transit, @Nullable Animation anim) {
+    private int handleStartTransitionForKeyguardLw(int transit, long duration) {
         if (mKeyguardOccludedChanged) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
                     + mPendingKeyguardOccluded);
@@ -4000,13 +4000,7 @@
         }
         if (AppTransition.isKeyguardGoingAwayTransit(transit)) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
-            final long startTime = anim != null
-                    ? SystemClock.uptimeMillis() + anim.getStartOffset()
-                    : SystemClock.uptimeMillis();
-            final long duration = anim != null
-                    ? anim.getDuration()
-                    : 0;
-            startKeyguardExitAnimation(startTime, duration);
+            startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
         }
         return 0;
     }
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
index af7e91c..e6e4d7f 100644
--- a/services/core/java/com/android/server/policy/StatusBarController.java
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -37,8 +37,6 @@
  */
 public class StatusBarController extends BarController {
 
-    private static final long TRANSITION_DURATION = 120L;
-
     private final AppTransitionListener mAppTransitionListener
             = new AppTransitionListener() {
 
@@ -57,17 +55,15 @@
 
         @Override
         public int onAppTransitionStartingLocked(int transit, IBinder openToken,
-                IBinder closeToken, final Animation openAnimation, final Animation closeAnimation) {
+                IBinder closeToken, long duration, long statusBarAnimationStartTime,
+                long statusBarAnimationDuration) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     StatusBarManagerInternal statusbar = getStatusBarInternal();
                     if (statusbar != null) {
-                        long startTime = calculateStatusBarTransitionStartTime(openAnimation,
-                                closeAnimation);
-                        long duration = closeAnimation != null || openAnimation != null
-                                ? TRANSITION_DURATION : 0;
-                        statusbar.appTransitionStarting(startTime, duration);
+                        statusbar.appTransitionStarting(statusBarAnimationStartTime,
+                                statusBarAnimationDuration);
                     }
                 }
             });
@@ -128,72 +124,4 @@
     public AppTransitionListener getAppTransitionListener() {
         return mAppTransitionListener;
     }
-
-    /**
-     * For a given app transition with {@code openAnimation} and {@code closeAnimation}, this
-     * calculates the timings for the corresponding status bar transition.
-     *
-     * @return the desired start time of the status bar transition, in uptime millis
-     */
-    private static long calculateStatusBarTransitionStartTime(Animation openAnimation,
-            Animation closeAnimation) {
-        if (openAnimation != null && closeAnimation != null) {
-            TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
-            TranslateAnimation closeTranslateAnimation = findTranslateAnimation(closeAnimation);
-            if (openTranslateAnimation != null) {
-
-                // Some interpolators are extremely quickly mostly finished, but not completely. For
-                // our purposes, we need to find the fraction for which ther interpolator is mostly
-                // there, and use that value for the calculation.
-                float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
-                return SystemClock.uptimeMillis()
-                        + openTranslateAnimation.getStartOffset()
-                        + (long)(openTranslateAnimation.getDuration()*t) - TRANSITION_DURATION;
-            } else if (closeTranslateAnimation != null) {
-                return SystemClock.uptimeMillis();
-            } else {
-                return SystemClock.uptimeMillis();
-            }
-        } else {
-            return SystemClock.uptimeMillis();
-        }
-    }
-
-    /**
-     * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
-     *
-     * @return the found animation, {@code null} otherwise
-     */
-    private static TranslateAnimation findTranslateAnimation(Animation animation) {
-        if (animation instanceof TranslateAnimation) {
-            return (TranslateAnimation) animation;
-        } else if (animation instanceof AnimationSet) {
-            AnimationSet set = (AnimationSet) animation;
-            for (int i = 0; i < set.getAnimations().size(); i++) {
-                Animation a = set.getAnimations().get(i);
-                if (a instanceof TranslateAnimation) {
-                    return (TranslateAnimation) a;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
-     * {@code interpolator(t + eps) > 0.99}.
-     */
-    private static float findAlmostThereFraction(Interpolator interpolator) {
-        float val = 0.5f;
-        float adj = 0.25f;
-        while (adj >= 0.01f) {
-            if (interpolator.getInterpolation(val) < 0.99f) {
-                val += adj;
-            } else {
-                val -= adj;
-            }
-            adj /= 2;
-        }
-        return val;
-    }
 }
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 84d47b4..ed4543e 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -30,6 +30,8 @@
  */
 interface AnimationAdapter {
 
+    long STATUS_BAR_TRANSITION_DURATION = 120L;
+
     /**
      * @return Whether we should detach the wallpaper during the animation.
      * @see Animation#setDetachWallpaper
@@ -66,4 +68,13 @@
      * @return The approximate duration of the animation, in milliseconds.
      */
     long getDurationHint();
+
+    /**
+     * If this animation is run as an app opening animation, this calculates the start time for all
+     * status bar transitions that happen as part of the app opening animation, which will be
+     * forwarded to SystemUI.
+     *
+     * @return the desired start time of the status bar transition, in uptime millis
+     */
+    long getStatusBarTransitionsStartTime();
 }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 4a74e29..2ac7583 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -38,7 +38,6 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -63,6 +62,7 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArraySet;
@@ -415,17 +415,23 @@
      * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
      *         layout pass needs to be done
      */
-    int goodToGo(int transit, AppWindowAnimator topOpeningAppAnimator,
-            AppWindowAnimator topClosingAppAnimator, ArraySet<AppWindowToken> openingApps,
+    int goodToGo(int transit, AppWindowToken topOpeningApp,
+            AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps,
             ArraySet<AppWindowToken> closingApps) {
         mNextAppTransition = TRANSIT_UNSET;
         mNextAppTransitionFlags = 0;
         setAppTransitionState(APP_STATE_RUNNING);
+        final AnimationAdapter topOpeningAnim = topOpeningApp != null
+                ? topOpeningApp.getAnimation()
+                : null;
         int redoLayout = notifyAppTransitionStartingLocked(transit,
-                topOpeningAppAnimator != null ? topOpeningAppAnimator.mAppToken.token : null,
-                topClosingAppAnimator != null ? topClosingAppAnimator.mAppToken.token : null,
-                topOpeningAppAnimator != null ? topOpeningAppAnimator.animation : null,
-                topClosingAppAnimator != null ? topClosingAppAnimator.animation : null);
+                topOpeningApp != null ? topOpeningApp.token : null,
+                topClosingApp != null ? topClosingApp.token : null,
+                topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
+                topOpeningAnim != null
+                        ? topOpeningAnim.getStatusBarTransitionsStartTime()
+                        : SystemClock.uptimeMillis(),
+                AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
         mService.getDefaultDisplayContentLocked().getDockedDividerController()
                 .notifyAppTransitionStarting(openingApps, transit);
 
@@ -433,8 +439,8 @@
         // ended it already then we don't need to wait.
         if (transit == TRANSIT_DOCK_TASK_FROM_RECENTS && !mProlongedAnimationsEnded) {
             for (int i = openingApps.size() - 1; i >= 0; i--) {
-                final AppWindowAnimator appAnimator = openingApps.valueAt(i).mAppAnimator;
-                appAnimator.startProlongAnimation(PROLONG_ANIMATION_AT_START);
+                final AppWindowToken app = openingApps.valueAt(i);
+                app.startDelayingAnimationStart();
             }
         }
         return redoLayout;
@@ -504,11 +510,12 @@
     }
 
     private int notifyAppTransitionStartingLocked(int transit, IBinder openToken,
-            IBinder closeToken, Animation openAnimation, Animation closeAnimation) {
+            IBinder closeToken, long duration, long statusBarAnimationStartTime,
+            long statusBarAnimationDuration) {
         int redoLayout = 0;
         for (int i = 0; i < mListeners.size(); i++) {
             redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken,
-                    closeToken, openAnimation, closeAnimation);
+                    closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration);
         }
         return redoLayout;
     }
@@ -1885,9 +1892,6 @@
                             mNextAppTransitionFutureCallback, null /* finishedCallback */,
                             mNextAppTransitionScaleUp);
                     mNextAppTransitionFutureCallback = null;
-                    if (specs != null) {
-                        mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
-                    }
                 }
                 mService.requestTraversal();
             });
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index fbefd59d..6274745 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -11,51 +11,25 @@
  * 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.
+ * limitations under the License
  */
 
 package com.android.server.wm;
 
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 
-import android.graphics.Matrix;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.view.Choreographer;
-import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
 
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
+// TODO: Move all remaining fields to AppWindowToken and remove this class.
 public class AppWindowAnimator {
     static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowAnimator" : TAG_WM;
 
-    private static final int PROLONG_ANIMATION_DISABLED = 0;
     static final int PROLONG_ANIMATION_AT_END = 1;
     static final int PROLONG_ANIMATION_AT_START = 2;
 
-    final AppWindowToken mAppToken;
-    final WindowManagerService mService;
-    final WindowAnimator mAnimator;
-
-    boolean animating;
-    boolean wasAnimating;
-    Animation animation;
-    boolean hasTransformation;
-    final Transformation transformation = new Transformation();
-
     // Have we been asked to have this token keep the screen frozen?
     // Protect with mAnimator.
     boolean freezingScreen;
@@ -78,400 +52,11 @@
     // requires that the duration of the two animations are the same.
     SurfaceControl thumbnail;
     int thumbnailTransactionSeq;
-    private int mThumbnailLayer;
 
     Animation thumbnailAnimation;
-    final Transformation thumbnailTransformation = new Transformation();
+
     // This flag indicates that the destruction of the thumbnail surface is synchronized with
     // another animation, so defer the destruction of this thumbnail surface for a single frame
     // after the secondary animation completes.
     boolean deferThumbnailDestruction;
-    // This flag is set if the animator has deferThumbnailDestruction set and has reached the final
-    // frame of animation.  It will extend the animation by one frame and then clean up afterwards.
-    boolean deferFinalFrameCleanup;
-    // If true when the animation hits the last frame, it will keep running on that last frame.
-    // This is used to synchronize animation with Recents and we wait for Recents to tell us to
-    // finish or for a new animation be set as fail-safe mechanism.
-    private int mProlongAnimation;
-    // Whether the prolong animation can be removed when animation is set. The purpose of this is
-    // that if recents doesn't tell us to remove the prolonged animation, we will get rid of it
-    // when new animation is set.
-    private boolean mClearProlongedAnimation;
-    private int mTransit;
-    private int mTransitFlags;
-
-    /** True if the current animation was transferred from another AppWindowAnimator.
-     *  See {@link #transferCurrentAnimation}*/
-    boolean usingTransferredAnimation = false;
-
-    private boolean mSkipFirstFrame = false;
-    private int mStackClip = STACK_CLIP_BEFORE_ANIM;
-
-    static final Animation sDummyAnimation = new DummyAnimation();
-
-    public AppWindowAnimator(final AppWindowToken atoken, WindowManagerService service) {
-        mAppToken = atoken;
-        mService = service;
-        mAnimator = mService.mAnimator;
-    }
-
-    public void setAnimation(Animation anim, int width, int height, int parentWidth,
-            int parentHeight, boolean skipFirstFrame, int stackClip, int transit,
-            int transitFlags) {
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
-                + ": " + anim + " wxh=" + width + "x" + height
-                + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
-        animation = anim;
-        animating = false;
-        if (!anim.isInitialized()) {
-            anim.initialize(width, height, parentWidth, parentHeight);
-        }
-        anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
-        anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
-        int zorder = anim.getZAdjustment();
-        int adj = 0;
-        if (zorder == Animation.ZORDER_TOP) {
-            adj = TYPE_LAYER_OFFSET;
-        } else if (zorder == Animation.ZORDER_BOTTOM) {
-            adj = -TYPE_LAYER_OFFSET;
-        }
-
-        if (animLayerAdjustment != adj) {
-            animLayerAdjustment = adj;
-            updateLayers();
-        }
-        // Start out animation gone if window is gone, or visible if window is visible.
-        transformation.clear();
-        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
-        hasTransformation = true;
-        mStackClip = stackClip;
-
-        mSkipFirstFrame = skipFirstFrame;
-        mTransit = transit;
-        mTransitFlags = transitFlags;
-
-        if (!mAppToken.fillsParent()) {
-            anim.setBackgroundColor(0);
-        }
-        if (mClearProlongedAnimation) {
-            mProlongAnimation = PROLONG_ANIMATION_DISABLED;
-        } else {
-            mClearProlongedAnimation = true;
-        }
-    }
-
-    public void setDummyAnimation() {
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken
-                + " hasContentToDisplay=" + mAppToken.hasContentToDisplay());
-        animation = sDummyAnimation;
-        hasTransformation = true;
-        transformation.clear();
-        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
-    }
-
-    void setNullAnimation() {
-        animation = null;
-        usingTransferredAnimation = false;
-    }
-
-    public void clearAnimation() {
-        if (animation != null) {
-            animating = true;
-        }
-        clearThumbnail();
-        setNullAnimation();
-        if (mAppToken.deferClearAllDrawn) {
-            mAppToken.clearAllDrawn();
-        }
-        mStackClip = STACK_CLIP_BEFORE_ANIM;
-        mTransit = TRANSIT_UNSET;
-        mTransitFlags = 0;
-    }
-
-    public boolean isAnimating() {
-        return animation != null || mAppToken.inPendingTransaction;
-    }
-
-    /**
-     * @return whether an animation is about to start, i.e. the animation is set already but we
-     *         haven't processed the first frame yet.
-     */
-    boolean isAnimationStarting() {
-        return animation != null && !animating;
-    }
-
-    public int getTransit() {
-        return mTransit;
-    }
-
-    int getTransitFlags() {
-        return mTransitFlags;
-    }
-
-    public void clearThumbnail() {
-        if (thumbnail != null) {
-            thumbnail.hide();
-            mService.mWindowPlacerLocked.destroyAfterTransaction(thumbnail);
-            thumbnail = null;
-        }
-        deferThumbnailDestruction = false;
-    }
-
-    int getStackClip() {
-        return mStackClip;
-    }
-
-    void transferCurrentAnimation(
-            AppWindowAnimator toAppAnimator, WindowStateAnimator transferWinAnimator) {
-
-        if (animation != null) {
-            toAppAnimator.animation = animation;
-            toAppAnimator.animating = animating;
-            toAppAnimator.animLayerAdjustment = animLayerAdjustment;
-            setNullAnimation();
-            animLayerAdjustment = 0;
-            toAppAnimator.updateLayers();
-            updateLayers();
-            toAppAnimator.usingTransferredAnimation = true;
-            toAppAnimator.mTransit = mTransit;
-        }
-        if (transferWinAnimator != null) {
-            toAppAnimator.hasTransformation = transferWinAnimator.mAppAnimator.hasTransformation;
-            if (toAppAnimator.hasTransformation) {
-                toAppAnimator.transformation.set(transferWinAnimator.mAppAnimator.transformation);
-            } else {
-                toAppAnimator.transformation.clear();
-            }
-            transferWinAnimator.mAppAnimator = toAppAnimator;
-        }
-    }
-
-    private void updateLayers() {
-        mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-    }
-
-    private void stepThumbnailAnimation(long currentTime) {
-        thumbnailTransformation.clear();
-        final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime);
-        thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation);
-
-        ScreenRotationAnimation screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
-        final boolean screenAnimation = screenRotationAnimation != null
-                && screenRotationAnimation.isAnimating();
-        if (screenAnimation) {
-            thumbnailTransformation.postCompose(screenRotationAnimation.getEnterTransformation());
-        }
-        // cache often used attributes locally
-        final float tmpFloats[] = mService.mTmpFloats;
-        thumbnailTransformation.getMatrix().getValues(tmpFloats);
-        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
-                "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X]
-                + ", " + tmpFloats[Matrix.MTRANS_Y]);
-        thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
-        if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
-                "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
-                + " layer=" + mThumbnailLayer
-                + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
-                + "," + tmpFloats[Matrix.MSKEW_Y]
-                + "][" + tmpFloats[Matrix.MSKEW_X]
-                + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
-        thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
-                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
-        thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
-    }
-
-    /**
-     * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
-     * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
-     * and keep producing the first frame of the animation.
-     */
-    private long getAnimationFrameTime(Animation animation, long currentTime) {
-        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
-            animation.setStartTime(currentTime);
-            return currentTime + 1;
-        }
-        return currentTime;
-    }
-
-    private boolean stepAnimation(long currentTime) {
-        if (animation == null) {
-            return false;
-        }
-        transformation.clear();
-        final long animationFrameTime = getAnimationFrameTime(animation, currentTime);
-        boolean hasMoreFrames = animation.getTransformation(animationFrameTime, transformation);
-        if (!hasMoreFrames) {
-            if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
-                // We are deferring the thumbnail destruction, so extend the animation for one more
-                // (dummy) frame before we clean up
-                deferFinalFrameCleanup = true;
-                hasMoreFrames = true;
-            } else {
-                if (false && DEBUG_ANIM) Slog.v(TAG,
-                        "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames +
-                        ", xform=" + transformation + ", mProlongAnimation=" + mProlongAnimation);
-                deferFinalFrameCleanup = false;
-                if (mProlongAnimation == PROLONG_ANIMATION_AT_END) {
-                    hasMoreFrames = true;
-                } else {
-                    setNullAnimation();
-                    clearThumbnail();
-                    if (DEBUG_ANIM) Slog.v(TAG, "Finished animation in " + mAppToken + " @ "
-                            + currentTime);
-                }
-            }
-        }
-        hasTransformation = hasMoreFrames;
-        return hasMoreFrames;
-    }
-
-    private long getStartTimeCorrection() {
-        if (mSkipFirstFrame) {
-
-            // If the transition is an animation in which the first frame doesn't change the screen
-            // contents at all, we can just skip it and start at the second frame. So we shift the
-            // start time of the animation forward by minus the frame duration.
-            return -Choreographer.getInstance().getFrameIntervalNanos() / TimeUtils.NANOS_PER_MS;
-        } else {
-            return 0;
-        }
-    }
-
-    // This must be called while inside a transaction.
-    boolean stepAnimationLocked(long currentTime) {
-        if (mAppToken.okToAnimate()) {
-            // We will run animations as long as the display isn't frozen.
-
-            if (animation == sDummyAnimation) {
-                // This guy is going to animate, but not yet.  For now count
-                // it as not animating for purposes of scheduling transactions;
-                // when it is really time to animate, this will be set to
-                // a real animation and the next call will execute normally.
-                return false;
-            }
-
-            if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
-                    && animation != null) {
-                if (!animating) {
-                    if (DEBUG_ANIM) Slog.v(TAG,
-                        "Starting animation in " + mAppToken +
-                        " @ " + currentTime + " scale="
-                        + mService.getTransitionAnimationScaleLocked()
-                        + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating);
-                    long correction = getStartTimeCorrection();
-                    animation.setStartTime(currentTime + correction);
-                    animating = true;
-                    if (thumbnail != null) {
-                        thumbnail.show();
-                        thumbnailAnimation.setStartTime(currentTime + correction);
-                    }
-                    mSkipFirstFrame = false;
-                }
-                if (stepAnimation(currentTime)) {
-                    // animation isn't over, step any thumbnail and that's
-                    // it for now.
-                    if (thumbnail != null) {
-                        stepThumbnailAnimation(currentTime);
-                    }
-                    return true;
-                }
-            }
-        } else if (animation != null) {
-            // If the display is frozen, and there is a pending animation,
-            // clear it and make sure we run the cleanup code.
-            animating = true;
-            animation = null;
-        }
-
-        hasTransformation = false;
-
-        if (!animating && animation == null) {
-            return false;
-        }
-
-        mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "AppWindowToken");
-
-        clearAnimation();
-        animating = false;
-        if (animLayerAdjustment != 0) {
-            animLayerAdjustment = 0;
-            updateLayers();
-        }
-        if (mService.mInputMethodTarget != null
-                && mService.mInputMethodTarget.mAppToken == mAppToken) {
-            mAppToken.getDisplayContent().computeImeTarget(true /* updateImeTarget */);
-        }
-
-        if (DEBUG_ANIM) Slog.v(TAG, "Animation done in " + mAppToken
-                + ": reportedVisible=" + mAppToken.reportedVisible
-                + " okToDisplay=" + mAppToken.okToDisplay()
-                + " okToAnimate=" + mAppToken.okToAnimate()
-                + " startingDisplayed=" + mAppToken.startingDisplayed);
-
-        transformation.clear();
-
-        mAppToken.forAllWindows(WindowState::onExitAnimationDone, false /* traverseTopToBottom */);
-        mService.mAppTransition.notifyAppTransitionFinishedLocked(mAppToken.token);
-        return false;
-    }
-
-    // This must be called while inside a transaction.
-    void showAllWindowsLocked() {
-        mAppToken.forAllWindows(windowState -> {
-            if (DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + windowState);
-            windowState.performShowLocked();
-        }, false /* traverseTopToBottom */);
-    }
-
-    void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
-        pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator);
-        pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
-                pw.print(" allDrawn="); pw.print(allDrawn);
-                pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment);
-        if (lastFreezeDuration != 0) {
-            pw.print(prefix); pw.print("lastFreezeDuration=");
-                    TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println();
-        }
-        if (animating || animation != null) {
-            pw.print(prefix); pw.print("animating="); pw.println(animating);
-            pw.print(prefix); pw.print("animation="); pw.println(animation);
-            pw.print(prefix); pw.print("mTransit="); pw.println(mTransit);
-            pw.print(prefix); pw.print("mTransitFlags="); pw.println(mTransitFlags);
-        }
-        if (hasTransformation) {
-            pw.print(prefix); pw.print("XForm: ");
-                    transformation.printShortString(pw);
-                    pw.println();
-        }
-        if (thumbnail != null) {
-            pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
-                    pw.print(" layer="); pw.println(mThumbnailLayer);
-            pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
-            pw.print(prefix); pw.print("thumbnailTransformation=");
-                    pw.println(thumbnailTransformation.toShortString());
-        }
-    }
-
-    void startProlongAnimation(int prolongType) {
-        mProlongAnimation = prolongType;
-        mClearProlongedAnimation = false;
-    }
-
-    void endProlongedAnimation() {
-        mProlongAnimation = PROLONG_ANIMATION_DISABLED;
-    }
-
-    // This is an animation that does nothing: it just immediately finishes
-    // itself every time it is called.  It is used as a stub animation in cases
-    // where we want to synchronize multiple things that may be animating.
-    static final class DummyAnimation extends Animation {
-        @Override
-        public boolean getTransformation(long currentTime, Transformation outTransformation) {
-            return false;
-        }
-    }
-
 }
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 00a0d3d..73732dd 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 
 import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
@@ -25,7 +24,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
@@ -34,16 +32,13 @@
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Trace;
 import android.util.Slog;
-import android.view.DisplayInfo;
 import android.view.IApplicationToken;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -339,7 +334,7 @@
 
             if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG_WM, "setAppVisibility("
                     + mToken + ", visible=" + visible + "): " + mService.mAppTransition
-                    + " hidden=" + wtoken.hidden + " hiddenRequested="
+                    + " hidden=" + wtoken.isHidden() + " hiddenRequested="
                     + wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6));
 
             mService.mOpeningApps.remove(wtoken);
@@ -364,11 +359,11 @@
                 wtoken.startingMoved = false;
                 // If the token is currently hidden (should be the common case), or has been
                 // stopped, then we need to set up to wait for its windows to be ready.
-                if (wtoken.hidden || wtoken.mAppStopped) {
+                if (wtoken.isHidden() || wtoken.mAppStopped) {
                     wtoken.clearAllDrawn();
 
                     // If the app was already visible, don't reset the waitingToShow state.
-                    if (wtoken.hidden) {
+                    if (wtoken.isHidden()) {
                         wtoken.waitingToShow = true;
                     }
 
@@ -389,21 +384,6 @@
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
             if (wtoken.okToAnimate() && mService.mAppTransition.isTransitionSet()) {
-                // A dummy animation is a placeholder animation which informs others that an
-                // animation is going on (in this case an application transition). If the animation
-                // was transferred from another application/animator, no dummy animator should be
-                // created since an animation is already in progress.
-                if (wtoken.mAppAnimator.usingTransferredAnimation
-                        && wtoken.mAppAnimator.animation == null) {
-                    Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
-                            + ", using null transferred animation!");
-                }
-                if (!wtoken.mAppAnimator.usingTransferredAnimation &&
-                        (!wtoken.startingDisplayed || mService.mSkipAppTransitionAnimation)) {
-                    if (DEBUG_APP_TRANSITIONS) Slog.v(
-                            TAG_WM, "Setting dummy animation on: " + wtoken);
-                    wtoken.mAppAnimator.setDummyAnimation();
-                }
                 wtoken.inPendingTransaction = true;
                 if (visible) {
                     mService.mOpeningApps.add(wtoken);
@@ -423,7 +403,7 @@
                             if (DEBUG_APP_TRANSITIONS) Slog.d(TAG_WM, "TRANSIT_TASK_OPEN_BEHIND, "
                                     + " adding " + focusedToken + " to mOpeningApps");
                             // Force animation to be loaded.
-                            focusedToken.hidden = true;
+                            focusedToken.setHidden(true);
                             mService.mOpeningApps.add(focusedToken);
                         }
                     }
@@ -710,7 +690,7 @@
                 return;
             }
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Clear freezing of " + mToken + ": hidden="
-                    + mContainer.hidden + " freezing=" + mContainer.mAppAnimator.freezingScreen);
+                    + mContainer.isHidden() + " freezing=" + mContainer.mAppAnimator.freezingScreen);
             mContainer.stopFreezingScreen(true, force);
         }
     }
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
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 627af69..274ac04 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -437,15 +437,14 @@
 
         // If this window's app token is running a detached wallpaper animation, make a note so
         // we can ensure the wallpaper is displayed behind it.
-        final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
-        if (appAnimator != null && appAnimator.animation != null
-                && appAnimator.animating) {
-            if ((flags & FLAG_SHOW_WALLPAPER) != 0
-                    && appAnimator.animation.getDetachWallpaper()) {
+        final AppWindowToken atoken = winAnimator.mWin.mAppToken;
+        final AnimationAdapter animation = atoken != null ? atoken.getAnimation() : null;
+        if (animation != null) {
+            if ((flags & FLAG_SHOW_WALLPAPER) != 0 && animation.getDetachWallpaper()) {
                 mTmpWindow = w;
             }
 
-            final int color = appAnimator.animation.getBackgroundColor();
+            final int color = animation.getBackgroundColor();
             if (color != 0) {
                 final TaskStack stack = w.getStack();
                 if (stack != null) {
@@ -528,11 +527,11 @@
                     + " screen changed=" + w.isConfigChanged());
             final AppWindowToken atoken = w.mAppToken;
             if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
-                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
                     + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
                     + " parentHidden=" + w.isParentWindowHidden());
             else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
-                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.hidden
+                    + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
                     + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
                     + " parentHidden=" + w.isParentWindowHidden());
         }
@@ -707,7 +706,7 @@
                 }
             }
             final TaskStack stack = w.getStack();
-            if ((!winAnimator.isWaitingForOpening())
+            if (!winAnimator.isWaitingForOpening()
                     || (stack != null && stack.isAnimatingBounds())) {
                 // Updates the shown frame before we set up the surface. This is needed
                 // because the resizing could change the top-left position (in addition to
@@ -2169,7 +2168,9 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
         final String subPrefix = "  " + prefix;
         pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
@@ -2203,7 +2204,7 @@
         pw.println(prefix + "Application tokens in top down Z order:");
         for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
             final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
-            stack.dump(prefix + "  ", pw);
+            stack.dump(pw, prefix + "  ", dumpAll);
         }
 
         pw.println();
@@ -2215,7 +2216,7 @@
                 pw.print("  Exiting #"); pw.print(i);
                 pw.print(' '); pw.print(token);
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             }
         }
 
@@ -2467,7 +2468,7 @@
                 // to look at all windows below the current target that are in this app, finding the
                 // highest visible one in layering.
                 WindowState highestTarget = null;
-                if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
+                if (token.isSelfAnimating()) {
                     highestTarget = token.getHighestAnimLayerWindow(curTarget);
                 }
 
@@ -2574,7 +2575,7 @@
             pw.print(token);
             if (dumpAll) {
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             } else {
                 pw.println();
             }
@@ -3463,8 +3464,7 @@
                         // 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.
-                        token.mAppAnimator.clearAnimation();
-                        token.mAppAnimator.animating = false;
+                        cancelAnimation();
                         if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
                                 "performLayout: App token exiting now removed" + token);
                         token.removeIfPossible();
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 5fe4565..f546240 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -16,10 +16,9 @@
 
 package com.android.server.wm;
 
-import android.graphics.Point;
+import android.os.SystemClock;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
 
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
@@ -30,6 +29,7 @@
 class LocalAnimationAdapter implements AnimationAdapter {
 
     private final AnimationSpec mSpec;
+
     private final SurfaceAnimationRunner mAnimator;
 
     LocalAnimationAdapter(AnimationSpec spec, SurfaceAnimationRunner animator) {
@@ -64,6 +64,11 @@
         return mSpec.getDuration();
     }
 
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return mSpec.calculateStatusBarTransitionStartTime();
+    }
+
     /**
      * Describes how to apply an animation.
      */
@@ -84,6 +89,13 @@
         }
 
         /**
+         * @see AnimationAdapter#getStatusBarTransitionsStartTime
+         */
+        default long calculateStatusBarTransitionStartTime() {
+            return SystemClock.uptimeMillis();
+        }
+
+        /**
          * @return The duration of the animation.
          */
         long getDuration();
@@ -91,8 +103,8 @@
         /**
          * Called when the spec needs to apply the current animation state to the leash.
          *
-         * @param t The transaction to use to apply a transform.
-         * @param leash The leash to apply the state to.
+         * @param t               The transaction to use to apply a transform.
+         * @param leash           The leash to apply the state to.
          * @param currentPlayTime The current time of the animation.
          */
         void apply(Transaction t, SurfaceControl leash, long currentPlayTime);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e653e7d..2a77c92 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -612,7 +612,7 @@
                         defaultDisplay.pendingLayoutChanges);
         }
 
-        if (!mService.mAnimator.mAppWindowAnimating && mService.mAppTransition.isRunning()) {
+        if (!isAppAnimating() && mService.mAppTransition.isRunning()) {
             // We have finished the animation of an app transition. To do this, we have delayed a
             // lot of operations like showing and hiding apps, moving apps in Z-order, etc. The app
             // token list reflects the correct Z-order, but the window list may now be out of sync
@@ -1035,7 +1035,7 @@
             final int count = mChildren.size();
             for (int i = 0; i < count; ++i) {
                 final DisplayContent displayContent = mChildren.get(i);
-                displayContent.dump("  ", pw);
+                displayContent.dump(pw, "  ", true /* dumpAll */);
             }
         } else {
             pw.println("  NO DISPLAY");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 244eb66..81be595 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -536,14 +536,14 @@
     /** Cancels any running app transitions associated with the task. */
     void cancelTaskWindowTransition() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
-            mChildren.get(i).mAppAnimator.clearAnimation();
+            mChildren.get(i).cancelAnimation();
         }
     }
 
     /** Cancels any running thumbnail transitions associated with the task. */
     void cancelTaskThumbnailTransition() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
-            mChildren.get(i).mAppAnimator.clearThumbnail();
+            mChildren.get(i).clearThumbnail();
         }
     }
 
@@ -677,7 +677,9 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         final String doublePrefix = prefix + "  ";
 
         pw.println(prefix + "taskId=" + mTaskId);
@@ -691,7 +693,7 @@
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final AppWindowToken wtoken = mChildren.get(i);
             pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
-            wtoken.dump(pw, triplePrefix);
+            wtoken.dump(pw, triplePrefix, dumpAll);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9946c6a..a07a8d4 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1284,7 +1284,8 @@
         proto.end(token);
     }
 
-    public void dump(String prefix, PrintWriter pw) {
+    @Override
+     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         pw.println(prefix + "mStackId=" + mStackId);
         pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
         pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
@@ -1300,7 +1301,7 @@
             pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString());
         }
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
-            mChildren.get(taskNdx).dump(prefix + "  ", pw);
+            mChildren.get(taskNdx).dump(pw, prefix + "  ", dumpAll);
         }
         if (mAnimationBackgroundSurfaceIsShown) {
             pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
@@ -1313,7 +1314,7 @@
                 pw.print("  Exiting App #"); pw.print(i);
                 pw.print(' '); pw.print(token);
                 pw.println(':');
-                token.dump(pw, "    ");
+                token.dump(pw, "    ", dumpAll);
             }
         }
     }
@@ -1653,35 +1654,6 @@
         return super.checkCompleteDeferredRemoval();
     }
 
-    void stepAppWindowsAnimation(long currentTime) {
-        super.stepAppWindowsAnimation(currentTime);
-
-        // TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
-        // below but is set in the loop above. See if it really matters...
-
-        // Clear before using.
-        mTmpAppTokens.clear();
-        // We copy the list as things can be removed from the exiting token list while we are
-        // processing.
-        mTmpAppTokens.addAll(mExitingAppTokens);
-        for (int i = 0; i < mTmpAppTokens.size(); i++) {
-            final AppWindowAnimator appAnimator = mTmpAppTokens.get(i).mAppAnimator;
-            appAnimator.wasAnimating = appAnimator.animating;
-            if (appAnimator.stepAnimationLocked(currentTime)) {
-                mService.mAnimator.setAnimating(true);
-                mService.mAnimator.mAppWindowAnimating = true;
-            } else if (appAnimator.wasAnimating) {
-                // stopped animating, do one more pass through the layout
-                appAnimator.mAppToken.setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
-                        "exiting appToken " + appAnimator.mAppToken + " done");
-                if (DEBUG_ANIM) Slog.v(TAG_WM,
-                        "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
-            }
-        }
-        // Clear to avoid holding reference to tokens.
-        mTmpAppTokens.clear();
-    }
-
     @Override
     int getOrientation() {
         return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3ae4549..8a53a2b 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -112,7 +112,7 @@
         mFindResults.resetTopWallpaper = true;
         if (w != winAnimator.mWindowDetachedWallpaper && w.mAppToken != null) {
             // If this window's app token is hidden and not animating, it is of no interest to us.
-            if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) {
+            if (w.mAppToken.isHidden() && !w.mAppToken.isSelfAnimating()) {
                 if (DEBUG_WALLPAPER) Slog.v(TAG,
                         "Skipping hidden and not animating token: " + w);
                 return false;
@@ -130,10 +130,10 @@
         }
 
         final boolean keyguardGoingAwayWithWallpaper = (w.mAppToken != null
-                && AppTransition.isKeyguardGoingAwayTransit(
-                w.mAppToken.mAppAnimator.getTransit())
-                && (w.mAppToken.mAppAnimator.getTransitFlags()
-                & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
+                && w.mAppToken.isSelfAnimating()
+                && AppTransition.isKeyguardGoingAwayTransit(w.mAppToken.getTransit())
+                && (w.mAppToken.getTransitFlags()
+                        & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
 
         boolean needsShowWhenLockedWallpaper = false;
         if ((w.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
@@ -204,18 +204,19 @@
     private boolean isWallpaperVisible(WindowState wallpaperTarget) {
         if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
                 + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
-                + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
-                ? wallpaperTarget.mAppToken.mAppAnimator.animation : null)
+                + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
+                ? wallpaperTarget.mAppToken.isSelfAnimating() : null)
                 + " prev=" + mPrevWallpaperTarget);
         return (wallpaperTarget != null
                 && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null
-                && wallpaperTarget.mAppToken.mAppAnimator.animation != null)))
+                && wallpaperTarget.mAppToken.isSelfAnimating())))
                 || mPrevWallpaperTarget != null;
     }
 
     boolean isWallpaperTargetAnimating() {
         return mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimationSet()
-                && !mWallpaperTarget.mWinAnimator.isDummyAnimation();
+                && (mWallpaperTarget.mAppToken == null
+                        || !mWallpaperTarget.mAppToken.isWaitingForTransitionStart());
     }
 
     void updateWallpaperVisibility() {
@@ -250,7 +251,7 @@
         for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(i);
             token.hideWallpaperToken(wasDeferred, "hideWallpapers");
-            if (DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, "Hiding wallpaper " + token
+            if (DEBUG_WALLPAPER_LIGHT && !token.isHidden()) Slog.d(TAG, "Hiding wallpaper " + token
                     + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
                     + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, "  "));
         }
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 3389f71..c6b3bd3 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -16,12 +16,9 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -56,7 +53,7 @@
             final WindowState wallpaper = mChildren.get(j);
             wallpaper.hideWallpaperWindow(wasDeferred, reason);
         }
-        hidden = true;
+        setHidden(true);
     }
 
     void sendWindowWallpaperCommand(
@@ -92,8 +89,9 @@
         final int dw = displayInfo.logicalWidth;
         final int dh = displayInfo.logicalHeight;
 
-        if (hidden == visible) {
-            hidden = !visible;
+        if (isHidden() == visible) {
+            setHidden(!visible);
+
             // Need to do a layout to ensure the wallpaper now has the correct size.
             mDisplayContent.setLayoutNeeded();
         }
@@ -121,10 +119,10 @@
 
     void updateWallpaperWindows(boolean visible, int animLayerAdj) {
 
-        if (hidden == visible) {
+        if (isHidden() == visible) {
             if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
                     "Wallpaper token " + token + " hidden=" + !visible);
-            hidden = !visible;
+            setHidden(!visible);
             // Need to do a layout to ensure the wallpaper now has the correct size.
             mDisplayContent.setLayoutNeeded();
         }
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index bb25297..9504d45 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -16,11 +16,17 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
+
 import android.graphics.Point;
+import android.os.SystemClock;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.Interpolator;
 import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
 
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 
@@ -61,6 +67,63 @@
         tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
         t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
         t.setAlpha(leash, tmp.transformation.getAlpha());
+        t.setWindowCrop(leash, tmp.transformation.getClipRect());
+    }
+
+    @Override
+    public long calculateStatusBarTransitionStartTime() {
+        TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
+        if (openTranslateAnimation != null) {
+
+            // Some interpolators are extremely quickly mostly finished, but not completely. For
+            // our purposes, we need to find the fraction for which ther interpolator is mostly
+            // there, and use that value for the calculation.
+            float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
+            return SystemClock.uptimeMillis()
+                    + openTranslateAnimation.getStartOffset()
+                    + (long)(openTranslateAnimation.getDuration() * t)
+                    - STATUS_BAR_TRANSITION_DURATION;
+        } else {
+            return SystemClock.uptimeMillis();
+        }
+    }
+
+    /**
+     * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
+     *
+     * @return the found animation, {@code null} otherwise
+     */
+    private static TranslateAnimation findTranslateAnimation(Animation animation) {
+        if (animation instanceof TranslateAnimation) {
+            return (TranslateAnimation) animation;
+        } else if (animation instanceof AnimationSet) {
+            AnimationSet set = (AnimationSet) animation;
+            for (int i = 0; i < set.getAnimations().size(); i++) {
+                Animation a = set.getAnimations().get(i);
+                if (a instanceof TranslateAnimation) {
+                    return (TranslateAnimation) a;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
+     * {@code interpolator(t + eps) > 0.99}.
+     */
+    private static float findAlmostThereFraction(Interpolator interpolator) {
+        float val = 0.5f;
+        float adj = 0.25f;
+        while (adj >= 0.01f) {
+            if (interpolator.getInterpolation(val) < 0.99f) {
+                val += adj;
+            } else {
+                val -= adj;
+            }
+            adj /= 2;
+        }
+        return val;
     }
 
     private static class TmpValues {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 7c56f00..a5b63d8 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -52,14 +52,12 @@
     private boolean mAnimating;
     private boolean mLastAnimating;
 
-    /** Is any app window animating? */
-    boolean mAppWindowAnimating;
-
     final Choreographer.FrameCallback mAnimationFrameCallback;
 
     /** Time of current animation step. Reset on each iteration */
     long mCurrentTime;
 
+    boolean mAppWindowAnimating;
     /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
      * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
     int mAnimTransactionSequence;
@@ -156,7 +154,6 @@
             mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
             mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
             mAnimating = false;
-            mAppWindowAnimating = false;
             if (DEBUG_WINDOW_TRACE) {
                 Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
             }
@@ -170,7 +167,6 @@
                 for (int i = 0; i < numDisplays; i++) {
                     final int displayId = mDisplayContentsAnimators.keyAt(i);
                     final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
-                    dc.stepAppWindowsAnimation(mCurrentTime);
                     DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
 
                     final ScreenRotationAnimation screenRotationAnimation =
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b2b6119..a0c704e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -446,6 +446,20 @@
     }
 
     /**
+     * @return {@code true} if in this subtree of the hierarchy we have an {@link AppWindowToken}
+     *         that is {@link #isSelfAnimating}; {@code false} otherwise.
+     */
+    boolean isAppAnimating() {
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            final WindowContainer wc = mChildren.get(j);
+            if (wc.isAppAnimating()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * @return Whether our own container running an animation at the moment.
      */
     boolean isSelfAnimating() {
@@ -532,14 +546,6 @@
         }
     }
 
-    /** Step currently ongoing animation for App window containers. */
-    void stepAppWindowsAnimation(long currentTime) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            wc.stepAppWindowsAnimation(currentTime);
-        }
-    }
-
     void onAppTransitionDone() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
@@ -1059,6 +1065,7 @@
         return mSurfaceControl.getHeight();
     }
 
+    @CallSuper
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         if (mSurfaceAnimator.isAnimating()) {
             pw.print(prefix); pw.println("ContainerAnimator:");
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 62d2e7d..1935a44 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -118,8 +118,11 @@
          *                of AppTransition.TRANSIT_* values
          * @param openToken the token for the opening app
          * @param closeToken the token for the closing app
-         * @param openAnimation the animation for the opening app
-         * @param closeAnimation the animation for the closing app
+         * @param duration the total duration of the transition
+         * @param statusBarAnimationStartTime the desired start time for all visual animations in
+         *        the status bar caused by this app transition in uptime millis
+         * @param statusBarAnimationDuration the duration for all visual animations in the status
+         *        bar caused by this app transition in millis
          *
          * @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
          * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
@@ -127,7 +130,7 @@
          * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
          */
         public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
-                Animation openAnimation, Animation closeAnimation) {
+                long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) {
             return 0;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 58a5ca4..9678826 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -566,12 +566,10 @@
     int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     Rect mDockedStackCreateBounds;
 
-    private final SparseIntArray mTmpTaskIds = new SparseIntArray();
-
     boolean mForceResizableTasks = false;
     boolean mSupportsPictureInPicture = false;
 
-    private boolean mDisableTransitionAnimation = false;
+    boolean mDisableTransitionAnimation = false;
 
     int getDragLayerLocked() {
         return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
@@ -2279,83 +2277,6 @@
         }
     }
 
-    boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
-            int transit, boolean enter, boolean isVoiceInteraction) {
-        if (mDisableTransitionAnimation) {
-            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
-                Slog.v(TAG_WM,
-                        "applyAnimation: transition animation is disabled. atoken=" + atoken);
-            }
-            atoken.mAppAnimator.clearAnimation();
-            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, "WM#applyAnimationLocked");
-        if (atoken.okToAnimate()) {
-            final DisplayContent displayContent = atoken.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=" + atoken);
-
-            // Determine the visible rect to calculate the thumbnail clip
-            final WindowState win = atoken.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 (atoken.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();
-            Animation a = mAppTransition.loadAnimation(lp, transit, enter, displayConfig.uiMode,
-                    displayConfig.orientation, frame, displayFrame, insets, surfaceInsets,
-                    stableInsets, isVoiceInteraction, freeform, atoken.getTask().mTaskId);
-            if (a != null) {
-                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + atoken);
-                final int containingWidth = frame.width();
-                final int containingHeight = frame.height();
-                atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, width,
-                        height, mAppTransition.canSkipFirstFrame(),
-                        mAppTransition.getAppStackClipMode(),
-                        transit, mAppTransition.getTransitFlags());
-            }
-        } else {
-            atoken.mAppAnimator.clearAnimation();
-        }
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
-        return atoken.mAppAnimator.animation != null;
-    }
-
     boolean checkCallingPermission(String permission, String func) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == myPid()) {
@@ -2701,7 +2622,6 @@
         synchronized (mWindowMap) {
             mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
                     onAnimationFinishedCallback, scaleUp);
-            prolongAnimationsFromSpecs(specs, scaleUp);
 
         }
     }
@@ -2712,27 +2632,6 @@
         }
     }
 
-    void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) {
-        // This is used by freeform <-> recents windows transition. We need to synchronize
-        // the animation with the appearance of the content of recents, so we will make
-        // animation stay on the first or last frame a little longer.
-        mTmpTaskIds.clear();
-        for (int i = specs.length - 1; i >= 0; i--) {
-            mTmpTaskIds.put(specs[i].taskId, 0);
-        }
-        for (final WindowState win : mWindowMap.values()) {
-            final Task task = win.getTask();
-            if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1
-                    && task.inFreeformWindowingMode()) {
-                final AppWindowToken appToken = win.mAppToken;
-                if (appToken != null && appToken.mAppAnimator != null) {
-                    appToken.mAppAnimator.startProlongAnimation(scaleUp ?
-                            PROLONG_ANIMATION_AT_START : PROLONG_ANIMATION_AT_END);
-                }
-            }
-        }
-    }
-
     @Override
     public void overridePendingAppTransitionInPlace(String packageName, int anim) {
         synchronized(mWindowMap) {
@@ -2756,7 +2655,7 @@
             for (final WindowState win : mWindowMap.values()) {
                 final AppWindowToken appToken = win.mAppToken;
                 if (appToken != null && appToken.mAppAnimator != null) {
-                    appToken.mAppAnimator.endProlongedAnimation();
+                    appToken.endDelayingAnimationStart();
                 }
             }
             mAppTransition.notifyProlongedAnimationsEnded();
@@ -2811,15 +2710,6 @@
         }
     }
 
-    void updateTokenInPlaceLocked(AppWindowToken wtoken, int transit) {
-        if (transit != TRANSIT_UNSET) {
-            if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
-                wtoken.mAppAnimator.setNullAnimation();
-            }
-            applyAnimationLocked(wtoken, null, transit, false, false);
-        }
-    }
-
     public void setDockedStackCreateState(int mode, Rect bounds) {
         synchronized (mWindowMap) {
             setDockedStackCreateStateLocked(mode, bounds);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ddc1eac..69182da 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1432,7 +1432,7 @@
      */
     // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
     boolean isWinVisibleLw() {
-        return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
+        return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.isSelfAnimating())
                 && isVisible();
     }
 
@@ -1441,7 +1441,7 @@
      * not the pending requested hidden state.
      */
     boolean isVisibleNow() {
-        return (!mToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
+        return (!mToken.isHidden() || mAttrs.type == TYPE_APPLICATION_STARTING)
                 && isVisible();
     }
 
@@ -1479,7 +1479,7 @@
         final AppWindowToken atoken = mAppToken;
         if (atoken != null) {
             return ((!isParentWindowHidden() && !atoken.hiddenRequested)
-                    || mWinAnimator.isAnimationSet() || atoken.mAppAnimator.animation != null);
+                    || mWinAnimator.isAnimationSet());
         }
         return !isParentWindowHidden() || mWinAnimator.isAnimationSet();
     }
@@ -1513,33 +1513,21 @@
             return false;
         }
         return mHasSurface && mPolicyVisibility && !mDestroying
-                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
-                        || mWinAnimator.isAnimationSet()
-                        || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null)));
+                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.isHidden())
+                        || mWinAnimator.isAnimationSet());
     }
 
     // TODO: Another visibility method that was added late in the release to minimize risk.
     @Override
     public boolean canAffectSystemUiFlags() {
-        final boolean shown = mWinAnimator.getShown();
-
-        // We only consider the app to be exiting when the animation has started. After the app
-        // transition is executed the windows are marked exiting before the new windows have been
-        // shown. Thus, wait considering a window to be exiting after the animation has actually
-        // started.
-        final boolean appAnimationStarting = mAppToken != null
-                && mAppToken.mAppAnimator.isAnimationStarting();
-        final boolean exitingSelf = mAnimatingExit && !appAnimationStarting;
-        final boolean appExiting = mAppToken != null && mAppToken.hidden && !appAnimationStarting;
-
-        final boolean exiting = exitingSelf || mDestroying || appExiting;
         final boolean translucent = mAttrs.alpha == 0.0f;
-
-        // If we are entering with a dummy animation, avoid affecting SystemUI flags until the
-        // transition is starting.
-        final boolean enteringWithDummyAnimation =
-                mWinAnimator.isDummyAnimation() && mWinAnimator.mShownAlpha == 0f;
-        return shown && !exiting && !translucent && !enteringWithDummyAnimation;
+        if (mAppToken == null) {
+            final boolean shown = mWinAnimator.getShown();
+            final boolean exiting = mAnimatingExit || mDestroying;
+            return shown && !exiting && !translucent;
+        } else {
+            return !mAppToken.isHidden();
+        }
     }
 
     /**
@@ -1550,10 +1538,8 @@
     public boolean isDisplayedLw() {
         final AppWindowToken atoken = mAppToken;
         return isDrawnLw() && mPolicyVisibility
-            && ((!isParentWindowHidden() &&
-                    (atoken == null || !atoken.hiddenRequested))
-                        || mWinAnimator.isAnimationSet()
-                        || (atoken != null && atoken.mAppAnimator.animation != null));
+                && ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested))
+                        || mWinAnimator.isAnimationSet());
     }
 
     /**
@@ -1561,8 +1547,7 @@
      */
     @Override
     public boolean isAnimatingLw() {
-        return mWinAnimator.isAnimationSet()
-                || (mAppToken != null && mAppToken.mAppAnimator.animation != null);
+        return isAnimating();
     }
 
     @Override
@@ -1570,7 +1555,7 @@
         final AppWindowToken atoken = mAppToken;
         return mViewVisibility == View.GONE
                 || !mRelayoutCalled
-                || (atoken == null && mToken.hidden)
+                || (atoken == null && mToken.isHidden())
                 || (atoken != null && atoken.hiddenRequested)
                 || isParentWindowHidden()
                 || (mAnimatingExit && !isAnimatingLw())
@@ -1608,8 +1593,7 @@
         // to determine if it's occluding apps.
         return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
                 || (mIsWallpaper && mWallpaperVisible))
-                && isDrawnLw() && !mWinAnimator.isAnimationSet()
-                && (mAppToken == null || mAppToken.mAppAnimator.animation == null);
+                && isDrawnLw() && !mWinAnimator.isAnimationSet();
     }
 
     @Override
@@ -1631,7 +1615,7 @@
             // Starting window that's exiting will be removed when the animation finishes.
             // Mark all relevant flags for that onExitAnimationDone will proceed all the way
             // to actually remove it.
-            if (!visible && isVisibleNow() && mAppToken.mAppAnimator.isAnimating()) {
+            if (!visible && isVisibleNow() && mAppToken.isSelfAnimating()) {
                 mAnimatingExit = true;
                 mRemoveOnExit = true;
                 mWindowRemovalAllowed = true;
@@ -1908,7 +1892,7 @@
                     + " surfaceShowing=" + mWinAnimator.getShown()
                     + " isAnimationSet=" + mWinAnimator.isAnimationSet()
                     + " app-animation="
-                    + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+                    + (mAppToken != null ? mAppToken.isSelfAnimating() : "false")
                     + " mWillReplaceWindow=" + mWillReplaceWindow
                     + " inPendingTransaction="
                     + (mAppToken != null ? mAppToken.inPendingTransaction : false)
@@ -1968,8 +1952,8 @@
                         mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
                     }
                 }
-                final boolean isAnimating =
-                        mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+                final boolean isAnimating = mWinAnimator.isAnimationSet()
+                        && (mAppToken == null || !mAppToken.isWaitingForTransitionStart());
                 final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
                         && mAppToken.isLastWindow(this);
                 // We delay the removal of a window if it has a showing surface that can be used to run
@@ -3697,10 +3681,10 @@
                     + " parentHidden=" + isParentWindowHidden()
                     + " tok.hiddenRequested="
                     + (mAppToken != null && mAppToken.hiddenRequested)
-                    + " tok.hidden=" + (mAppToken != null && mAppToken.hidden)
+                    + " tok.hidden=" + (mAppToken != null && mAppToken.isHidden())
                     + " animationSet=" + mWinAnimator.isAnimationSet()
                     + " tok animating="
-                    + (mWinAnimator.mAppAnimator != null && mWinAnimator.mAppAnimator.animating)
+                    + (mAppToken != null && mAppToken.isSelfAnimating())
                     + " Callers=" + Debug.getCallers(4));
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1f2ab07..022b6f3 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -25,7 +25,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.AppWindowAnimator.sDummyAnimation;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -242,17 +241,12 @@
         return mWin.isAnimating();
     }
 
-    /** Is the window animating the DummyAnimation? */
-    boolean isDummyAnimation() {
-        return mAppAnimator != null
-                && mAppAnimator.animation == sDummyAnimation;
-    }
-
     /**
      * Is this window currently waiting to run an opening animation?
      */
     boolean isWaitingForOpening() {
-        return mService.mAppTransition.isTransitionSet() && isDummyAnimation()
+        return mService.mAppTransition.isTransitionSet()
+                && (mWin.mAppToken != null && mWin.mAppToken.isHidden())
                 && mService.mOpeningApps.contains(mWin.mAppToken);
     }
 
@@ -423,7 +417,7 @@
             return;
         }
 
-        if (mWin.mAppToken.mAppAnimator.animation == null) {
+        if (!mWin.mAppToken.isSelfAnimating()) {
             mWin.mAppToken.clearAllDrawn();
         } else {
             // Currently animating, persist current state of allDrawn until animation
@@ -914,12 +908,13 @@
     }
 
     private int resolveStackClip() {
-        // App animation overrides window animation stack clip mode.
+        // TODO
+        /*// App animation overrides window animation stack clip mode.
         if (mAppAnimator != null && mAppAnimator.animation != null) {
             return mAppAnimator.getStackClip();
-        } else {
+        } else {*/
             return STACK_CLIP_AFTER_ANIM;
-        }
+        //}
     }
 
     private boolean shouldCropToStackBounds() {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 782e609..76faf4b 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -361,14 +361,9 @@
 
         mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
 
-        final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
-                topOpeningApp.mAppAnimator;
-        final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null :
-                topClosingApp.mAppAnimator;
-
         final int flags = mService.mAppTransition.getTransitFlags();
-        int layoutRedo = mService.mAppTransition.goodToGo(transit, openingAppAnimator,
-                closingAppAnimator, mService.mOpeningApps, mService.mClosingApps);
+        int layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp,
+                topClosingApp, mService.mOpeningApps, mService.mClosingApps);
         handleNonAppWindowsInTransition(transit, flags);
         mService.mAppTransition.postAnimationCallback();
         mService.mAppTransition.clear();
@@ -408,11 +403,6 @@
             final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
 
-            if (!appAnimator.usingTransferredAnimation) {
-                appAnimator.clearThumbnail();
-                appAnimator.setNullAnimation();
-            }
-
             if (!wtoken.setVisibility(animLp, true, transit, false, voiceInteraction)){
                 // This token isn't going to be animating. Add it to the list of tokens to
                 // be notified of app transition complete since the notification will not be
@@ -426,13 +416,12 @@
                     ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
             mService.openSurfaceTransaction();
             try {
-                appAnimator.showAllWindowsLocked();
+                wtoken.showAllWindowsLocked();
             } finally {
                 mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                         "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
             }
-            mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
             if (animLp != null) {
                 final int layer = wtoken.getHighestAnimLayer();
@@ -455,10 +444,7 @@
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mClosingApps.valueAt(i);
 
-            final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
-            appAnimator.clearThumbnail();
-            appAnimator.setNullAnimation();
             // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
             //       animating?
             wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
@@ -474,7 +460,6 @@
                     && wtoken.getController() != null) {
                 wtoken.getController().removeStartingWindow();
             }
-            mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
             if (animLp != null) {
                 int layer = wtoken.getHighestAnimLayer();
@@ -661,22 +646,19 @@
             final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
             if (win != null) {
                 final AppWindowToken wtoken = win.mAppToken;
-                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
                 if (DEBUG_APP_TRANSITIONS)
                     Slog.v(TAG, "Now animating app in place " + wtoken);
-                appAnimator.clearThumbnail();
-                appAnimator.setNullAnimation();
-                mService.updateTokenInPlaceLocked(wtoken, transit);
+                wtoken.cancelAnimation();
+                wtoken.applyAnimationLocked(null, transit, false, false);
                 wtoken.updateReportedVisibilityLocked();
-                mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
-                appAnimator.showAllWindowsLocked();
+                wtoken.showAllWindowsLocked();
             }
         }
     }
 
     private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) {
         AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
-        if (openingAppAnimator == null || openingAppAnimator.animation == null) {
+        if (appToken == null || !appToken.isSelfAnimating()) {
             return;
         }
         final int taskId = appToken.getTask().mTaskId;
@@ -752,7 +734,7 @@
         } catch (Surface.OutOfResourcesException e) {
             Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
                     + dirty.width() + " h=" + dirty.height(), e);
-            openingAppAnimator.clearThumbnail();
+            appToken.clearThumbnail();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5bcf59c..924aa62 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -63,7 +63,7 @@
     boolean paused = false;
 
     // Should this token's windows be hidden?
-    boolean hidden;
+    private boolean mHidden;
 
     // Temporary for finding which tokens no longer have visible windows.
     boolean hasVisible;
@@ -112,6 +112,16 @@
         onDisplayChanged(dc);
     }
 
+    void setHidden(boolean hidden) {
+        if (hidden != mHidden) {
+            mHidden = hidden;
+        }
+    }
+
+    boolean isHidden() {
+        return mHidden;
+    }
+
     void removeAllWindowsIfPossible() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowState win = mChildren.get(i);
@@ -130,7 +140,7 @@
         // This token is exiting, so allow it to be removed when it no longer contains any windows.
         mPersistOnEmpty = false;
 
-        if (hidden) {
+        if (mHidden) {
             return;
         }
 
@@ -146,7 +156,7 @@
             changed |= win.onSetAppExiting();
         }
 
-        hidden = true;
+        setHidden(true);
 
         if (changed) {
             mService.mWindowPlacerLocked.performSurfacePlacement();
@@ -274,10 +284,11 @@
         proto.end(token);
     }
 
-    void dump(PrintWriter pw, String prefix) {
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("windows="); pw.println(mChildren);
         pw.print(prefix); pw.print("windowType="); pw.print(windowType);
-                pw.print(" hidden="); pw.print(hidden);
+                pw.print(" hidden="); pw.print(mHidden);
                 pw.print(" hasVisible="); pw.println(hasVisible);
         if (waitingToShow || sendingToBottom) {
             pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow);