Refactor SplashScreenAnimation so it can be reused in other parts of the code.

Bug: 202826469
Test: manual
Change-Id: Ib2ce8b8b69bc3f9e4b3003978bac8b3884563678
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 82573b2..f615ad6 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -45,6 +45,9 @@
         "src/com/android/wm/shell/util/**/*.java",
         "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
         "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
+        "src/com/android/wm/shell/common/TransactionPool.java",
+        "src/com/android/wm/shell/animation/Interpolators.java",
+        "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
     ],
     path: "src",
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 014f02b..8bba4404 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -15,38 +15,20 @@
  */
 package com.android.wm.shell.startingsurface;
 
-import static android.view.Choreographer.CALLBACK_COMMIT;
 import static android.view.View.GONE;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
 
 import android.animation.Animator;
-import android.animation.ValueAnimator;
 import android.content.Context;
-import android.graphics.BlendMode;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.RadialGradient;
 import android.graphics.Rect;
-import android.graphics.Shader;
-import android.util.MathUtils;
 import android.util.Slog;
-import android.view.Choreographer;
 import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
 import android.window.SplashScreenView;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.TransactionPool;
 
 /**
@@ -55,14 +37,8 @@
  */
 public class SplashScreenExitAnimation implements Animator.AnimatorListener {
     private static final boolean DEBUG_EXIT_ANIMATION = false;
-    private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
     private static final String TAG = StartingWindowController.TAG;
 
-    private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
-    private static final Interpolator MASK_RADIUS_INTERPOLATOR =
-            new PathInterpolator(0f, 0f, 0.4f, 1f);
-    private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
-
     private final SurfaceControl mFirstWindowSurface;
     private final Rect mFirstWindowFrame = new Rect();
     private final SplashScreenView mSplashScreenView;
@@ -75,9 +51,6 @@
     private final float mBrandingStartAlpha;
     private final TransactionPool mTransactionPool;
 
-    private ValueAnimator mMainAnimator;
-    private ShiftUpAnimation mShiftUpAnimation;
-    private RadialVanishAnimation mRadialVanishAnimation;
     private Runnable mFinishCallback;
 
     SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
@@ -121,187 +94,10 @@
     }
 
     void startAnimations() {
-        mMainAnimator = createAnimator();
-        mMainAnimator.start();
-    }
-
-    // fade out icon, reveal app, shift up main window
-    private ValueAnimator createAnimator() {
-        // reveal app
-        final float transparentRatio = 0.8f;
-        final int globalHeight = mSplashScreenView.getHeight();
-        final int verticalCircleCenter = 0;
-        final int finalVerticalLength = globalHeight - verticalCircleCenter;
-        final int halfWidth = mSplashScreenView.getWidth() / 2;
-        final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
-                Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
-        final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
-        final float[] stops = {0f, transparentRatio, 1f};
-
-        mRadialVanishAnimation = new RadialVanishAnimation(mSplashScreenView);
-        mRadialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
-        mRadialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
-        mRadialVanishAnimation.setRadialPaintParam(colors, stops);
-
-        if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
-            // shift up main window
-            View occludeHoleView = new View(mSplashScreenView.getContext());
-            if (DEBUG_EXIT_ANIMATION_BLEND) {
-                occludeHoleView.setBackgroundColor(Color.BLUE);
-            } else {
-                occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor());
-            }
-            final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
-                    WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
-            mSplashScreenView.addView(occludeHoleView, params);
-
-            mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
-        }
-
-        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
-        animator.setDuration(mAnimationDuration);
-        animator.setInterpolator(Interpolators.LINEAR);
-        animator.addListener(this);
-        animator.addUpdateListener(a -> onAnimationProgress((float) a.getAnimatedValue()));
-        return animator;
-    }
-
-    private static class RadialVanishAnimation extends View {
-        private final SplashScreenView mView;
-        private int mInitRadius;
-        private int mFinishRadius;
-
-        private final Point mCircleCenter = new Point();
-        private final Matrix mVanishMatrix = new Matrix();
-        private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
-        RadialVanishAnimation(SplashScreenView target) {
-            super(target.getContext());
-            mView = target;
-            mView.addView(this);
-            mVanishPaint.setAlpha(0);
-        }
-
-        void onAnimationProgress(float linearProgress) {
-            if (mVanishPaint.getShader() == null) {
-                return;
-            }
-
-            final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
-            final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
-            final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
-
-            mVanishMatrix.setScale(scale, scale);
-            mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
-            mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
-            mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
-
-            postInvalidate();
-        }
-
-        void setRadius(int initRadius, int finishRadius) {
-            if (DEBUG_EXIT_ANIMATION) {
-                Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
-                        + " final " + finishRadius);
-            }
-            mInitRadius = initRadius;
-            mFinishRadius = finishRadius;
-        }
-
-        void setCircleCenter(int x, int y) {
-            if (DEBUG_EXIT_ANIMATION) {
-                Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
-            }
-            mCircleCenter.set(x, y);
-        }
-
-        void setRadialPaintParam(int[] colors, float[] stops) {
-            // setup gradient shader
-            final RadialGradient rShader =
-                    new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
-            mVanishPaint.setShader(rShader);
-            if (!DEBUG_EXIT_ANIMATION_BLEND) {
-                // We blend the reveal gradient with the splash screen using DST_OUT so that the
-                // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
-                // fully invisible when radius = finishRadius AND gradient opacity is 1.
-                mVanishPaint.setBlendMode(BlendMode.DST_OUT);
-            }
-        }
-
-        @Override
-        protected void onDraw(Canvas canvas) {
-            super.onDraw(canvas);
-            canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
-        }
-    }
-
-    private final class ShiftUpAnimation {
-        private final float mFromYDelta;
-        private final float mToYDelta;
-        private final View mOccludeHoleView;
-        private final SyncRtSurfaceTransactionApplier mApplier;
-        private final Matrix mTmpTransform = new Matrix();
-
-        ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView) {
-            mFromYDelta = fromYDelta;
-            mToYDelta = toYDelta;
-            mOccludeHoleView = occludeHoleView;
-            mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
-        }
-
-        void onAnimationProgress(float linearProgress) {
-            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
-                    || !mSplashScreenView.isAttachedToWindow()) {
-                return;
-            }
-
-            final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
-            final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
-
-            mOccludeHoleView.setTranslationY(dy);
-            mTmpTransform.setTranslate(0 /* dx */, dy);
-
-            // set the vsyncId to ensure the transaction doesn't get applied too early.
-            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
-            tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
-            mTmpTransform.postTranslate(mFirstWindowFrame.left,
-                    mFirstWindowFrame.top + mMainWindowShiftLength);
-
-            SyncRtSurfaceTransactionApplier.SurfaceParams
-                    params = new SyncRtSurfaceTransactionApplier.SurfaceParams
-                    .Builder(mFirstWindowSurface)
-                    .withMatrix(mTmpTransform)
-                    .withMergeTransaction(tx)
-                    .build();
-            mApplier.scheduleApply(params);
-
-            mTransactionPool.release(tx);
-        }
-
-        void finish() {
-            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
-                return;
-            }
-            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
-            if (mSplashScreenView.isAttachedToWindow()) {
-                tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
-
-                SyncRtSurfaceTransactionApplier.SurfaceParams
-                        params = new SyncRtSurfaceTransactionApplier.SurfaceParams
-                        .Builder(mFirstWindowSurface)
-                        .withWindowCrop(null)
-                        .withMergeTransaction(tx)
-                        .build();
-                mApplier.scheduleApply(params);
-            } else {
-                tx.setWindowCrop(mFirstWindowSurface, null);
-                tx.apply();
-            }
-            mTransactionPool.release(tx);
-
-            Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
-                    mFirstWindowSurface::release, null);
-        }
+        SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
+                mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
+                mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
+                mAppRevealDuration, this);
     }
 
     private void reset() {
@@ -316,9 +112,6 @@
                 mFinishCallback = null;
             }
         }
-        if (mShiftUpAnimation != null) {
-            mShiftUpAnimation.finish();
-        }
     }
 
     @Override
@@ -342,40 +135,4 @@
     public void onAnimationRepeat(Animator animation) {
         // ignore
     }
-
-    private void onFadeOutProgress(float linearProgress) {
-        final float iconProgress = ICON_INTERPOLATOR.getInterpolation(
-                getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration));
-        final View iconView = mSplashScreenView.getIconView();
-        final View brandingView = mSplashScreenView.getBrandingView();
-        if (iconView != null) {
-            iconView.setAlpha(mIconStartAlpha * (1 - iconProgress));
-        }
-        if (brandingView != null) {
-            brandingView.setAlpha(mBrandingStartAlpha * (1 - iconProgress));
-        }
-    }
-
-    private void onAnimationProgress(float linearProgress) {
-        onFadeOutProgress(linearProgress);
-
-        final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
-                mAppRevealDuration);
-
-        if (mRadialVanishAnimation != null) {
-            mRadialVanishAnimation.onAnimationProgress(revealLinearProgress);
-        }
-
-        if (mShiftUpAnimation != null) {
-            mShiftUpAnimation.onAnimationProgress(revealLinearProgress);
-        }
-    }
-
-    private float getProgress(float linearProgress, long delay, long duration) {
-        return MathUtils.constrain(
-                (linearProgress * (mAnimationDuration) - delay) / duration,
-                0.0f,
-                1.0f
-        );
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
new file mode 100644
index 0000000..3098e55
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.startingsurface;
+
+import static android.view.Choreographer.CALLBACK_COMMIT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.window.SplashScreenView;
+
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.TransactionPool;
+
+/**
+ * Utilities for creating the splash screen window animations.
+ * @hide
+ */
+public class SplashScreenExitAnimationUtils {
+    private static final boolean DEBUG_EXIT_ANIMATION = false;
+    private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+    private static final String TAG = "SplashScreenExitAnimationUtils";
+
+    private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
+    private static final Interpolator MASK_RADIUS_INTERPOLATOR =
+            new PathInterpolator(0f, 0f, 0.4f, 1f);
+    private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
+
+    /**
+     * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+     * window.
+     * @hide
+     */
+    public static void startAnimations(ViewGroup splashScreenView,
+            SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+            TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+            int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+        ValueAnimator animator =
+                createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+                        transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+                        iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+                        animatorListener);
+        animator.start();
+    }
+
+    /**
+     * Creates the animator to fade out the icon, reveal the app, and shift up main window.
+     * @hide
+     */
+    private static ValueAnimator createAnimator(ViewGroup splashScreenView,
+            SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
+            TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+            int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+        // reveal app
+        final float transparentRatio = 0.8f;
+        final int globalHeight = splashScreenView.getHeight();
+        final int verticalCircleCenter = 0;
+        final int finalVerticalLength = globalHeight - verticalCircleCenter;
+        final int halfWidth = splashScreenView.getWidth() / 2;
+        final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
+                Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
+        final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
+        final float[] stops = {0f, transparentRatio, 1f};
+
+        RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation(splashScreenView);
+        radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
+        radialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
+        radialVanishAnimation.setRadialPaintParam(colors, stops);
+
+        View occludeHoleView = null;
+        ShiftUpAnimation shiftUpAnimation = null;
+        if (firstWindowSurface != null && firstWindowSurface.isValid()) {
+            // shift up main window
+            occludeHoleView = new View(splashScreenView.getContext());
+            if (DEBUG_EXIT_ANIMATION_BLEND) {
+                occludeHoleView.setBackgroundColor(Color.BLUE);
+            } else if (splashScreenView instanceof SplashScreenView) {
+                occludeHoleView.setBackgroundColor(
+                        ((SplashScreenView) splashScreenView).getInitBackgroundColor());
+            } else {
+                occludeHoleView.setBackgroundColor(
+                        isDarkTheme(splashScreenView.getContext()) ? Color.BLACK : Color.WHITE);
+            }
+            final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
+                    WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
+            splashScreenView.addView(occludeHoleView, params);
+
+            shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
+                    firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
+                    mMainWindowShiftLength);
+        }
+
+        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+        animator.setDuration(animationDuration);
+        animator.setInterpolator(Interpolators.LINEAR);
+        if (animatorListener != null) {
+            animator.addListener(animatorListener);
+        }
+        View finalOccludeHoleView = occludeHoleView;
+        ShiftUpAnimation finalShiftUpAnimation = shiftUpAnimation;
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (finalShiftUpAnimation != null) {
+                    finalShiftUpAnimation.finish();
+                }
+                splashScreenView.removeView(radialVanishAnimation);
+                splashScreenView.removeView(finalOccludeHoleView);
+            }
+        });
+        animator.addUpdateListener(animation -> {
+            float linearProgress = (float) animation.getAnimatedValue();
+
+            // Fade out progress
+            final float iconProgress =
+                    ICON_INTERPOLATOR.getInterpolation(getProgress(
+                            linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
+            View iconView = null;
+            View brandingView = null;
+            if (splashScreenView instanceof SplashScreenView) {
+                iconView = ((SplashScreenView) splashScreenView).getIconView();
+                brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
+            }
+            if (iconView != null) {
+                iconView.setAlpha(iconStartAlpha * (1 - iconProgress));
+            }
+            if (brandingView != null) {
+                brandingView.setAlpha(brandingStartAlpha * (1 - iconProgress));
+            }
+
+            final float revealLinearProgress = getProgress(linearProgress, appRevealDelay,
+                    appRevealDuration, animationDuration);
+
+            radialVanishAnimation.onAnimationProgress(revealLinearProgress);
+
+            if (finalShiftUpAnimation != null) {
+                finalShiftUpAnimation.onAnimationProgress(revealLinearProgress);
+            }
+        });
+        return animator;
+    }
+
+    private static float getProgress(float linearProgress, long delay, long duration,
+                                     int animationDuration) {
+        return MathUtils.constrain(
+                (linearProgress * (animationDuration) - delay) / duration,
+                0.0f,
+                1.0f
+        );
+    }
+
+    private static boolean isDarkTheme(Context context) {
+        Configuration configuration = context.getResources().getConfiguration();
+        int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        return nightMode == Configuration.UI_MODE_NIGHT_YES;
+    }
+
+    /**
+     * View which creates a circular reveal of the underlying view.
+     * @hide
+     */
+    @SuppressLint("ViewConstructor")
+    public static class RadialVanishAnimation extends View {
+        private final ViewGroup mView;
+        private int mInitRadius;
+        private int mFinishRadius;
+
+        private final Point mCircleCenter = new Point();
+        private final Matrix mVanishMatrix = new Matrix();
+        private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+        public RadialVanishAnimation(ViewGroup target) {
+            super(target.getContext());
+            mView = target;
+            mView.addView(this);
+            if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
+                ((ViewGroup.MarginLayoutParams) getLayoutParams()).setMargins(0, 0, 0, 0);
+            }
+            mVanishPaint.setAlpha(0);
+        }
+
+        void onAnimationProgress(float linearProgress) {
+            if (mVanishPaint.getShader() == null) {
+                return;
+            }
+
+            final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
+            final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
+            final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
+
+            mVanishMatrix.setScale(scale, scale);
+            mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
+            mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
+            mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
+
+            postInvalidate();
+        }
+
+        void setRadius(int initRadius, int finishRadius) {
+            if (DEBUG_EXIT_ANIMATION) {
+                Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
+                        + " final " + finishRadius);
+            }
+            mInitRadius = initRadius;
+            mFinishRadius = finishRadius;
+        }
+
+        void setCircleCenter(int x, int y) {
+            if (DEBUG_EXIT_ANIMATION) {
+                Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
+            }
+            mCircleCenter.set(x, y);
+        }
+
+        void setRadialPaintParam(int[] colors, float[] stops) {
+            // setup gradient shader
+            final RadialGradient rShader =
+                    new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
+            mVanishPaint.setShader(rShader);
+            if (!DEBUG_EXIT_ANIMATION_BLEND) {
+                // We blend the reveal gradient with the splash screen using DST_OUT so that the
+                // splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
+                // fully invisible when radius = finishRadius AND gradient opacity is 1.
+                mVanishPaint.setBlendMode(BlendMode.DST_OUT);
+            }
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
+        }
+    }
+
+    /**
+     * Shifts up the main window.
+     * @hide
+     */
+    public static final class ShiftUpAnimation {
+        private final float mFromYDelta;
+        private final float mToYDelta;
+        private final View mOccludeHoleView;
+        private final SyncRtSurfaceTransactionApplier mApplier;
+        private final Matrix mTmpTransform = new Matrix();
+        private final SurfaceControl mFirstWindowSurface;
+        private final ViewGroup mSplashScreenView;
+        private final TransactionPool mTransactionPool;
+        private final Rect mFirstWindowFrame;
+        private final int mMainWindowShiftLength;
+
+        public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
+                                SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
+                                TransactionPool transactionPool, Rect firstWindowFrame,
+                                int mainWindowShiftLength) {
+            mFromYDelta = fromYDelta;
+            mToYDelta = toYDelta;
+            mOccludeHoleView = occludeHoleView;
+            mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
+            mFirstWindowSurface = firstWindowSurface;
+            mSplashScreenView = splashScreenView;
+            mTransactionPool = transactionPool;
+            mFirstWindowFrame = firstWindowFrame;
+            mMainWindowShiftLength = mainWindowShiftLength;
+        }
+
+        void onAnimationProgress(float linearProgress) {
+            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
+                    || !mSplashScreenView.isAttachedToWindow()) {
+                return;
+            }
+
+            final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
+            final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
+
+            mOccludeHoleView.setTranslationY(dy);
+            mTmpTransform.setTranslate(0 /* dx */, dy);
+
+            // set the vsyncId to ensure the transaction doesn't get applied too early.
+            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+            tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+            mTmpTransform.postTranslate(mFirstWindowFrame.left,
+                    mFirstWindowFrame.top + mMainWindowShiftLength);
+
+            SyncRtSurfaceTransactionApplier.SurfaceParams
+                    params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+                    .Builder(mFirstWindowSurface)
+                    .withMatrix(mTmpTransform)
+                    .withMergeTransaction(tx)
+                    .build();
+            mApplier.scheduleApply(params);
+
+            mTransactionPool.release(tx);
+        }
+
+        void finish() {
+            if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
+                return;
+            }
+            final SurfaceControl.Transaction tx = mTransactionPool.acquire();
+            if (mSplashScreenView.isAttachedToWindow()) {
+                tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+
+                SyncRtSurfaceTransactionApplier.SurfaceParams
+                        params = new SyncRtSurfaceTransactionApplier.SurfaceParams
+                        .Builder(mFirstWindowSurface)
+                        .withWindowCrop(null)
+                        .withMergeTransaction(tx)
+                        .build();
+                mApplier.scheduleApply(params);
+            } else {
+                tx.setWindowCrop(mFirstWindowSurface, null);
+                tx.apply();
+            }
+            mTransactionPool.release(tx);
+
+            Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
+                    mFirstWindowSurface::release, null);
+        }
+    }
+}