Fix entering PiP transition in button navigation mode
Included in this CL - when there is no source rect hint, use a content overlay during the transition - otherwise, take account the display cutout to offset/inset the source rect hint Test with following variants when entering PiP in button nav - from 0 / 90 / 270 rotation - with or without source rect hint - display cutout mode set to default or shortEdge Video: http://recall/-/aaaaaabFQoRHlzixHdtY/cxk4vy8VenQPSv83vHEs22 Bug: 191310680 Test: manual, see the test cases listed and video above Change-Id: Ie54a54de6e55397e25024373ea4e2855fde2d9f7 Merged-In: Ie54a54de6e55397e25024373ea4e2855fde2d9f7
This commit is contained in:
parent
5a59072018
commit
33c3fcc671
libs/WindowManager/Shell/src/com/android/wm/shell/pip
services/core/java/com/android/server/wm
@ -26,10 +26,14 @@ import android.animation.RectEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.IntDef;
|
||||
import android.app.TaskInfo;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceSession;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
|
||||
@ -253,6 +257,7 @@ public class PipAnimationController {
|
||||
mSurfaceControlTransactionFactory;
|
||||
private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
|
||||
private @TransitionDirection int mTransitionDirection;
|
||||
protected SurfaceControl mContentOverlay;
|
||||
|
||||
private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
|
||||
@AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue,
|
||||
@ -331,6 +336,53 @@ public class PipAnimationController {
|
||||
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
|
||||
@TransitionDirection public int getTransitionDirection() {
|
||||
return mTransitionDirection;
|
||||
@ -517,6 +569,9 @@ public class PipAnimationController {
|
||||
final Rect base = getBaseValue();
|
||||
final Rect start = getStartValue();
|
||||
final Rect end = getEndValue();
|
||||
if (mContentOverlay != null) {
|
||||
tx.setAlpha(mContentOverlay, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
|
||||
}
|
||||
if (rotatedEndRect != null) {
|
||||
// Animate the bounds in a different orientation. It only happens when
|
||||
// switching between PiP and fullscreen.
|
||||
|
@ -107,6 +107,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
*/
|
||||
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.
|
||||
private enum State {
|
||||
UNDEFINED(0),
|
||||
@ -176,6 +183,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
final int direction = animator.getTransitionDirection();
|
||||
final int animationType = animator.getAnimationType();
|
||||
final Rect destinationBounds = animator.getDestinationBounds();
|
||||
if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
|
||||
fadeOutAndRemoveOverlay(animator.getContentOverlay(),
|
||||
animator::clearContentOverlay, true /* withStartDelay*/);
|
||||
}
|
||||
if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
|
||||
&& direction == TRANSITION_DIRECTION_TO_PIP) {
|
||||
// Notify the display to continue the deferred orientation change.
|
||||
@ -199,17 +210,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
finishResize(tx, destinationBounds, direction, animationType);
|
||||
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
|
||||
public void onPipAnimationCancel(TaskInfo taskInfo,
|
||||
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
|
||||
if (swipeToHomeOverlay != null) {
|
||||
fadeOutAndRemoveOverlay(swipeToHomeOverlay);
|
||||
fadeOutAndRemoveOverlay(swipeToHomeOverlay,
|
||||
null /* callback */, false /* withStartDelay */);
|
||||
}
|
||||
}, tx);
|
||||
mInSwipePipToHomeTransition = false;
|
||||
@ -723,9 +735,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
|
||||
}
|
||||
|
||||
final PipAnimationController.PipTransitionAnimator animator =
|
||||
final PipAnimationController.PipTransitionAnimator<?> animator =
|
||||
mPipAnimationController.getCurrentAnimator();
|
||||
if (animator != null) {
|
||||
if (animator.getContentOverlay() != null) {
|
||||
removeContentOverlay(animator.getContentOverlay(), animator::clearContentOverlay);
|
||||
}
|
||||
animator.removeAllUpdateListeners();
|
||||
animator.removeAllListeners();
|
||||
animator.cancel();
|
||||
@ -1196,7 +1211,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
snapshotDest);
|
||||
|
||||
// Start animation to fade out the snapshot.
|
||||
fadeOutAndRemoveOverlay(snapshotSurface);
|
||||
fadeOutAndRemoveOverlay(snapshotSurface,
|
||||
null /* callback */, false /* withStartDelay */);
|
||||
});
|
||||
} else {
|
||||
applyFinishBoundsResize(wct, direction);
|
||||
@ -1287,15 +1303,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
animator.setTransitionDirection(direction)
|
||||
.setPipAnimationCallback(mPipAnimationCallback)
|
||||
.setPipTransactionHandler(mPipTransactionHandler)
|
||||
.setDuration(durationMs)
|
||||
.start();
|
||||
if (rotationDelta != Surface.ROTATION_0 && direction == TRANSITION_DIRECTION_TO_PIP) {
|
||||
.setDuration(durationMs);
|
||||
if (isInPipDirection(direction)) {
|
||||
// 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
|
||||
// after animation finishes. So after the animation is started, the destination bounds
|
||||
// can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
|
||||
// without affecting the animation.
|
||||
animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
|
||||
if (rotationDelta != Surface.ROTATION_0) {
|
||||
animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
|
||||
}
|
||||
}
|
||||
animator.start();
|
||||
return animator;
|
||||
}
|
||||
|
||||
@ -1308,6 +1329,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
|
||||
// Transform the destination bounds to current display coordinates.
|
||||
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) {
|
||||
final Rect rotatedDestinationBounds = new Rect(outDestinationBounds);
|
||||
rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(),
|
||||
@ -1346,7 +1375,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
/**
|
||||
* Fades out and removes an overlay surface.
|
||||
*/
|
||||
private void fadeOutAndRemoveOverlay(SurfaceControl surface) {
|
||||
private void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
|
||||
boolean withStartDelay) {
|
||||
if (surface == null) {
|
||||
return;
|
||||
}
|
||||
@ -1363,15 +1393,21 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
final SurfaceControl.Transaction tx =
|
||||
mSurfaceControlTransactionFactory.getTransaction();
|
||||
tx.remove(surface);
|
||||
tx.apply();
|
||||
removeContentOverlay(surface, callback);
|
||||
}
|
||||
});
|
||||
animator.setStartDelay(withStartDelay ? CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
|
||||
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.
|
||||
*/
|
||||
|
@ -1884,7 +1884,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
forAllWindows(w -> {
|
||||
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
|
||||
}, true /* traverseTopToBottom */);
|
||||
mPinnedTaskController.startSeamlessRotationIfNeeded(transaction);
|
||||
mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
|
||||
}
|
||||
|
||||
mWmService.mDisplayManagerInternal.performTraversal(transaction);
|
||||
|
@ -27,13 +27,16 @@ import android.app.RemoteAction;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.RotationUtils;
|
||||
import android.util.Slog;
|
||||
import android.view.IPinnedTaskListener;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.PictureInPictureSurfaceTransaction;
|
||||
|
||||
@ -237,7 +240,8 @@ class PinnedTaskController {
|
||||
* rotation of display. The final surface matrix will be replaced by PiPTaskOrganizer after it
|
||||
* 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 PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
|
||||
if (bounds == null && pipTx == null) {
|
||||
@ -280,6 +284,16 @@ class PinnedTaskController {
|
||||
? params.getSourceRectHint()
|
||||
: null;
|
||||
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)
|
||||
? sourceHintRect : areaBounds;
|
||||
final int w = contentBounds.width();
|
||||
|
@ -4107,7 +4107,7 @@ class Task extends WindowContainer<WindowContainer> {
|
||||
info.positionInParent = getRelativePosition();
|
||||
|
||||
info.pictureInPictureParams = getPictureInPictureParams(top);
|
||||
info.displayCutoutInsets = getDisplayCutoutInsets(top);
|
||||
info.displayCutoutInsets = top != null ? top.getDisplayCutoutInsets() : null;
|
||||
info.topActivityInfo = mReuseActivitiesReport.top != null
|
||||
? mReuseActivitiesReport.top.info
|
||||
: null;
|
||||
@ -4142,16 +4142,15 @@ class Task extends WindowContainer<WindowContainer> {
|
||||
? null : new PictureInPictureParams(topVisibleActivity.pictureInPictureArgs);
|
||||
}
|
||||
|
||||
private Rect getDisplayCutoutInsets(Task top) {
|
||||
if (top == null || top.mDisplayContent == null
|
||||
|| top.getDisplayInfo().displayCutout == null) return null;
|
||||
final WindowState w = top.getTopVisibleAppMainWindow();
|
||||
Rect getDisplayCutoutInsets() {
|
||||
if (mDisplayContent == null || getDisplayInfo().displayCutout == null) return null;
|
||||
final WindowState w = getTopVisibleAppMainWindow();
|
||||
final int displayCutoutMode = w == null
|
||||
? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|
||||
: w.getAttrs().layoutInDisplayCutoutMode;
|
||||
return (displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|
||||
|| displayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)
|
||||
? null : top.getDisplayInfo().displayCutout.getSafeInsets();
|
||||
? null : getDisplayInfo().displayCutout.getSafeInsets();
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user