diff options
5 files changed, 130 insertions, 26 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index c46b5590bab6..200af7415eb1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -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. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index f367cd608f37..5a506193b8a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -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. */ diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a72d9aa9ec6b..3144c87e8314 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -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); diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index 31e2edec2601..7e95e7d2aa8c 100644 --- a/services/core/java/com/android/server/wm/PinnedTaskController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -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(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a11325422e29..382a2b1b5062 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4105,7 +4105,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; @@ -4140,16 +4140,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(); } /** |
