Merge "Fix entering PiP transition in button navigation mode" into sc-dev am: 73ef335c0f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15329896

Change-Id: I73c79942f7fc7fb44d881929121100aa768e53cd
This commit is contained in:
TreeHugger Robot
2021-07-21 03:05:12 +00:00
committed by Automerger Merge Worker
5 changed files with 130 additions and 26 deletions

View File

@@ -26,10 +26,14 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.app.TaskInfo; import android.app.TaskInfo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect; import android.graphics.Rect;
import android.view.Choreographer; import android.view.Choreographer;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SurfaceSession;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -253,6 +257,7 @@ public class PipAnimationController {
mSurfaceControlTransactionFactory; mSurfaceControlTransactionFactory;
private PipSurfaceTransactionHelper mSurfaceTransactionHelper; private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private @TransitionDirection int mTransitionDirection; private @TransitionDirection int mTransitionDirection;
protected SurfaceControl mContentOverlay;
private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
@AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue, @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue,
@@ -331,6 +336,53 @@ public class PipAnimationController {
return false; return false;
} }
SurfaceControl getContentOverlay() {
return mContentOverlay;
}
PipTransitionAnimator<T> setUseContentOverlay(Context context) {
final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
if (mContentOverlay != null) {
// remove existing content overlay if there is any.
tx.remove(mContentOverlay);
tx.apply();
}
mContentOverlay = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite("PipAnimation")
.setName("PipContentOverlay")
.setColorLayer()
.build();
tx.show(mContentOverlay);
tx.setLayer(mContentOverlay, Integer.MAX_VALUE);
tx.setColor(mContentOverlay, getContentOverlayColor(context));
tx.setAlpha(mContentOverlay, 0f);
tx.reparent(mContentOverlay, mLeash);
tx.apply();
return this;
}
private float[] getContentOverlayColor(Context context) {
final TypedArray ta = context.obtainStyledAttributes(new int[] {
android.R.attr.colorBackground });
try {
int colorAccent = ta.getColor(0, 0);
return new float[] {
Color.red(colorAccent) / 255f,
Color.green(colorAccent) / 255f,
Color.blue(colorAccent) / 255f };
} finally {
ta.recycle();
}
}
/**
* Clears the {@link #mContentOverlay}, this should be done after the content overlay is
* faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay}
*/
void clearContentOverlay() {
mContentOverlay = null;
}
@VisibleForTesting @VisibleForTesting
@TransitionDirection public int getTransitionDirection() { @TransitionDirection public int getTransitionDirection() {
return mTransitionDirection; return mTransitionDirection;
@@ -517,6 +569,9 @@ public class PipAnimationController {
final Rect base = getBaseValue(); final Rect base = getBaseValue();
final Rect start = getStartValue(); final Rect start = getStartValue();
final Rect end = getEndValue(); final Rect end = getEndValue();
if (mContentOverlay != null) {
tx.setAlpha(mContentOverlay, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
if (rotatedEndRect != null) { if (rotatedEndRect != null) {
// Animate the bounds in a different orientation. It only happens when // Animate the bounds in a different orientation. It only happens when
// switching between PiP and fullscreen. // switching between PiP and fullscreen.

View File

@@ -107,6 +107,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/ */
private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000; private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
* This is to overcome the flicker caused by configuration change when rotating from landscape
* to portrait PiP in button navigation mode.
*/
private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;
// Not a complete set of states but serves what we want right now. // Not a complete set of states but serves what we want right now.
private enum State { private enum State {
UNDEFINED(0), UNDEFINED(0),
@@ -176,6 +183,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final int direction = animator.getTransitionDirection(); final int direction = animator.getTransitionDirection();
final int animationType = animator.getAnimationType(); final int animationType = animator.getAnimationType();
final Rect destinationBounds = animator.getDestinationBounds(); final Rect destinationBounds = animator.getDestinationBounds();
if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
fadeOutAndRemoveOverlay(animator.getContentOverlay(),
animator::clearContentOverlay, true /* withStartDelay*/);
}
if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
&& direction == TRANSITION_DIRECTION_TO_PIP) { && direction == TRANSITION_DIRECTION_TO_PIP) {
// Notify the display to continue the deferred orientation change. // Notify the display to continue the deferred orientation change.
@@ -199,17 +210,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
finishResize(tx, destinationBounds, direction, animationType); finishResize(tx, destinationBounds, direction, animationType);
sendOnPipTransitionFinished(direction); sendOnPipTransitionFinished(direction);
} }
if (direction == TRANSITION_DIRECTION_TO_PIP) {
// TODO (b//169221267): Add jank listener for transactions without buffer updates.
//InteractionJankMonitor.getInstance().end(
// InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
}
} }
@Override @Override
public void onPipAnimationCancel(TaskInfo taskInfo, public void onPipAnimationCancel(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) { PipAnimationController.PipTransitionAnimator animator) {
sendOnPipTransitionCancelled(animator.getTransitionDirection()); final int direction = animator.getTransitionDirection();
if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
fadeOutAndRemoveOverlay(animator.getContentOverlay(),
animator::clearContentOverlay, true /* withStartDelay */);
}
sendOnPipTransitionCancelled(direction);
} }
}; };
@@ -640,7 +651,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// Remove the swipe to home overlay // Remove the swipe to home overlay
if (swipeToHomeOverlay != null) { if (swipeToHomeOverlay != null) {
fadeOutAndRemoveOverlay(swipeToHomeOverlay); fadeOutAndRemoveOverlay(swipeToHomeOverlay,
null /* callback */, false /* withStartDelay */);
} }
}, tx); }, tx);
mInSwipePipToHomeTransition = false; mInSwipePipToHomeTransition = false;
@@ -723,9 +735,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY); mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
} }
final PipAnimationController.PipTransitionAnimator animator = final PipAnimationController.PipTransitionAnimator<?> animator =
mPipAnimationController.getCurrentAnimator(); mPipAnimationController.getCurrentAnimator();
if (animator != null) { if (animator != null) {
if (animator.getContentOverlay() != null) {
removeContentOverlay(animator.getContentOverlay(), animator::clearContentOverlay);
}
animator.removeAllUpdateListeners(); animator.removeAllUpdateListeners();
animator.removeAllListeners(); animator.removeAllListeners();
animator.cancel(); animator.cancel();
@@ -1196,7 +1211,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
snapshotDest); snapshotDest);
// Start animation to fade out the snapshot. // Start animation to fade out the snapshot.
fadeOutAndRemoveOverlay(snapshotSurface); fadeOutAndRemoveOverlay(snapshotSurface,
null /* callback */, false /* withStartDelay */);
}); });
} else { } else {
applyFinishBoundsResize(wct, direction); applyFinishBoundsResize(wct, direction);
@@ -1287,15 +1303,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
animator.setTransitionDirection(direction) animator.setTransitionDirection(direction)
.setPipAnimationCallback(mPipAnimationCallback) .setPipAnimationCallback(mPipAnimationCallback)
.setPipTransactionHandler(mPipTransactionHandler) .setPipTransactionHandler(mPipTransactionHandler)
.setDuration(durationMs) .setDuration(durationMs);
.start(); if (isInPipDirection(direction)) {
if (rotationDelta != Surface.ROTATION_0 && direction == TRANSITION_DIRECTION_TO_PIP) { // Similar to auto-enter-pip transition, we use content overlay when there is no
// source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) animator.setUseContentOverlay(mContext);
// The destination bounds are used for the end rect of animation and the final bounds // The destination bounds are used for the end rect of animation and the final bounds
// after animation finishes. So after the animation is started, the destination bounds // after animation finishes. So after the animation is started, the destination bounds
// can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
// without affecting the animation. // without affecting the animation.
animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); if (rotationDelta != Surface.ROTATION_0) {
animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
}
} }
animator.start();
return animator; return animator;
} }
@@ -1308,6 +1329,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
// Transform the destination bounds to current display coordinates. // Transform the destination bounds to current display coordinates.
rotateBounds(outDestinationBounds, displayBounds, mNextRotation, mCurrentRotation); rotateBounds(outDestinationBounds, displayBounds, mNextRotation, mCurrentRotation);
// When entering PiP (from button navigation mode), adjust the source rect hint by
// display cutout if applicable.
if (sourceHintRect != null && mTaskInfo.displayCutoutInsets != null) {
if (rotationDelta == Surface.ROTATION_270) {
sourceHintRect.offset(mTaskInfo.displayCutoutInsets.left,
mTaskInfo.displayCutoutInsets.top);
}
}
} else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) { } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
final Rect rotatedDestinationBounds = new Rect(outDestinationBounds); final Rect rotatedDestinationBounds = new Rect(outDestinationBounds);
rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(), rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(),
@@ -1346,7 +1375,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
/** /**
* Fades out and removes an overlay surface. * Fades out and removes an overlay surface.
*/ */
private void fadeOutAndRemoveOverlay(SurfaceControl surface) { private void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
boolean withStartDelay) {
if (surface == null) { if (surface == null) {
return; return;
} }
@@ -1363,15 +1393,21 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
animator.addListener(new AnimatorListenerAdapter() { animator.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
final SurfaceControl.Transaction tx = removeContentOverlay(surface, callback);
mSurfaceControlTransactionFactory.getTransaction();
tx.remove(surface);
tx.apply();
} }
}); });
animator.setStartDelay(withStartDelay ? CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
animator.start(); animator.start();
} }
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
tx.remove(surface);
tx.apply();
if (callback != null) callback.run();
}
/** /**
* Dumps internal states. * Dumps internal states.
*/ */

View File

@@ -1884,7 +1884,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
forAllWindows(w -> { forAllWindows(w -> {
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly); w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
}, true /* traverseTopToBottom */); }, true /* traverseTopToBottom */);
mPinnedTaskController.startSeamlessRotationIfNeeded(transaction); mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
} }
mWmService.mDisplayManagerInternal.performTraversal(transaction); mWmService.mDisplayManagerInternal.performTraversal(transaction);

View File

@@ -27,13 +27,16 @@ import android.app.RemoteAction;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import android.util.RotationUtils;
import android.util.Slog; import android.util.Slog;
import android.view.IPinnedTaskListener; import android.view.IPinnedTaskListener;
import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.window.PictureInPictureSurfaceTransaction; import android.window.PictureInPictureSurfaceTransaction;
@@ -237,7 +240,8 @@ class PinnedTaskController {
* rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it * rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it
* receives the callback of fixed rotation completion. * receives the callback of fixed rotation completion.
*/ */
void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t) { void startSeamlessRotationIfNeeded(SurfaceControl.Transaction t,
int oldRotation, int newRotation) {
final Rect bounds = mDestRotatedBounds; final Rect bounds = mDestRotatedBounds;
final PictureInPictureSurfaceTransaction pipTx = mPipTransaction; final PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
if (bounds == null && pipTx == null) { if (bounds == null && pipTx == null) {
@@ -280,6 +284,16 @@ class PinnedTaskController {
? params.getSourceRectHint() ? params.getSourceRectHint()
: null; : null;
Slog.i(TAG, "Seamless rotation PiP bounds=" + bounds + " hintRect=" + sourceHintRect); Slog.i(TAG, "Seamless rotation PiP bounds=" + bounds + " hintRect=" + sourceHintRect);
final int rotationDelta = RotationUtils.deltaRotation(oldRotation, newRotation);
// Adjust for display cutout if applicable.
if (sourceHintRect != null && rotationDelta == Surface.ROTATION_270) {
if (pinnedTask.getDisplayCutoutInsets() != null) {
final int rotationBackDelta = RotationUtils.deltaRotation(newRotation, oldRotation);
final Rect displayCutoutInsets = RotationUtils.rotateInsets(
Insets.of(pinnedTask.getDisplayCutoutInsets()), rotationBackDelta).toRect();
sourceHintRect.offset(displayCutoutInsets.left, displayCutoutInsets.top);
}
}
final Rect contentBounds = sourceHintRect != null && areaBounds.contains(sourceHintRect) final Rect contentBounds = sourceHintRect != null && areaBounds.contains(sourceHintRect)
? sourceHintRect : areaBounds; ? sourceHintRect : areaBounds;
final int w = contentBounds.width(); final int w = contentBounds.width();

View File

@@ -4105,7 +4105,7 @@ class Task extends WindowContainer<WindowContainer> {
info.positionInParent = getRelativePosition(); info.positionInParent = getRelativePosition();
info.pictureInPictureParams = getPictureInPictureParams(top); info.pictureInPictureParams = getPictureInPictureParams(top);
info.displayCutoutInsets = getDisplayCutoutInsets(top); info.displayCutoutInsets = top != null ? top.getDisplayCutoutInsets() : null;
info.topActivityInfo = mReuseActivitiesReport.top != null info.topActivityInfo = mReuseActivitiesReport.top != null
? mReuseActivitiesReport.top.info ? mReuseActivitiesReport.top.info
: null; : null;
@@ -4140,16 +4140,15 @@ class Task extends WindowContainer<WindowContainer> {
? null : new PictureInPictureParams(topVisibleActivity.pictureInPictureArgs); ? null : new PictureInPictureParams(topVisibleActivity.pictureInPictureArgs);
} }
private Rect getDisplayCutoutInsets(Task top) { Rect getDisplayCutoutInsets() {
if (top == null || top.mDisplayContent == null if (mDisplayContent == null || getDisplayInfo().displayCutout == null) return null;
|| top.getDisplayInfo().displayCutout == null) return null; final WindowState w = getTopVisibleAppMainWindow();
final WindowState w = top.getTopVisibleAppMainWindow();
final int displayCutoutMode = w == null final int displayCutoutMode = w == null
? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
: w.getAttrs().layoutInDisplayCutoutMode; : w.getAttrs().layoutInDisplayCutoutMode;
return (displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS return (displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|| displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) || displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)
? null : top.getDisplayInfo().displayCutout.getSafeInsets(); ? null : getDisplayInfo().displayCutout.getSafeInsets();
} }
/** /**