Merge changes I24d33b94,I9db3c5d1 into rvc-dev
* changes:
System Bars animation for fixed rotation transform
Defer animation finish from AnimationAdatpter
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 0519b80..529c4f6 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 @@
}
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 e31eaf7..6655e92 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 @@
*/
ActivityRecord mFixedRotationLaunchingApp;
+ FixedRotationAnimationController mFixedRotationAnimationController;
+
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1528,6 +1530,11 @@
}
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 @@
}
}
+ 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 ebfe70c..c3f9061 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 @@
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
+ mDisplayContent.finishFixedRotationAnimation();
}
private void prepareSeamlessRotation() {
@@ -646,6 +647,7 @@
"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 0000000..7aca637
--- /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 18e32c0..0143eb1 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 @@
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 @@
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 @@
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 @@
* 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 768f89e..8739bad 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 @@
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 ac95a81..7b23bfb 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.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 @@
@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 @@
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 @@
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 552c476..79ba175 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.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.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.PrintWriter;
+
/**
* Test class for {@link SurfaceAnimatorTest}.
*
@@ -267,6 +270,27 @@
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 @@
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;
+ }
+ }
}