diff options
| author | 2020-11-16 13:21:46 +0800 | |
|---|---|---|
| committer | 2021-03-12 03:46:15 +0000 | |
| commit | d52e491d2f13acae00a8397ccebf156634da4466 (patch) | |
| tree | ddb303914fa510a21a36ac765a17578a631d13a7 | |
| parent | 2f3e141580ac403efc0d2327d154bc5848bda557 (diff) | |
Implement default exit animation for splash screen.(9/N)
Implement default exit animation on the view of splash screen, there
will be a two layers switch happen when playing exit animation. For
the detail please reference
go/improved_app_launch_animations and go/app-startup
Note: For this version we skip shift-up animation, need to fix the
flicker test so we can enable it.
Note2: Fix the possible missing splash screen view issue, but could
make the first window draw slower, keep tracking.
Bug: 73289295
Test: build/flash
Test: check splash screen starting window.
Test: atest StartingSurfaceDrawerTests ShellTaskOrganizerTests
WindowOrganizerTests SplashscreenTests
Change-Id: I796811d010ac70f256169dd03d5e05ef0ed79d28
23 files changed, 596 insertions, 196 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 86949e05ba71..bf455cdc7799 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2907,7 +2907,7 @@ package android.window { method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo); method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo); method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer(); - method @BinderThread public void removeStartingWindow(int); + method @BinderThread public void removeStartingWindow(int, @Nullable android.view.SurfaceControl, @Nullable android.graphics.Rect, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean); method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer(); } diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 8f541d0bd194..3eb35c2c5e8a 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -18,6 +18,7 @@ package android.window; import android.view.SurfaceControl; import android.app.ActivityManager; +import android.graphics.Rect; import android.window.StartingWindowInfo; import android.window.WindowContainerToken; @@ -38,8 +39,12 @@ oneway interface ITaskOrganizer { /** * Called when the Task want to remove the starting window. + * @param leash A persistent leash for the top window in this task. + * @param frame Window frame of the top window. + * @param playRevealAnimation Play vanish animation. */ - void removeStartingWindow(int taskId); + void removeStartingWindow(int taskId, in SurfaceControl leash, in Rect frame, + in boolean playRevealAnimation); /** * Called when the Task want to copy the splash screen. diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 35ccfca101d3..da445b8b9f33 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -46,6 +46,8 @@ import android.widget.FrameLayout; import com.android.internal.R; import com.android.internal.policy.DecorView; +import java.util.function.Consumer; + /** * <p>The view which allows an activity to customize its splash screen exit animation.</p> * @@ -77,7 +79,8 @@ public final class SplashScreenView extends FrameLayout { private Animatable mAnimatableIcon; private ValueAnimator mAnimator; - + private Runnable mAnimationFinishListener; + private Consumer<Canvas> mOnDrawCallback; // cache original window and status private Window mWindow; private boolean mDrawBarBackground; @@ -85,7 +88,7 @@ public final class SplashScreenView extends FrameLayout { private int mNavigationBarColor; /** - * Internal builder to create a SplashScreenWindowView object. + * Internal builder to create a SplashScreenView object. * @hide */ public static class Builder { @@ -391,7 +394,7 @@ public final class SplashScreenView extends FrameLayout { * Get the initial background color of this view. * @hide */ - @ColorInt int getInitBackgroundColor() { + public @ColorInt int getInitBackgroundColor() { return mInitBackgroundColor; } diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 04020ec3ee8a..3340cf4fb707 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -24,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityManager; +import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.view.SurfaceControl; @@ -100,9 +101,14 @@ public class TaskOrganizer extends WindowOrganizer { /** * Called when the Task want to remove the starting window. + * @param leash A persistent leash for the top window in this task. Release it once exit + * animation has finished. + * @param frame Window frame of the top window. + * @param playRevealAnimation Play vanish animation. */ @BinderThread - public void removeStartingWindow(int taskId) {} + public void removeStartingWindow(int taskId, @Nullable SurfaceControl leash, + @Nullable Rect frame, boolean playRevealAnimation) {} /** * Called when the Task want to copy the splash screen. @@ -217,15 +223,16 @@ public class TaskOrganizer extends WindowOrganizer { private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() { @Override - public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken)); } @Override - public void removeStartingWindow(int taskId) { - mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId)); + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { + mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId, leash, frame, + playRevealAnimation)); } @Override diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 24198659e15d..c2f591b9d7af 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -51,4 +51,10 @@ <!-- maximum animation duration for the icon when entering the starting window --> <integer name="max_starting_window_intro_icon_anim_duration">1000</integer> + + <!-- Animation duration when exit starting window: icon going away --> + <integer name="starting_window_icon_exit_anim_duration">166</integer> + + <!-- Animation duration when exit starting window: reveal app --> + <integer name="starting_window_app_reveal_anim_duration">333</integer> </resources> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 75bed3777a9d..3ced8d3ec6e7 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -188,4 +188,13 @@ <!-- The height of the brand image on staring surface. --> <dimen name="starting_surface_brand_image_height">80dp</dimen> + + <!-- The length of the shift of main window when exit starting window. --> + <dimen name="starting_surface_exit_animation_window_shift_length">20dp</dimen> + + <!-- The distance of the shift icon when normal exit starting window. --> + <dimen name="starting_surface_normal_exit_icon_distance">120dp</dimen> + + <!-- The distance of the shift icon when early exit starting window. --> + <dimen name="starting_surface_early_exit_icon_distance">32dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index cb04bd7ce02b..fcb53cd890b7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -31,6 +31,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; import android.content.Context; import android.content.LocusId; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.util.ArrayMap; @@ -307,9 +308,10 @@ public class ShellTaskOrganizer extends TaskOrganizer { } @Override - public void removeStartingWindow(int taskId) { + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { if (mStartingSurface != null) { - mStartingSurface.removeStartingWindow(taskId); + mStartingSurface.removeStartingWindow(taskId, leash, frame, playRevealAnimation); } } 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 new file mode 100644 index 000000000000..5bc2afd11fe8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2020 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.View.GONE; + +import android.animation.Animator; +import android.animation.ValueAnimator; +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.Slog; +import android.view.SurfaceControl; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; +import android.view.animation.Transformation; +import android.view.animation.TranslateYAnimation; +import android.window.SplashScreenView; + +import com.android.wm.shell.common.TransactionPool; + +/** + * Default animation for exiting the splash screen window. + * @hide + */ +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 = StartingSurfaceDrawer.TAG; + + private static final Interpolator ICON_EXIT_INTERPOLATOR = new PathInterpolator(1f, 0f, 1f, 1f); + private static final Interpolator APP_EXIT_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f); + + private static final int EXTRA_REVEAL_DELAY = 133; + private final Matrix mTmpTransform = new Matrix(); + private final float[] mTmpFloat9 = new float[9]; + private SurfaceControl mFirstWindowSurface; + private final Rect mFirstWindowFrame = new Rect(); + private final SplashScreenView mSplashScreenView; + private final int mMainWindowShiftLength; + private final int mIconShiftLength; + private final int mAppDuration; + private final int mIconDuration; + private final TransactionPool mTransactionPool; + + private ValueAnimator mMainAnimator; + private Animation mShiftUpAnimation; + private AnimationSet mIconAnimationSet; + private Runnable mFinishCallback; + + SplashScreenExitAnimation(SplashScreenView view, SurfaceControl leash, Rect frame, + int appDuration, int iconDuration, int mainWindowShiftLength, int iconShiftLength, + TransactionPool pool, Runnable handleFinish) { + mSplashScreenView = view; + mFirstWindowSurface = leash; + if (frame != null) { + mFirstWindowFrame.set(frame); + } + mAppDuration = appDuration; + mIconDuration = iconDuration; + mMainWindowShiftLength = mainWindowShiftLength; + mIconShiftLength = iconShiftLength; + mFinishCallback = handleFinish; + mTransactionPool = pool; + } + + void prepareAnimations() { + prepareRevealAnimation(); + prepareShiftAnimation(); + } + + void startAnimations() { + if (mIconAnimationSet != null) { + mIconAnimationSet.start(); + } + if (mMainAnimator != null) { + mMainAnimator.start(); + } + if (mShiftUpAnimation != null) { + mShiftUpAnimation.start(); + } + } + + // reveal splash screen, shift up main window + private void prepareRevealAnimation() { + // splash screen + mMainAnimator = ValueAnimator.ofFloat(0f, 1f); + mMainAnimator.setDuration(mAppDuration); + mMainAnimator.setInterpolator(APP_EXIT_INTERPOLATOR); + mMainAnimator.addListener(this); + + final int startDelay = mIconDuration + EXTRA_REVEAL_DELAY; + final float transparentRatio = 0.95f; + 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 RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation( + mSplashScreenView, mMainAnimator); + radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter); + radialVanishAnimation.setRadius(0/* initRadius */, endRadius); + final int[] colors = {Color.TRANSPARENT, Color.TRANSPARENT, Color.WHITE}; + final float[] stops = {0f, transparentRatio, 1f}; + radialVanishAnimation.setRadialPaintParam(colors, stops); + radialVanishAnimation.setReady(); + mMainAnimator.setStartDelay(startDelay); + + if (mFirstWindowSurface != null) { + // 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); + mShiftUpAnimation.setDuration(mAppDuration); + mShiftUpAnimation.setInterpolator(APP_EXIT_INTERPOLATOR); + mShiftUpAnimation.setStartOffset(startDelay); + + occludeHoleView.setAnimation(mShiftUpAnimation); + } + } + + // shift down icon and branding view + private void prepareShiftAnimation() { + final View iconView = mSplashScreenView.getIconView(); + if (iconView == null) { + return; + } + if (mIconShiftLength > 0) { + mIconAnimationSet = new AnimationSet(true /* shareInterpolator */); + if (DEBUG_EXIT_ANIMATION) { + Slog.v(TAG, "first exit animation, shift length: " + mIconShiftLength); + } + mIconAnimationSet.addAnimation(new TranslateYAnimation(0, mIconShiftLength)); + mIconAnimationSet.addAnimation(new AlphaAnimation(1, 0)); + mIconAnimationSet.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + if (DEBUG_EXIT_ANIMATION) { + Slog.v(TAG, "first exit animation finished"); + } + iconView.post(() -> iconView.setVisibility(GONE)); + } + + @Override + public void onAnimationRepeat(Animation animation) { + // ignore + } + }); + mIconAnimationSet.setDuration(mIconDuration); + mIconAnimationSet.setInterpolator(ICON_EXIT_INTERPOLATOR); + iconView.setAnimation(mIconAnimationSet); + final View brandingView = mSplashScreenView.getBrandingView(); + if (brandingView != null) { + brandingView.setAnimation(mIconAnimationSet); + } + } + } + + private static class RadialVanishAnimation extends View { + private SplashScreenView mView; + private int mInitRadius; + private int mFinishRadius; + private boolean mReady; + + 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, ValueAnimator animator) { + super(target.getContext()); + mView = target; + animator.addUpdateListener((animation) -> { + if (mVanishPaint.getShader() == null) { + return; + } + final float value = (float) animation.getAnimatedValue(); + final float scale = (mFinishRadius - mInitRadius) * value + mInitRadius; + mVanishMatrix.setScale(scale, scale); + mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y); + mVanishPaint.getShader().setLocalMatrix(mVanishMatrix); + mView.postInvalidate(); + }); + mView.addView(this); + } + + 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) { + mVanishPaint.setBlendMode(BlendMode.MODULATE); + } + } + + void setReady() { + mReady = true; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mReady) { + canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint); + } + } + } + + private final class ShiftUpAnimation extends TranslateYAnimation { + ShiftUpAnimation(float fromYDelta, float toYDelta) { + super(fromYDelta, toYDelta); + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + super.applyTransformation(interpolatedTime, t); + + if (mFirstWindowSurface == null) { + return; + } + mTmpTransform.set(t.getMatrix()); + final SurfaceControl.Transaction tx = mTransactionPool.acquire(); + mTmpTransform.postTranslate(mFirstWindowFrame.left, + mFirstWindowFrame.top + mMainWindowShiftLength); + tx.setMatrix(mFirstWindowSurface, mTmpTransform, mTmpFloat9); + // TODO set the vsyncId to ensure the transaction doesn't get applied too early. + // Additionally, do you want to have this synchronized with your view animations? + // If so, you'll need to use SyncRtSurfaceTransactionApplier + tx.apply(); + mTransactionPool.release(tx); + } + } + + private void reset() { + if (DEBUG_EXIT_ANIMATION) { + Slog.v(TAG, "vanish animation finished"); + } + mSplashScreenView.post(() -> { + mSplashScreenView.setVisibility(GONE); + if (mFinishCallback != null) { + mFinishCallback.run(); + mFinishCallback = null; + } + }); + if (mFirstWindowSurface != null) { + final SurfaceControl.Transaction tx = mTransactionPool.acquire(); + tx.setWindowCrop(mFirstWindowSurface, null); + tx.apply(); + mFirstWindowSurface.release(); + mFirstWindowSurface = null; + } + } + + @Override + public void onAnimationStart(Animator animation) { + // ignore + } + + @Override + public void onAnimationEnd(Animator animation) { + reset(); + } + + @Override + public void onAnimationCancel(Animator animation) { + reset(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + // ignore + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 2973b5080ae6..3f9c2717731a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -31,12 +31,14 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.util.Slog; +import android.view.SurfaceControl; import android.window.SplashScreenView; import com.android.internal.R; import com.android.internal.graphics.palette.Palette; import com.android.internal.graphics.palette.Quantizer; import com.android.internal.graphics.palette.VariationalKMeansQuantizer; +import com.android.wm.shell.common.TransactionPool; import java.util.List; @@ -56,15 +58,25 @@ public class SplashscreenContentDrawer { // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon. private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f); private final Context mContext; - private final int mMaxIconAnimationDuration; + private final int mMaxAnimatableIconDuration; private int mIconSize; private int mBrandingImageWidth; private int mBrandingImageHeight; - - SplashscreenContentDrawer(Context context, int maxIconAnimationDuration) { + private final int mAppRevealDuration; + private final int mIconExitDuration; + private int mMainWindowShiftLength; + private int mIconNormalExitDistance; + private int mIconEarlyExitDistance; + private final TransactionPool mTransactionPool; + + SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration, + int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) { mContext = context; - mMaxIconAnimationDuration = maxIconAnimationDuration; + mMaxAnimatableIconDuration = maxAnimatableIconDuration; + mAppRevealDuration = appRevealAnimDuration; + mIconExitDuration = iconExitAnimDuration; + mTransactionPool = pool; } private void updateDensity() { @@ -74,6 +86,12 @@ public class SplashscreenContentDrawer { com.android.wm.shell.R.dimen.starting_surface_brand_image_width); mBrandingImageHeight = mContext.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.starting_surface_brand_image_height); + mMainWindowShiftLength = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.starting_surface_exit_animation_window_shift_length); + mIconNormalExitDistance = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.starting_surface_normal_exit_icon_distance); + mIconEarlyExitDistance = mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.starting_surface_early_exit_icon_distance); } private int getSystemBGColor() { @@ -119,7 +137,7 @@ public class SplashscreenContentDrawer { if (attrs.mReplaceIcon != null) { iconDrawable = attrs.mReplaceIcon; animationDuration = Math.max(0, - Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration)); + Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration)); } else { iconDrawable = iconRes != 0 ? context.getDrawable(iconRes) : context.getPackageManager().getDefaultActivityIcon(); @@ -439,8 +457,8 @@ public class SplashscreenContentDrawer { } /** - * For ColorDrawable only. - * There will be only one color so don't spend too much resource for it. + * For ColorDrawable only. There will be only one color so don't spend too much resource for + * it. */ private static class SingleColorTester implements ColorTester { private final ColorDrawable mColorDrawable; @@ -472,9 +490,8 @@ public class SplashscreenContentDrawer { } /** - * For any other Drawable except ColorDrawable. - * This will use the Palette API to check the color information and use a quantizer to - * filter out transparent colors when needed. + * For any other Drawable except ColorDrawable. This will use the Palette API to check the + * color information and use a quantizer to filter out transparent colors when needed. */ private static class ComplexDrawableTester implements ColorTester { private static final int MAX_BITMAP_SIZE = 40; @@ -593,4 +610,17 @@ public class SplashscreenContentDrawer { } } } + + /** + * Create and play the default exit animation for splash screen view. + */ + void applyExitAnimation(SplashScreenView view, SurfaceControl leash, + Rect frame, boolean isEarlyExit, Runnable finishCallback) { + final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(view, leash, + frame, mAppRevealDuration, mIconExitDuration, mMainWindowShiftLength, + isEarlyExit ? mIconEarlyExitDistance : mIconNormalExitDistance, mTransactionPool, + finishCallback); + animation.prepareAnimations(); + animation.startAnimations(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java index a594a9f31dde..f258286f2d17 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java @@ -16,7 +16,9 @@ package com.android.wm.shell.startingsurface; +import android.graphics.Rect; import android.os.IBinder; +import android.view.SurfaceControl; import android.window.StartingWindowInfo; import java.util.function.BiConsumer; @@ -31,7 +33,8 @@ public interface StartingSurface { /** * Called when the content of a task is ready to show, starting window can be removed. */ - void removeStartingWindow(int taskId); + void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation); /** * Called when the Task wants to copy the splash screen. * @param taskId diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 2d1d65b87718..dbd518d4a3d3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -29,14 +29,18 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.hardware.display.DisplayManager; import android.os.IBinder; +import android.os.SystemClock; import android.util.Slog; import android.util.SparseArray; -import android.view.Choreographer; import android.view.Display; +import android.view.SurfaceControl; import android.view.View; -import android.view.Window; import android.view.WindowManager; import android.window.SplashScreenView; import android.window.SplashScreenView.SplashScreenViewParcelable; @@ -46,6 +50,7 @@ import android.window.TaskSnapshot; import com.android.internal.R; import com.android.internal.policy.PhoneWindow; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; import java.util.function.Consumer; @@ -62,23 +67,23 @@ public class StartingSurfaceDrawer { private final DisplayManager mDisplayManager; private final ShellExecutor mSplashScreenExecutor; private final SplashscreenContentDrawer mSplashscreenContentDrawer; - protected Choreographer mChoreographer; // TODO(b/131727939) remove this when clearing ActivityRecord private static final int REMOVE_WHEN_TIMEOUT = 2000; - public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor) { + public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor, + TransactionPool pool) { mContext = context; mDisplayManager = mContext.getSystemService(DisplayManager.class); mSplashScreenExecutor = splashScreenExecutor; - final int maxIconAnimDuration = context.getResources().getInteger( + final int maxAnimatableIconDuration = context.getResources().getInteger( com.android.wm.shell.R.integer.max_starting_window_intro_icon_anim_duration); - mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, maxIconAnimDuration); - mSplashScreenExecutor.execute(this::initChoreographer); - } - - protected void initChoreographer() { - mChoreographer = Choreographer.getInstance(); + final int iconExitAnimDuration = context.getResources().getInteger( + com.android.wm.shell.R.integer.starting_window_icon_exit_anim_duration); + final int appRevealAnimDuration = context.getResources().getInteger( + com.android.wm.shell.R.integer.starting_window_app_reveal_anim_duration); + mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, + maxAnimatableIconDuration, iconExitAnimDuration, appRevealAnimDuration, pool); } private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); @@ -195,6 +200,7 @@ public class StartingSurfaceDrawer { } final PhoneWindow win = new PhoneWindow(context); + win.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); win.setIsStartingWindow(true); CharSequence label = context.getResources().getText(labelRes, null); @@ -247,6 +253,7 @@ public class StartingSurfaceDrawer { // Setting as trusted overlay to let touches pass through. This is safe because this // window is controlled by the system. params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; + params.format = PixelFormat.RGBA_8888; final Resources res = context.getResources(); final boolean supportsScreen = res != null && (res.getCompatibilityInfo() != null @@ -257,98 +264,25 @@ public class StartingSurfaceDrawer { params.setTitle("Splash Screen " + activityInfo.packageName); - // TODO(b/173975965) If the target activity doesn't request FLAG_HARDWARE_ACCELERATED, we - // cannot replace the content view after first view was drawn, sounds like an issue. - new AddSplashScreenViewRunnable(taskInfo.taskId, win, context, appToken, params, iconRes, - splashscreenContentResId[0], enableHardAccelerated).run(); - } - - private class AddSplashScreenViewRunnable implements Runnable { - private final int mTaskId; - private final Window mWin; - private final IBinder mAppToken; - private final WindowManager.LayoutParams mLayoutParams; - private final Context mContext; - private final int mIconRes; - private final int mSplashscreenContentResId; - private final boolean mReplaceSplashScreenView; - private int mSequence; - - AddSplashScreenViewRunnable(int taskId, Window window, Context context, - IBinder appToken, WindowManager.LayoutParams params, int iconRes, - int splashscreenContentResId, boolean replaceSplashScreenView) { - mTaskId = taskId; - mWin = window; - mAppToken = appToken; - mContext = context; - mLayoutParams = params; - mIconRes = iconRes; - mSplashscreenContentResId = splashscreenContentResId; - mReplaceSplashScreenView = replaceSplashScreenView; - } - - private void createInitialView() { - View tempView = new View(mContext); - mWin.setContentView(tempView); - mSequence++; - final View view = mWin.getDecorView(); - final WindowManager wm = mContext.getSystemService(WindowManager.class); - if (postAddWindow(mTaskId, mAppToken, view, wm, mLayoutParams)) { - mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, this, null); - } - } - - private SplashScreenView replaceRealView() { - final SplashScreenView sView = - mSplashscreenContentDrawer.makeSplashScreenContentView(mContext, - mIconRes, mSplashscreenContentResId); - mWin.setContentView(sView); - sView.cacheRootWindow(mWin); - return sView; - } - - private SplashScreenView initiateOnce() { - final SplashScreenView sView = - mSplashscreenContentDrawer.makeSplashScreenContentView(mContext, mIconRes, - mSplashscreenContentResId); - final View view = mWin.getDecorView(); + // TODO(b/173975965) tracking performance + final int taskId = taskInfo.taskId; + SplashScreenView sView = null; + try { + sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes, + splashscreenContentResId[0]); + final View view = win.getDecorView(); final WindowManager wm = mContext.getSystemService(WindowManager.class); - if (postAddWindow(mTaskId, mAppToken, view, wm, mLayoutParams)) { - mWin.setContentView(sView); - sView.cacheRootWindow(mWin); - } - return sView; - } - - @Override - public void run() { - SplashScreenView view = null; - boolean setRecord = false; - try { - if (mReplaceSplashScreenView) { - // Tricky way to make animation start faster... create the real content after - // first window drawn. The first empty window won't been see because wm will - // still need to wait for transition ready. - if (mSequence == 0) { - createInitialView(); - } else if (mSequence == 1) { - setRecord = true; - view = replaceRealView(); - } - } else { - setRecord = true; - view = initiateOnce(); - } - } catch (RuntimeException e) { - // don't crash if something else bad happens, for example a - // failure loading resources because we are loading from an app - // on external storage that has been unmounted. - Slog.w(TAG, " failed creating starting window", e); - } finally { - if (setRecord) { - setSplashScreenRecord(mTaskId, view); - } + if (postAddWindow(taskId, appToken, view, wm, params)) { + win.setContentView(sView); + sView.cacheRootWindow(win); } + } catch (RuntimeException e) { + // don't crash if something else bad happens, for example a + // failure loading resources because we are loading from an app + // on external storage that has been unmounted. + Slog.w(TAG, " failed creating starting window", e); + } finally { + setSplashScreenRecord(taskId, sView); } } @@ -359,8 +293,10 @@ public class StartingSurfaceDrawer { TaskSnapshot snapshot) { final int taskId = startingWindowInfo.taskInfo.taskId; final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken, - snapshot, mSplashScreenExecutor, () -> removeWindowSynced(taskId)); - mSplashScreenExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT); + snapshot, mSplashScreenExecutor, + () -> removeWindowNoAnimate(taskId)); + mSplashScreenExecutor.executeDelayed(() -> removeWindowNoAnimate(taskId), + REMOVE_WHEN_TIMEOUT); final StartingWindowRecord tView = new StartingWindowRecord(null/* decorView */, surface); mStartingWindowRecords.put(taskId, tView); @@ -369,11 +305,12 @@ public class StartingSurfaceDrawer { /** * Called when the content of a task is ready to show, starting window can be removed. */ - public void removeStartingWindow(int taskId) { + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { Slog.d(TAG, "Task start finish, remove starting surface for task " + taskId); } - removeWindowSynced(taskId); + removeWindowSynced(taskId, leash, frame, playRevealAnimation); } /** @@ -383,13 +320,6 @@ public class StartingSurfaceDrawer { public void copySplashScreenView(int taskId) { final StartingWindowRecord preView = mStartingWindowRecords.get(taskId); SplashScreenViewParcelable parcelable; - if (preView != null) { - if (preView.isWaitForContent()) { - mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, - () -> copySplashScreenView(taskId), null); - return; - } - } if (preView != null && preView.mContentView != null && preView.mContentView.isCopyable()) { parcelable = new SplashScreenViewParcelable(preView.mContentView); @@ -427,9 +357,9 @@ public class StartingSurfaceDrawer { } } if (shouldSaveView) { - removeWindowSynced(taskId); - mSplashScreenExecutor.executeDelayed(() -> removeWindowSynced(taskId), - REMOVE_WHEN_TIMEOUT); + removeWindowNoAnimate(taskId); + mSplashScreenExecutor.executeDelayed( + () -> removeWindowNoAnimate(taskId), REMOVE_WHEN_TIMEOUT); saveSplashScreenRecord(taskId, view); } return shouldSaveView; @@ -449,24 +379,30 @@ public class StartingSurfaceDrawer { } } - protected void removeWindowSynced(int taskId) { + private void removeWindowNoAnimate(int taskId) { + removeWindowSynced(taskId, null, null, false); + } + + protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { final StartingWindowRecord record = mStartingWindowRecords.get(taskId); if (record != null) { - if (record.isWaitForContent()) { - if (DEBUG_SPLASH_SCREEN) { - Slog.v(TAG, "splash screen window haven't been draw yet"); - } - mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, - () -> removeWindowSynced(taskId), null); - return; - } if (record.mDecorView != null) { if (DEBUG_SPLASH_SCREEN) { Slog.v(TAG, "Removing splash screen window for task: " + taskId); } - final WindowManager wm = record.mDecorView.getContext() - .getSystemService(WindowManager.class); - wm.removeView(record.mDecorView); + if (record.mContentView != null) { + final HandleExitFinish exitFinish = new HandleExitFinish(record.mDecorView); + if (leash != null || playRevealAnimation) { + mSplashscreenContentDrawer.applyExitAnimation(record.mContentView, + leash, frame, record.isEarlyExit(), exitFinish); + mSplashScreenExecutor.executeDelayed(exitFinish, REMOVE_WHEN_TIMEOUT); + } else { + // the SplashScreenView has been copied to client, skip default exit + // animation + exitFinish.run(); + } + } } if (record.mTaskSnapshotWindow != null) { if (DEBUG_TASK_SNAPSHOT) { @@ -478,6 +414,26 @@ public class StartingSurfaceDrawer { } } + private static class HandleExitFinish implements Runnable { + private View mDecorView; + + HandleExitFinish(View decorView) { + mDecorView = decorView; + } + + @Override + public void run() { + if (mDecorView == null) { + return; + } + final WindowManager wm = mDecorView.getContext().getSystemService(WindowManager.class); + if (wm != null) { + wm.removeView(mDecorView); + } + mDecorView = null; + } + } + private void getWindowResFromContext(Context ctx, Consumer<TypedArray> consumer) { final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window); consumer.accept(a); @@ -488,10 +444,12 @@ public class StartingSurfaceDrawer { * Record the view or surface for a starting window. */ private static class StartingWindowRecord { + private static final long EARLY_START_MINIMUM_TIME_MS = 250; private final View mDecorView; private final TaskSnapshotWindow mTaskSnapshotWindow; private SplashScreenView mContentView; private boolean mSetSplashScreen; + private long mContentCreateTime; StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) { mDecorView = decorView; @@ -503,11 +461,12 @@ public class StartingSurfaceDrawer { return; } mContentView = splashScreenView; + mContentCreateTime = SystemClock.uptimeMillis(); mSetSplashScreen = true; } - private boolean isWaitForContent() { - return mDecorView != null && !mSetSplashScreen; + boolean isEarlyExit() { + return SystemClock.uptimeMillis() - mContentCreateTime < EARLY_START_MINIMUM_TIME_MS; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index 5eb7071fbd63..60f9585137f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -28,14 +28,17 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import android.view.SurfaceControl; import android.window.StartingWindowInfo; import android.window.TaskOrganizer; import android.window.TaskSnapshot; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; import java.util.function.BiConsumer; @@ -67,8 +70,16 @@ public class StartingWindowController { private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl(); private final ShellExecutor mSplashScreenExecutor; + // For Car Launcher public StartingWindowController(Context context, ShellExecutor splashScreenExecutor) { - mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor); + mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, + new TransactionPool()); + mSplashScreenExecutor = splashScreenExecutor; + } + + public StartingWindowController(Context context, ShellExecutor splashScreenExecutor, + TransactionPool pool) { + mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool); mSplashScreenExecutor = splashScreenExecutor; } @@ -198,8 +209,9 @@ public class StartingWindowController { /** * Called when the content of a task is ready to show, starting window can be removed. */ - void removeStartingWindow(int taskId) { - mStartingSurfaceDrawer.removeStartingWindow(taskId); + void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { + mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation); } private class StartingSurfaceImpl implements StartingSurface { @@ -211,9 +223,11 @@ public class StartingWindowController { } @Override - public void removeStartingWindow(int taskId) { + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { mSplashScreenExecutor.execute(() -> - StartingWindowController.this.removeStartingWindow(taskId)); + StartingWindowController.this.removeStartingWindow(taskId, leash, frame, + playRevealAnimation)); } @Override diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index a531ef58725d..624c27fe9fd2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -33,11 +33,12 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.testing.TestableContext; -import android.view.Choreographer; +import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; import android.view.WindowMetrics; @@ -49,6 +50,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.startingsurface.StartingSurfaceDrawer; import org.junit.Before; @@ -68,7 +70,7 @@ public class StartingSurfaceDrawerTests { @Mock private WindowManager mMockWindowManager; @Mock - private static Choreographer sFakeChoreographer; + private TransactionPool mTransactionPool; TestStartingSurfaceDrawer mStartingSurfaceDrawer; @@ -76,13 +78,9 @@ public class StartingSurfaceDrawerTests { int mAddWindowForTask = 0; int mViewThemeResId; - TestStartingSurfaceDrawer(Context context, ShellExecutor executor) { - super(context, executor); - } - - @Override - protected void initChoreographer() { - mChoreographer = sFakeChoreographer; + TestStartingSurfaceDrawer(Context context, ShellExecutor animExecutor, + TransactionPool pool) { + super(context, animExecutor, pool); } @Override @@ -95,7 +93,8 @@ public class StartingSurfaceDrawerTests { } @Override - protected void removeWindowSynced(int taskId) { + protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { // listen for removeView if (mAddWindowForTask == taskId) { mAddWindowForTask = 0; @@ -123,7 +122,8 @@ public class StartingSurfaceDrawerTests { doNothing().when(mMockWindowManager).addView(any(), any()); mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context, - new HandlerExecutor(new Handler(Looper.getMainLooper())))); + new HandlerExecutor(new Handler(Looper.getMainLooper())), + mTransactionPool)); } @Test @@ -137,9 +137,9 @@ public class StartingSurfaceDrawerTests { verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any()); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); - mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId); + mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId, null, null, false); waitHandlerIdle(mainLoop); - verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId)); + verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId), any(), any(), eq(false)); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index a12326961b08..1b6c61237e29 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -480,8 +480,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static StartingWindowController provideStartingWindowController(Context context, - @ShellSplashscreenThread ShellExecutor executor) { - return new StartingWindowController(context, executor); + @ShellAnimationThread ShellExecutor executor, TransactionPool pool) { + return new StartingWindowController(context, executor, pool); } // diff --git a/services/core/java/com/android/server/policy/SplashScreenSurface.java b/services/core/java/com/android/server/policy/SplashScreenSurface.java index b9202c334fec..72933a0ad309 100644 --- a/services/core/java/com/android/server/policy/SplashScreenSurface.java +++ b/services/core/java/com/android/server/policy/SplashScreenSurface.java @@ -45,7 +45,7 @@ class SplashScreenSurface implements StartingSurface { } @Override - public void remove() { + public void remove(boolean animate) { if (DEBUG_SPLASH_SCREEN) Slog.v(TAG, "Removing splash screen window for " + mAppToken + ": " + this + " Callers=" + Debug.getCallers(4)); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index b5a9acacec83..8d644611cfbf 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -238,8 +238,9 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { /** * Removes the starting window surface. Do not hold the window manager lock when calling * this method! + * @param animate Whether need to play the default exit animation for starting window. */ - void remove(); + void remove(boolean animate); } /** diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index aeeabe21460c..d132f9e96479 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2114,7 +2114,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } if (abort) { - surface.remove(); + surface.remove(false /* prepareAnimation */); } } else { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s", @@ -2179,7 +2179,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A + ActivityRecord.this + " state " + mTransferringSplashScreenState); if (isTransferringSplashScreen()) { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; - // TODO show default exit splash screen animation removeStartingWindow(); } } @@ -2196,6 +2195,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } private boolean transferSplashScreenIfNeeded() { + if (!mWmService.mStartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER) { + return false; + } if (!mHandleExitSplashScreen || mStartingSurface == null || mStartingWindow == null || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) { return false; @@ -2265,10 +2267,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // no matter what, remove the starting window. mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; - removeStartingWindow(); + removeStartingWindowAnimation(false /* prepareAnimation */); } void removeStartingWindow() { + removeStartingWindowAnimation(true /* prepareAnimation */); + } + + void removeStartingWindowAnimation(boolean prepareAnimation) { if (transferSplashScreenIfNeeded()) { return; } @@ -2313,7 +2319,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mWmService.mAnimationHandler.post(() -> { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Removing startingView=%s", surface); try { - surface.remove(); + surface.remove(prepareAnimation); } catch (Exception e) { Slog.w(TAG_WM, "Exception when removing starting window", e); } @@ -6190,7 +6196,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Remove orphaned starting window. if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this); mStartingWindowState = STARTING_WINDOW_REMOVED; - removeStartingWindow(); + removeStartingWindowAnimation(false /* prepareAnimation */); } if (isState(INITIALIZING) && !shouldBeVisible( true /* behindFullscreenActivity */, true /* ignoringKeyguard */)) { diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index ef4a40f4837c..70666e7d9e9b 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -40,7 +40,7 @@ public class StartingSurfaceController { private static final String TAG = TAG_WITH_CLASS_NAME ? StartingSurfaceController.class.getSimpleName() : TAG_WM; /** Set to {@code true} to enable shell starting surface drawer. */ - private static final boolean DEBUG_ENABLE_SHELL_DRAWER = + static final boolean DEBUG_ENABLE_SHELL_DRAWER = SystemProperties.getBoolean("persist.debug.shell_starting_surface", false); private final WindowManagerService mService; @@ -139,8 +139,9 @@ public class StartingSurfaceController { } @Override - public void remove() { - mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask); + public void remove(boolean animate) { + mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, + animate); } } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index fc6db61bdbcd..385dc79567fa 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -32,6 +32,7 @@ import android.app.WindowConfiguration; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; @@ -131,10 +132,28 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { }); } - void removeStartingWindow(Task task) { + void removeStartingWindow(Task task, boolean prepareAnimation) { mDeferTaskOrgCallbacksConsumer.accept(() -> { + SurfaceControl firstWindowLeash = null; + Rect mainFrame = null; + // TODO enable shift up animation once we fix flicker test +// final boolean playShiftUpAnimation = !task.inMultiWindowMode(); +// if (prepareAnimation && playShiftUpAnimation) { +// final ActivityRecord topActivity = task.topActivityWithStartingWindow(); +// if (topActivity != null) { +// final WindowState mainWindow = +// topActivity.findMainWindow(false/* includeStartingApp */); +// if (mainWindow != null) { + // TODO create proper leash instead of the copied SC +// firstWindowLeash = new SurfaceControl(mainWindow.getSurfaceControl(), +// "TaskOrganizerController.removeStartingWindow"); +// mainFrame = mainWindow.getRelativeFrame(); +// } +// } +// } try { - mTaskOrganizer.removeStartingWindow(task.mTaskId); + mTaskOrganizer.removeStartingWindow(task.mTaskId, firstWindowLeash, mainFrame, + prepareAnimation); } catch (RemoteException e) { Slog.e(TAG, "Exception sending onStartTaskFinished callback", e); } @@ -249,8 +268,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mOrganizer.addStartingWindow(t, appToken, launchTheme); } - void removeStartingWindow(Task t) { - mOrganizer.removeStartingWindow(t); + void removeStartingWindow(Task t, boolean prepareAnimation) { + mOrganizer.removeStartingWindow(t, prepareAnimation); } void copySplashScreenView(Task t) { @@ -495,14 +514,14 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return true; } - void removeStartingWindow(Task task) { + void removeStartingWindow(Task task, boolean prepareAnimation) { final Task rootTask = task.getRootTask(); if (rootTask == null || rootTask.mTaskOrganizer == null) { return; } final TaskOrganizerState state = mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder()); - state.removeStartingWindow(task); + state.removeStartingWindow(task, prepareAnimation); } boolean copySplashScreenView(Task task) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index 07610ab6d546..79a6bd7dcd2c 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -281,13 +281,14 @@ class TaskSnapshotSurface implements StartingSurface { } @Override - public void remove() { + public void remove(boolean animate) { synchronized (mService.mGlobalLock) { final long now = SystemClock.uptimeMillis(); if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS // Show the latest content as soon as possible for unlocking to home. && mActivityType != ACTIVITY_TYPE_HOME) { - mHandler.postAtTime(this::remove, mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS); + mHandler.postAtTime(() -> remove(false /* prepareAnimation */), + mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS); ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Defer removing snapshot surface in %dms", (now - mShownTime)); @@ -517,7 +518,7 @@ class TaskSnapshotSurface implements StartingSurface { // The orientation of the screen is changing. We better remove the snapshot ASAP as // we are going to wait on the new window in any case to unfreeze the screen, and // the starting window is not needed anymore. - sHandler.post(mOuter::remove); + sHandler.post(() -> mOuter.remove(false /* prepareAnimation */)); } if (reportDraw) { sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 86d8eee878fd..7822a8514a13 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -119,7 +119,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { mRunnableWhenAddingSplashScreen.run(); mRunnableWhenAddingSplashScreen = null; } - return () -> { + return (a) -> { synchronized (wm.mGlobalLock) { activity.removeChild(window); activity.mStartingWindow = null; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 2c2c09a5750a..01c503e01326 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -547,7 +547,8 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Override - public void removeStartingWindow(int taskId) { } + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { } @@ -614,7 +615,8 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Override - public void removeStartingWindow(int taskId) { } + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { } @Override @@ -688,7 +690,8 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Override - public void removeStartingWindow(int taskId) { } + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { } @Override @@ -832,7 +835,8 @@ public class WindowOrganizerTests extends WindowTestsBase { @Override public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(int taskId) { } + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 827ff6c18a68..4ee459a10f0b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -67,6 +67,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Bundle; @@ -1161,7 +1162,8 @@ class WindowTestsBase extends SystemServiceTestsBase { public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(int taskId) { + public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, + boolean playRevealAnimation) { } @Override public void copySplashScreenView(int taskId) { |