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
This commit is contained in:
Hongwei Wang
2020-05-19 11:28:32 -07:00
parent 2d2b7250f6
commit f4e4bab403
9 changed files with 201 additions and 51 deletions

View File

@@ -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);
}

View File

@@ -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;
// TODO: Skip enter animation when entering pip from another orientation
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
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;
}
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();
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<Rect> 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;

View File

@@ -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) {}
}
}

View File

@@ -493,10 +493,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* The launching activity which is using fixed rotation transformation.
*
* @see #handleTopActivityLaunchingInDifferentOrientation
* @see #setFixedRotationLaunchingApp
* @see #setFixedRotationLaunchingApp(ActivityRecord, int)
* @see DisplayRotation#shouldRotateSeamlessly
*/
ActivityRecord mFixedRotationLaunchingApp;
private ActivityRecord mFixedRotationLaunchingApp;
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1475,6 +1475,23 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return true;
}
@Nullable ActivityRecord getFixedRotationLaunchingApp() {
return mFixedRotationLaunchingApp;
}
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
setFixedRotationLaunchingAppUnchecked(r, ROTATION_UNDEFINED);
}
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
if (mFixedRotationLaunchingApp == null && r != null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
} else if (mFixedRotationLaunchingApp != null && r == null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
}
mFixedRotationLaunchingApp = r;
}
/**
* Sets the provided record to {@link mFixedRotationLaunchingApp} if possible to apply fixed
* rotation transform to it and indicate that the display may be rotated after it is launched.
@@ -1496,7 +1513,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
if (!r.hasFixedRotationTransform()) {
startFixedRotationTransform(r, rotation);
}
mFixedRotationLaunchingApp = r;
setFixedRotationLaunchingAppUnchecked(r, rotation);
if (prevRotatedLaunchingApp != null) {
prevRotatedLaunchingApp.finishFixedRotationTransform();
}
@@ -1524,7 +1541,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
/**
* Clears the {@link mFixedRotationLaunchingApp} without applying rotation to display. It is
* Clears the {@link #mFixedRotationLaunchingApp} without applying rotation to display. It is
* used when the display won't rotate (e.g. the orientation from sensor has updated again before
* applying rotation to display) but the launching app has been transformed. So the record need
* to be cleared and restored to stop using seamless rotation and rotated configuration.
@@ -1534,7 +1551,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return;
}
mFixedRotationLaunchingApp.finishFixedRotationTransform();
mFixedRotationLaunchingApp = null;
setFixedRotationLaunchingAppUnchecked(null);
}
private void startFixedRotationTransform(WindowToken token, int rotation) {
@@ -5260,7 +5277,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
rotatedLaunchingApp.finishFixedRotationTransform(
() -> 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<DisplayContent.DisplayChildWindowCo
if (animatingRecents != null && animatingRecents == mFixedRotationLaunchingApp) {
// Because it won't affect display orientation, just finish the transform.
animatingRecents.finishFixedRotationTransform();
mFixedRotationLaunchingApp = null;
setFixedRotationLaunchingAppUnchecked(null);
} else {
// If there is already a launching activity that is not the recents, before its
// transition is completed, the recents animation may be started. So if the recents

View File

@@ -577,7 +577,7 @@ public class DisplayRotation {
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
// Display doesn't need to be frozen because application has been started in correct
// rotation already, so the rest of the windows can use seamless rotation.
if (mDisplayContent.mFixedRotationLaunchingApp != null) {
if (mDisplayContent.getFixedRotationLaunchingApp() != null) {
return true;
}

View File

@@ -95,4 +95,27 @@ class DisplayWindowListenerController {
}
mDisplayListeners.finishBroadcast();
}
void dispatchFixedRotationStarted(DisplayContent display, int newRotation) {
int count = mDisplayListeners.beginBroadcast();
for (int i = 0; i < count; ++i) {
try {
mDisplayListeners.getBroadcastItem(i).onFixedRotationStarted(
display.mDisplayId, newRotation);
} catch (RemoteException e) {
}
}
mDisplayListeners.finishBroadcast();
}
void dispatchFixedRotationFinished(DisplayContent display) {
int count = mDisplayListeners.beginBroadcast();
for (int i = 0; i < count; ++i) {
try {
mDisplayListeners.getBroadcastItem(i).onFixedRotationFinished(display.mDisplayId);
} catch (RemoteException e) {
}
}
mDisplayListeners.finishBroadcast();
}
}

View File

@@ -1405,14 +1405,14 @@ public class ActivityRecordTests extends ActivityTestsBase {
mActivity.setVisibility(true);
display.rotateInDifferentOrientationIfNeeded(mActivity);
display.mFixedRotationLaunchingApp = mActivity;
display.setFixedRotationLaunchingAppUnchecked(mActivity);
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
assertTrue(displayRotation.isRotatingSeamlessly());
// The launching rotated app should not be cleared when waiting for remote rotation.
display.continueUpdateOrientationForDiffOrienLaunchingApp();
assertNotNull(display.mFixedRotationLaunchingApp);
assertNotNull(display.getFixedRotationLaunchingApp());
// Simulate the rotation has been updated to previous one, e.g. sensor updates before the
// remote rotation is completed.
@@ -1438,10 +1438,10 @@ public class ActivityRecordTests extends ActivityTestsBase {
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
doReturn(false).when(displayRotation).isWaitingForRemoteRotation();
clearInvocations(mActivity);
display.mFixedRotationLaunchingApp = mActivity;
display.setFixedRotationLaunchingAppUnchecked(mActivity);
display.sendNewConfiguration();
assertNull(display.mFixedRotationLaunchingApp);
assertNull(display.getFixedRotationLaunchingApp());
assertFalse(mActivity.hasFixedRotationTransform());
}
@@ -1497,7 +1497,7 @@ public class ActivityRecordTests extends ActivityTestsBase {
// rotation should be applied when creating snapshot surface if the display rotation may be
// changed according to the activity orientation.
assertTrue(mActivity.hasFixedRotationTransform());
assertEquals(mActivity, mActivity.mDisplayContent.mFixedRotationLaunchingApp);
assertEquals(mActivity, mActivity.mDisplayContent.getFixedRotationLaunchingApp());
}
/**

View File

@@ -140,6 +140,12 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase {
public void onDisplayRemoved(int displayId) {
removed.add(displayId);
}
@Override
public void onFixedRotationStarted(int displayId, int newRotation) {}
@Override
public void onFixedRotationFinished(int displayId) {}
};
mService.mWindowManager.registerDisplayWindowListener(listener);
// Check that existing displays call added

View File

@@ -343,7 +343,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
initializeRecentsAnimationController(mController, homeActivity);
assertEquals(homeActivity, mDefaultDisplay.mFixedRotationLaunchingApp);
assertEquals(homeActivity, mDefaultDisplay.getFixedRotationLaunchingApp());
// Check that the home app is in portrait
assertEquals(Configuration.ORIENTATION_PORTRAIT,
@@ -353,7 +353,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
// top rotated record should be cleared.
mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
assertFalse(homeActivity.hasFixedRotationTransform());
assertNull(mDefaultDisplay.mFixedRotationLaunchingApp);
assertNull(mDefaultDisplay.getFixedRotationLaunchingApp());
}
@Test
@@ -367,7 +367,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
(mDefaultDisplay.getRotation() + 1) % 4);
assertTrue(activity.hasFixedRotationTransform());
assertEquals(activity, mDefaultDisplay.mFixedRotationLaunchingApp);
assertEquals(activity, mDefaultDisplay.getFixedRotationLaunchingApp());
// Before the transition is done, the recents animation is triggered.
initializeRecentsAnimationController(mController, homeActivity);
@@ -377,7 +377,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
// The rotation transform should be cleared after updating orientation with display.
assertFalse(activity.hasFixedRotationTransform());
assertNull(mDefaultDisplay.mFixedRotationLaunchingApp);
assertNull(mDefaultDisplay.getFixedRotationLaunchingApp());
}
@Test
@@ -436,7 +436,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
// The transform state should keep because we expect to listen the signal from the
// transition executed by moving the task to front.
assertTrue(homeActivity.hasFixedRotationTransform());
assertEquals(homeActivity, mDefaultDisplay.mFixedRotationLaunchingApp);
assertEquals(homeActivity, mDefaultDisplay.getFixedRotationLaunchingApp());
mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(
homeActivity.token);