From f4e4bab40350897dff3224873f98bb71ab2099d7 Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Tue, 19 May 2020 11:28:32 -0700 Subject: Ignores entering PiP animation on seamless rotation - Added onFixedRotationStarted/onFixedRotationFinished callback in DisplayWindowListener - onFixedRotationStarted shall be called before onTaskAppeared for PiP - When onTaskAppeared is received in PipTaskOrganizer, we defer the entering PiP transition if fixed rotation is ongoing - When onFixedRotationFinished is received in PipTaskOrganizer and the entering PiP transition is deferred, schedule an immediate transition to PiP to make sure all the expected callbacks from PipTaskOrganizer are still being sent Video: http://go/recall/-/aaaaaabFQoRHlzixHdtY/bb8HjJvMZKtpN8YTPKZXmj Bug: 153861223 Test: manually enter PiP from Play Movies / YT fullscreen Test: atest ActivityRecordTests \ RecentsAnimationControllerTest \ ActivityTaskManagerServiceTests \ PinnedStackTests Change-Id: I0dea905d610e2387af56b611be5f93518cc9a153 --- core/java/android/view/IDisplayWindowListener.aidl | 9 ++ .../com/android/systemui/pip/PipTaskOrganizer.java | 116 +++++++++++++++------ .../com/android/systemui/wm/DisplayController.java | 43 ++++++++ .../java/com/android/server/wm/DisplayContent.java | 31 ++++-- .../com/android/server/wm/DisplayRotation.java | 2 +- .../server/wm/DisplayWindowListenerController.java | 23 ++++ .../com/android/server/wm/ActivityRecordTests.java | 10 +- .../server/wm/ActivityTaskManagerServiceTests.java | 6 ++ .../server/wm/RecentsAnimationControllerTest.java | 10 +- 9 files changed, 200 insertions(+), 50 deletions(-) diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl index 973a208bf485..610e0f866b43 100644 --- a/core/java/android/view/IDisplayWindowListener.aidl +++ b/core/java/android/view/IDisplayWindowListener.aidl @@ -46,4 +46,13 @@ oneway interface IDisplayWindowListener { */ void onDisplayRemoved(int displayId); + /** + * Called when fixed rotation is started on a display. + */ + void onFixedRotationStarted(int displayId, int newRotation); + + /** + * Called when the previous fixed rotation on a display is finished. + */ + void onFixedRotationFinished(int displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index ae6100675cb4..72d76b9a4e8b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -58,6 +58,7 @@ import com.android.internal.os.SomeArgs; import com.android.systemui.R; import com.android.systemui.pip.phone.PipUpdateThread; import com.android.systemui.stackdivider.Divider; +import com.android.systemui.wm.DisplayController; import java.io.PrintWriter; import java.util.ArrayList; @@ -82,8 +83,10 @@ import javax.inject.Singleton; * see also {@link com.android.systemui.pip.phone.PipMotionHelper}. */ @Singleton -public class PipTaskOrganizer extends TaskOrganizer { +public class PipTaskOrganizer extends TaskOrganizer implements + DisplayController.OnDisplaysChangedListener { private static final String TAG = PipTaskOrganizer.class.getSimpleName(); + private static final boolean DEBUG = false; private static final int MSG_RESIZE_IMMEDIATE = 1; private static final int MSG_RESIZE_ANIMATE = 2; @@ -206,10 +209,17 @@ public class PipTaskOrganizer extends TaskOrganizer { mSurfaceControlTransactionFactory; private PictureInPictureParams mPictureInPictureParams; + /** + * If set to {@code true}, the entering animation will be skipped and we will wait for + * {@link #onFixedRotationFinished(int)} callback to actually enter PiP. + */ + private boolean mShouldDeferEnteringPip; + @Inject public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, - @Nullable Divider divider) { + @Nullable Divider divider, + @NonNull DisplayController displayController) { mMainHandler = new Handler(Looper.getMainLooper()); mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks); mPipBoundsHandler = boundsHandler; @@ -219,6 +229,7 @@ public class PipTaskOrganizer extends TaskOrganizer { mPipAnimationController = new PipAnimationController(context, surfaceTransactionHelper); mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; mSplitDivider = divider; + displayController.addDisplayWindowListener(this); } public Handler getUpdateHandler() { @@ -281,7 +292,8 @@ public class PipTaskOrganizer extends TaskOrganizer { final int direction = syncWithSplitScreenBounds(destinationBounds) ? TRANSITION_DIRECTION_TO_SPLIT_SCREEN : TRANSITION_DIRECTION_TO_FULLSCREEN; - final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds, mLastReportedBounds); tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height()); @@ -325,53 +337,67 @@ public class PipTaskOrganizer extends TaskOrganizer { @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) { Objects.requireNonNull(info, "Requires RunningTaskInfo"); - mPictureInPictureParams = info.pictureInPictureParams; - final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - info.topActivity, getAspectRatioOrDefault(mPictureInPictureParams), - null /* bounds */, getMinimalSize(info.topActivityInfo)); - Objects.requireNonNull(destinationBounds, "Missing destination bounds"); mTaskInfo = info; mToken = mTaskInfo.token; mInPip = true; mLeash = leash; + mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration)); + mPictureInPictureParams = mTaskInfo.pictureInPictureParams; + + if (mShouldDeferEnteringPip) { + if (DEBUG) Log.d(TAG, "Defer entering PiP animation, fixed rotation is ongoing"); + // if deferred, hide the surface till fixed rotation is completed + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + tx.setAlpha(mLeash, 0f); + tx.apply(); + return; + } - // TODO: Skip enter animation when entering pip from another orientation + final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( + mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams), + null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo)); + Objects.requireNonNull(destinationBounds, "Missing destination bounds"); final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); - mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration)); if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { scheduleAnimateResizePip(currentBounds, destinationBounds, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null /* updateBoundsCallback */); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { - // If we are fading the PIP in, then we should move the pip to the final location as - // soon as possible, but set the alpha immediately since the transaction can take a - // while to process - final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); - tx.setAlpha(mLeash, 0f); - tx.apply(); - final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); - wct.setBounds(mToken, destinationBounds); - wct.scheduleFinishEnterPip(mToken, destinationBounds); - applySyncTransaction(wct, new WindowContainerTransactionCallback() { - @Override - public void onTransactionReady(int id, SurfaceControl.Transaction t) { - t.apply(); - mUpdateHandler.post(() -> mPipAnimationController - .getAnimator(mLeash, destinationBounds, 0f, 1f) - .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) - .setPipAnimationCallback(mPipAnimationCallback) - .setDuration(mEnterExitAnimationDuration) - .start()); - } - }); + enterPipWithAlphaAnimation(destinationBounds, mEnterExitAnimationDuration); mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType); } } + private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) { + // If we are fading the PIP in, then we should move the pip to the final location as + // soon as possible, but set the alpha immediately since the transaction can take a + // while to process + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + tx.setAlpha(mLeash, 0f); + tx.apply(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); + wct.setBounds(mToken, destinationBounds); + wct.scheduleFinishEnterPip(mToken, destinationBounds); + applySyncTransaction(wct, new WindowContainerTransactionCallback() { + @Override + public void onTransactionReady(int id, SurfaceControl.Transaction t) { + t.apply(); + mUpdateHandler.post(() -> mPipAnimationController + .getAnimator(mLeash, destinationBounds, 0f, 1f) + .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) + .setPipAnimationCallback(mPipAnimationCallback) + .setDuration(durationMs) + .start()); + } + }); + } + /** * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}. * Meanwhile this callback is invoked whenever the task is removed. For instance: @@ -391,6 +417,7 @@ public class PipTaskOrganizer extends TaskOrganizer { Log.wtf(TAG, "Unrecognized token: " + token); return; } + mShouldDeferEnteringPip = false; mPictureInPictureParams = null; mInPip = false; } @@ -416,6 +443,23 @@ public class PipTaskOrganizer extends TaskOrganizer { // Do nothing } + @Override + public void onFixedRotationStarted(int displayId, int newRotation) { + mShouldDeferEnteringPip = true; + } + + @Override + public void onFixedRotationFinished(int displayId) { + if (mShouldDeferEnteringPip && mInPip) { + final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( + mTaskInfo.topActivity, getAspectRatioOrDefault(mPictureInPictureParams), + null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo)); + // schedule a regular animation to ensure all the callbacks are still being sent + enterPipWithAlphaAnimation(destinationBounds, 0 /* durationMs */); + } + mShouldDeferEnteringPip = false; + } + /** * TODO(b/152809058): consolidate the display info handling logic in SysUI * @@ -476,6 +520,10 @@ public class PipTaskOrganizer extends TaskOrganizer { */ public void scheduleAnimateResizePip(Rect toBounds, int duration, Consumer updateBoundsCallback) { + if (mShouldDeferEnteringPip) { + Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred"); + return; + } scheduleAnimateResizePip(mLastReportedBounds, toBounds, TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback); } @@ -567,6 +615,10 @@ public class PipTaskOrganizer extends TaskOrganizer { // can be initiated in other component, ignore if we are no longer in PIP return; } + if (mShouldDeferEnteringPip) { + Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred"); + return; + } SomeArgs args = SomeArgs.obtain(); args.arg1 = updateBoundsCallback; args.arg2 = originalBounds; diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java index c66f07dd4f1e..083c2439aa87 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java @@ -134,6 +134,39 @@ public class DisplayController { } }); } + + @Override + public void onFixedRotationStarted(int displayId, int newRotation) { + mHandler.post(() -> { + synchronized (mDisplays) { + if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { + Slog.w(TAG, "Skipping onFixedRotationStarted on unknown" + + " display, displayId=" + displayId); + return; + } + for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { + mDisplayChangedListeners.get(i).onFixedRotationStarted( + displayId, newRotation); + } + } + }); + } + + @Override + public void onFixedRotationFinished(int displayId) { + mHandler.post(() -> { + synchronized (mDisplays) { + if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { + Slog.w(TAG, "Skipping onFixedRotationFinished on unknown" + + " display, displayId=" + displayId); + return; + } + for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { + mDisplayChangedListeners.get(i).onFixedRotationFinished(displayId); + } + } + }); + } }; @Inject @@ -232,5 +265,15 @@ public class DisplayController { * Called when a display is removed. */ default void onDisplayRemoved(int displayId) {} + + /** + * Called when fixed rotation on a display is started. + */ + default void onFixedRotationStarted(int displayId, int newRotation) {} + + /** + * Called when fixed rotation on a display is finished. + */ + default void onFixedRotationFinished(int displayId) {} } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 0efb9698f4b0..d93e9764146f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -493,10 +493,10 @@ class DisplayContent extends WindowContainer applyRotation(oldRotation, newRotation)); - mFixedRotationLaunchingApp = null; + setFixedRotationLaunchingAppUnchecked(null); } /** Checks whether the given activity is in size compatibility mode and notifies the change. */ @@ -5574,7 +5591,7 @@ class DisplayContent extends WindowContainer