diff options
8 files changed, 338 insertions, 2 deletions
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index 0519b80c732a..529c4f608743 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -85,4 +85,25 @@ interface AnimationAdapter { } void dumpDebug(ProtoOutputStream proto); + + /** + * Gets called when the animation is about to finish and gives the client the opportunity to + * defer finishing the animation, i.e. it keeps the leash around until the client calls + * endDeferFinishCallback. + * <p> + * This has the same effect as + * {@link com.android.server.wm.SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)} + * . The later will be evaluated first and has precedence over this method if it returns true, + * which means that if the {@link com.android.server.wm.SurfaceAnimator.Animatable} requests to + * defer its finish, this method won't be called so this adapter will never have access to the + * finish callback. On the other hand, if the + * {@link com.android.server.wm.SurfaceAnimator.Animatable}, doesn't request to defer, this + * {@link AnimationAdapter} is responsible for ending the animation. + * + * @param endDeferFinishCallback The callback to call when defer finishing should be ended. + * @return Whether the client would like to defer the animation finish. + */ + default boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) { + return false; + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e31eaf76b100..6655e92634e4 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -499,6 +499,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ ActivityRecord mFixedRotationLaunchingApp; + FixedRotationAnimationController mFixedRotationAnimationController; + final FixedRotationTransitionListener mFixedRotationTransitionListener = new FixedRotationTransitionListener(); @@ -1528,6 +1530,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private void startFixedRotationTransform(WindowToken token, int rotation) { + if (mFixedRotationAnimationController == null) { + mFixedRotationAnimationController = new FixedRotationAnimationController( + this); + } + mFixedRotationAnimationController.hide(rotation); mTmpConfiguration.unset(); final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation); final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation); @@ -1549,6 +1556,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + void finishFixedRotationAnimation() { + if (mFixedRotationAnimationController != null + && mFixedRotationAnimationController.show()) { + mFixedRotationAnimationController = null; + } + } + /** * Update rotation of the display. * diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index ebfe70c0c371..c3f906135a00 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -560,6 +560,7 @@ public class DisplayRotation { }, true /* traverseTopToBottom */); mSeamlessRotationCount = 0; mRotatingSeamlessly = false; + mDisplayContent.finishFixedRotationAnimation(); } private void prepareSeamlessRotation() { @@ -646,6 +647,7 @@ public class DisplayRotation { "Performing post-rotate rotation after seamless rotation"); // Finish seamless rotation. mRotatingSeamlessly = false; + mDisplayContent.finishFixedRotationAnimation(); updateRotationAndSendNewConfigIfChanged(); } diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java new file mode 100644 index 000000000000..7aca63774889 --- /dev/null +++ b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java @@ -0,0 +1,197 @@ +/* + * 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.server.wm; + +import static com.android.server.wm.AnimationSpecProto.WINDOW; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM; +import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; + +import android.content.res.Configuration; +import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Transformation; + +import com.android.internal.R; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Controller to fade out and in system ui when applying a fixed rotation transform to a window + * token. + * + * The system bars will be fade out when the fixed rotation transform starts and will be fade in + * once all surfaces have been rotated. + */ +public class FixedRotationAnimationController { + + private final WindowManagerService mWmService; + private boolean mShowRequested = true; + private int mTargetRotation = Configuration.ORIENTATION_UNDEFINED; + private final ArrayList<WindowState> mAnimatedWindowStates = new ArrayList<>(2); + private final Runnable[] mDeferredFinishCallbacks; + + public FixedRotationAnimationController(DisplayContent displayContent) { + mWmService = displayContent.mWmService; + addAnimatedWindow(displayContent.getDisplayPolicy().getStatusBar()); + addAnimatedWindow(displayContent.getDisplayPolicy().getNavigationBar()); + mDeferredFinishCallbacks = new Runnable[mAnimatedWindowStates.size()]; + } + + private void addAnimatedWindow(WindowState windowState) { + if (windowState != null) { + mAnimatedWindowStates.add(windowState); + } + } + + /** + * Show the previously hidden {@link WindowToken} if their surfaces have already been rotated. + * + * @return True if the show animation has been started, in which case the caller no longer needs + * this {@link FixedRotationAnimationController}. + */ + boolean show() { + if (!mShowRequested && readyToShow()) { + mShowRequested = true; + for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) { + WindowState windowState = mAnimatedWindowStates.get(i); + fadeWindowToken(true, windowState.getParent(), i); + } + return true; + } + return false; + } + + void hide(int targetRotation) { + mTargetRotation = targetRotation; + if (mShowRequested) { + mShowRequested = false; + for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) { + WindowState windowState = mAnimatedWindowStates.get(i); + fadeWindowToken(false /* show */, windowState.getParent(), i); + } + } + } + + void cancel() { + for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) { + WindowState windowState = mAnimatedWindowStates.get(i); + mShowRequested = true; + fadeWindowToken(true /* show */, windowState.getParent(), i); + } + } + + private void fadeWindowToken(boolean show, WindowContainer<WindowToken> windowToken, + int index) { + Animation animation = AnimationUtils.loadAnimation(mWmService.mContext, + show ? R.anim.fade_in : R.anim.fade_out); + LocalAnimationAdapter.AnimationSpec windowAnimationSpec = createAnimationSpec(animation); + + FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter( + windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, index); + + // We deferred the end of the animation when hiding the token, so we need to end it now that + // it's shown again. + SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> { + if (mDeferredFinishCallbacks[index] != null) { + mDeferredFinishCallbacks[index].run(); + mDeferredFinishCallbacks[index] = null; + } + } : null; + windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter, + mShowRequested, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback); + } + + /** + * Check if all the mAnimatedWindowState's surfaces have been rotated to the + * mTargetRotation. + */ + private boolean readyToShow() { + for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) { + WindowState windowState = mAnimatedWindowStates.get(i); + if (windowState.getConfiguration().windowConfiguration.getRotation() + != mTargetRotation || windowState.mPendingSeamlessRotate != null) { + return false; + } + } + return true; + } + + + private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) { + return new LocalAnimationAdapter.AnimationSpec() { + + Transformation mTransformation = new Transformation(); + + @Override + public boolean getShowWallpaper() { + return true; + } + + @Override + public long getDuration() { + return animation.getDuration(); + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl leash, + long currentPlayTime) { + mTransformation.clear(); + animation.getTransformation(currentPlayTime, mTransformation); + t.setAlpha(leash, mTransformation.getAlpha()); + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.println(animation); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(WINDOW); + proto.write(ANIMATION, animation.toString()); + proto.end(token); + } + }; + } + + private class FixedRotationAnimationAdapter extends LocalAnimationAdapter { + private final boolean mShow; + private final int mIndex; + + FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec, + SurfaceAnimationRunner surfaceAnimationRunner, boolean show, int index) { + super(windowAnimationSpec, surfaceAnimationRunner); + mShow = show; + mIndex = index; + } + + @Override + public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) { + // We defer the end of the hide animation to ensure the tokens stay hidden until + // we show them again. + if (!mShow) { + mDeferredFinishCallbacks[mIndex] = endDeferFinishCallback; + return true; + } + return false; + } + } +} diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 18e32c0683d6..0143eb1abe03 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -109,7 +109,10 @@ class SurfaceAnimator { animationFinishCallback.onAnimationFinished(type, anim); } }; - if (!mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)) { + // If both the Animatable and AnimationAdapter requests to be deferred, only the + // first one will be called. + if (!(mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish) + || anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) { resetAndInvokeFinish.run(); } } @@ -486,6 +489,12 @@ class SurfaceAnimator { static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5; /** + * Animation when a fixed rotation transform is applied to a window token. + * @hide + */ + static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6; + + /** * Bitmask to include all animation types. This is NOT an {@link AnimationType} * @hide */ @@ -502,7 +511,8 @@ class SurfaceAnimator { ANIMATION_TYPE_DIMMER, ANIMATION_TYPE_RECENTS, ANIMATION_TYPE_WINDOW_ANIMATION, - ANIMATION_TYPE_INSETS_CONTROL + ANIMATION_TYPE_INSETS_CONTROL, + ANIMATION_TYPE_FIXED_TRANSFORM }) @Retention(RetentionPolicy.SOURCE) @interface AnimationType {} @@ -592,6 +602,12 @@ class SurfaceAnimator { * Gets called when the animation is about to finish and gives the client the opportunity to * defer finishing the animation, i.e. it keeps the leash around until the client calls * {@link #cancelAnimation}. + * <p> + * {@link AnimationAdapter} has a similar method which is called only if this method returns + * false. This mean that if both this {@link Animatable} and the {@link AnimationAdapter} + * request to be deferred, this method is the sole responsible to call + * endDeferFinishCallback. On the other hand, the animation finish might still be deferred + * if this method return false and the one from the {@link AnimationAdapter} returns true. * * @param endDeferFinishCallback The callback to call when defer finishing should be ended. * @return Whether the client would like to defer the animation finish. diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 768f89eff774..8739bad4398b 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -642,6 +642,9 @@ class WindowToken extends WindowContainer<WindowState> { final int originalRotation = getWindowConfiguration().getRotation(); onConfigurationChanged(parent.getConfiguration()); onCancelFixedRotationTransform(originalRotation); + if (mDisplayContent.mFixedRotationAnimationController != null) { + mDisplayContent.mFixedRotationAnimationController.cancel(); + } } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index ac95a817bec9..7b23bfb48a1a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -57,6 +57,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.same; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; @@ -1059,6 +1060,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testApplyTopFixedRotationTransform() { mWm.mIsFixedRotationTransformEnabled = true; + mDisplayContent.getDisplayPolicy().addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs); + mDisplayContent.getDisplayPolicy().addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs); final Configuration config90 = new Configuration(); mDisplayContent.computeScreenConfiguration(config90, ROTATION_90); @@ -1079,6 +1082,12 @@ public class DisplayContentTests extends WindowTestsBase { ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */, false /* forceUpdate */)); + assertNotNull(mDisplayContent.mFixedRotationAnimationController); + assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS, + ANIMATION_TYPE_FIXED_TRANSFORM)); + assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS, + ANIMATION_TYPE_FIXED_TRANSFORM)); + final Rect outFrame = new Rect(); final Rect outInsets = new Rect(); final Rect outStableInsets = new Rect(); @@ -1131,6 +1140,9 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(app.hasFixedRotationTransform()); assertFalse(app2.hasFixedRotationTransform()); assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation); + + mDisplayContent.finishFixedRotationAnimation(); + assertNull(mDisplayContent.mFixedRotationAnimationController); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java index 552c476613b2..79ba1759f4c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import android.platform.test.annotations.Presubmit; +import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.view.SurfaceControl.Builder; import android.view.SurfaceControl.Transaction; @@ -52,6 +53,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.PrintWriter; + /** * Test class for {@link SurfaceAnimatorTest}. * @@ -267,6 +270,27 @@ public class SurfaceAnimatorTest extends WindowTestsBase { assertFalse(mDeferFinishAnimatable.mFinishedCallbackCalled); } + @Test + public void testDeferFinishFromAdapter() { + + DeferredFinishAdapter deferredFinishAdapter = new DeferredFinishAdapter(); + // Start animation + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, deferredFinishAdapter, + true /* hidden */, + ANIMATION_TYPE_APP_TRANSITION); + assertAnimating(mAnimatable); + deferredFinishAdapter.mFinishCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, + deferredFinishAdapter); + + assertAnimating(mAnimatable); + assertFalse(mAnimatable.mFinishedCallbackCalled); + // Now end defer finishing. + deferredFinishAdapter.mEndDeferFinishCallback.run(); + assertNotAnimating(mAnimatable); + assertTrue(mAnimatable.mFinishedCallbackCalled); + verify(mTransaction).remove(eq(deferredFinishAdapter.mAnimationLeash)); + } + private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) { mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim, true /* hidden */, ANIMATION_TYPE_APP_TRANSITION); @@ -389,4 +413,51 @@ public class SurfaceAnimatorTest extends WindowTestsBase { return true; } } + + private static class DeferredFinishAdapter implements AnimationAdapter { + + private Runnable mEndDeferFinishCallback; + private OnAnimationFinishedCallback mFinishCallback; + private SurfaceControl mAnimationLeash; + + @Override + public boolean getShowWallpaper() { + return true; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, Transaction t, int type, + OnAnimationFinishedCallback finishCallback) { + mFinishCallback = finishCallback; + mAnimationLeash = animationLeash; + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + } + + @Override + public long getDurationHint() { + return 100; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return 100; + } + + @Override + public void dump(PrintWriter pw, String prefix) { + } + + @Override + public void dumpDebug(ProtoOutputStream proto) { + } + + @Override + public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) { + mEndDeferFinishCallback = endDeferFinishCallback; + return true; + } + } } |