diff options
| author | 2023-11-01 10:18:53 +0100 | |
|---|---|---|
| committer | 2023-11-10 10:14:08 +0100 | |
| commit | 1c22d5e1c6d05cfec7d89c9d99e1c8ba76168dad (patch) | |
| tree | fcea8b3f671dd2419acc66e9149dedd297e80ffb | |
| parent | b7c89a1b33a3f7ce118ec70a355920342dcce936 (diff) | |
Change TV PiP enter/exit transitions
TV PiP enters/exits with an alpha animation with slight zoom.
Bug: 228071323
Test: manual
Change-Id: I9de1e2d229d3e0a2793c5900b47394e875414d98
13 files changed, 978 insertions, 74 deletions
diff --git a/libs/WindowManager/Shell/res/values/config_tv.xml b/libs/WindowManager/Shell/res/values/config_tv.xml new file mode 100644 index 000000000000..3da5539c9ae6 --- /dev/null +++ b/libs/WindowManager/Shell/res/values/config_tv.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <integer name="config_tvPipEnterFadeOutDuration">500</integer> + <integer name="config_tvPipEnterFadeInDuration">1500</integer> + <integer name="config_tvPipExitFadeOutDuration">500</integer> + <integer name="config_tvPipExitFadeInDuration">500</integer> +</resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index d520ff791e07..8b6c7b663f82 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -258,7 +258,7 @@ public class PipBoundsState { ActivityTaskManager.getService().onPictureInPictureStateChanged( new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */) ); - } catch (RemoteException e) { + } catch (RemoteException | IllegalStateException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Unable to set alert PiP state change.", TAG); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java index a9675f976fa9..1947097c2f15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java @@ -20,6 +20,8 @@ import android.content.Context; import android.os.Handler; import android.os.SystemClock; +import androidx.annotation.NonNull; + import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; @@ -41,7 +43,6 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; import com.android.wm.shell.pip.tv.TvPipBoundsController; @@ -78,11 +79,12 @@ public abstract class TvPipModule { PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, TvPipMenuController tvPipMenuController, PipMediaController pipMediaController, - PipTransitionController pipTransitionController, + TvPipTransition tvPipTransition, TvPipNotificationController tvPipNotificationController, TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, @@ -99,9 +101,10 @@ public abstract class TvPipModule { pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipBoundsController, + pipTransitionState, pipAppOpsListener, pipTaskOrganizer, - pipTransitionController, + tvPipTransition, tvPipMenuController, pipMediaController, tvPipNotificationController, @@ -151,25 +154,23 @@ public abstract class TvPipModule { return new LegacySizeSpecSource(context, pipDisplayLayoutState); } - // Handler needed for loadDrawableAsync() in PipControlsViewController @WMSingleton @Provides - static PipTransitionController provideTvPipTransition( + static TvPipTransition provideTvPipTransition( Context context, - ShellInit shellInit, - ShellTaskOrganizer shellTaskOrganizer, - Transitions transitions, + @NonNull ShellInit shellInit, + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull Transitions transitions, TvPipBoundsState tvPipBoundsState, - PipDisplayLayoutState pipDisplayLayoutState, - PipTransitionState pipTransitionState, - TvPipMenuController pipMenuController, + TvPipMenuController tvPipMenuController, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipTransitionState pipTransitionState, PipAnimationController pipAnimationController, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper) { + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + PipDisplayLayoutState pipDisplayLayoutState) { return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions, - tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController, - tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - Optional.empty()); + tvPipBoundsState, tvPipMenuController, tvPipBoundsAlgorithm, pipTransitionState, + pipAnimationController, pipSurfaceTransactionHelper, pipDisplayLayoutState); } @WMSingleton @@ -207,7 +208,7 @@ public abstract class TvPipModule { PipTransitionState pipTransitionState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, PipAnimationController pipAnimationController, - PipTransitionController pipTransitionController, + TvPipTransition tvPipTransition, PipParamsChangedForwarder pipParamsChangedForwarder, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreenController> splitScreenControllerOptional, @@ -217,7 +218,7 @@ public abstract class TvPipModule { return new TvPipTaskOrganizer(context, syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController, - pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, + pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder, splitScreenControllerOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index cbed4b5a501f..a58d94ecd19b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -81,15 +81,35 @@ public class PipSurfaceTransactionHelper { */ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds) { + mTmpDestinationRectF.set(destinationBounds); + return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */); + } + + /** + * Operates the scale (setMatrix) on a given transaction and leash + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, RectF destinationBounds) { return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */); } /** - * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. + * Operates the scale (setMatrix) on a given transaction and leash * @return same {@link PipSurfaceTransactionHelper} instance for method chaining */ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees) { + mTmpDestinationRectF.set(destinationBounds); + return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees); + } + + /** + * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation. + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, RectF destinationBounds, float degrees) { mTmpSourceRectF.set(sourceBounds); // We want the matrix to position the surface relative to the screen coordinates so offset // the source to 0,0 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 9e8f9c68d43d..48f3c742ccb0 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 @@ -292,9 +292,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // changed RunningTaskInfo when it finishes. private ActivityManager.RunningTaskInfo mDeferredTaskInfo; private WindowContainerToken mToken; - private SurfaceControl mLeash; + protected SurfaceControl mLeash; protected PipTransitionState mPipTransitionState; - private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + protected PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; protected PictureInPictureParams mPictureInPictureParams; private IntConsumer mOnDisplayIdChangeCallback; @@ -968,7 +968,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - cancelCurrentAnimator(); + cancelAnimationOnTaskVanished(); onExitPipFinished(info); if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -976,6 +976,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } } + protected void cancelAnimationOnTaskVanished() { + cancelCurrentAnimator(); + } + @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken"); @@ -1095,7 +1099,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** Called when exiting PIP transition is finished to do the state cleanup. */ - void onExitPipFinished(TaskInfo info) { + public void onExitPipFinished(TaskInfo info) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "onExitPipFinished: %s, state=%s leash=%s", info.topActivity, mPipTransitionState, mLeash); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index a48e969fde35..72c0cd71f198 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -44,6 +44,7 @@ import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import java.util.Collections; import java.util.Set; /** @@ -101,12 +102,29 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0 && !mTvPipBoundsState.isTvPipManuallyCollapsed(); if (isPipExpanded) { - updateGravityOnExpansionToggled(/* expanding= */ true); + updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded); } mTvPipBoundsState.setTvPipExpanded(isPipExpanded); return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds()); } + @Override + public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: getEntryDestinationBoundsIgnoringKeepClearAreas()", TAG); + + updateExpandedPipSize(); + final boolean isPipExpanded = mTvPipBoundsState.isTvExpandedPipSupported() + && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0 + && !mTvPipBoundsState.isTvPipManuallyCollapsed(); + if (isPipExpanded) { + updateGravityOnExpansionToggled(/* expanding= */ isPipExpanded); + } + mTvPipBoundsState.setTvPipExpanded(isPipExpanded); + return adjustBoundsForTemporaryDecor(getTvPipPlacement(Collections.emptySet(), + Collections.emptySet()).getUnstashedBounds()); + } + /** Returns the current bounds adjusted to the new aspect ratio, if valid. */ @Override public Rect getAdjustedDestinationBounds(Rect currentBounds, float newAspectRatio) { @@ -133,16 +151,25 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { */ @NonNull public Placement getTvPipPlacement() { + final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); + final Set<Rect> unrestrictedKeepClearAreas = + mTvPipBoundsState.getUnrestrictedKeepClearAreas(); + + return getTvPipPlacement(restrictedKeepClearAreas, unrestrictedKeepClearAreas); + } + + /** + * Calculates the PiP bounds. + */ + @NonNull + private Placement getTvPipPlacement(Set<Rect> restrictedKeepClearAreas, + Set<Rect> unrestrictedKeepClearAreas) { final Size pipSize = getPipSize(); final Rect displayBounds = mTvPipBoundsState.getDisplayBounds(); final Size screenSize = new Size(displayBounds.width(), displayBounds.height()); final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); - final Set<Rect> restrictedKeepClearAreas = mTvPipBoundsState.getRestrictedKeepClearAreas(); - final Set<Rect> unrestrictedKeepClearAreas = - mTvPipBoundsState.getUnrestrictedKeepClearAreas(); - mKeepClearAlgorithm.setGravity(mTvPipBoundsState.getTvPipGravity()); mKeepClearAlgorithm.setScreenSize(screenSize); mKeepClearAlgorithm.setMovementBounds(insetBounds); @@ -189,8 +216,11 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { int updatedGravity; if (expanding) { - // Save collapsed gravity. - mTvPipBoundsState.setTvPipPreviousCollapsedGravity(mTvPipBoundsState.getTvPipGravity()); + if (!mTvPipBoundsState.isTvPipExpanded()) { + // Save collapsed gravity. + mTvPipBoundsState.setTvPipPreviousCollapsedGravity( + mTvPipBoundsState.getTvPipGravity()); + } if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) { updatedGravity = Gravity.CENTER_HORIZONTAL | currentY; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java index 2b3a93e3c3e8..5ee3734e371d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java @@ -131,6 +131,7 @@ public class TvPipBoundsState extends PipBoundsState { mTvFixedPipOrientation = ORIENTATION_UNDETERMINED; mTvPipGravity = mDefaultGravity; mPreviousCollapsedGravity = mDefaultGravity; + mIsTvPipExpanded = false; mTvPipManuallyCollapsed = false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 72115fdefa05..cd3d38b6500c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -56,6 +56,7 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellController; @@ -122,6 +123,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final PipDisplayLayoutState mPipDisplayLayoutState; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; private final TvPipBoundsController mTvPipBoundsController; + private final PipTransitionState mPipTransitionState; private final PipAppOpsListener mAppOpsListener; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; @@ -157,6 +159,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -177,6 +180,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal pipDisplayLayoutState, tvPipBoundsAlgorithm, tvPipBoundsController, + pipTransitionState, pipAppOpsListener, pipTaskOrganizer, pipTransitionController, @@ -199,6 +203,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipDisplayLayoutState pipDisplayLayoutState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, TvPipBoundsController tvPipBoundsController, + PipTransitionState pipTransitionState, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -212,6 +217,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal Handler mainHandler, ShellExecutor mainExecutor) { mContext = context; + mPipTransitionState = pipTransitionState; mMainHandler = mainHandler; mMainExecutor = mainExecutor; mShellController = shellController; @@ -365,7 +371,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState)); mPipTaskOrganizer.exitPip(mResizeAnimationDuration, false /* requestEnterSplit */); - onPipDisappeared(); } private void togglePipExpansion() { @@ -420,6 +425,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) { + if (!mPipTransitionState.hasEnteredPip()) { + // Do not schedule a move animation while we're still transitioning into/out of PiP + return; + } + mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds, animationDuration, null); mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds); @@ -447,7 +457,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal return; } mPipTaskOrganizer.removePip(); - onPipDisappeared(); + mTvPipMenuController.closeMenu(); } @Override @@ -477,7 +487,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mPipNotificationController.dismiss(); mActionBroadcastReceiver.unregister(); - mTvPipMenuController.closeMenu(); + mTvPipMenuController.detach(); mTvPipActionsProvider.reset(); mTvPipBoundsState.resetTvPipState(); mTvPipBoundsController.reset(); @@ -501,8 +511,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void onPipTransitionCanceled(int direction) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState)); - mTvPipMenuController.onPipTransitionFinished( - PipAnimationController.isInPipDirection(direction)); mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index ee55211a73a9..c6803f7beebd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -262,8 +262,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void detach() { - closeMenu(); detachPipMenu(); + switchToMenuMode(MODE_NO_MENU); mLeash = null; } @@ -320,10 +320,21 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx, Rect pipBounds, float alpha) { + movePipMenu(pipTx, pipBounds, alpha); + } + + /** + * Move the PiP menu with the given bounds and update its opacity. + * The PiP SurfaceControl is given if there is a need to synchronize the movements + * on the same frame as PiP. + */ + public void movePipMenu(@Nullable SurfaceControl.Transaction pipTx, @Nullable Rect pipBounds, + float alpha) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha); + "%s: movePipMenu: %s, alpha %s", TAG, + pipBounds != null ? pipBounds.toShortString() : null, alpha); - if (pipBounds.isEmpty()) { + if ((pipBounds == null || pipBounds.isEmpty()) && alpha == ALPHA_NO_CHANGE) { if (pipTx == null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: no transaction given", TAG); @@ -334,28 +345,36 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis return; } - final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView); - final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView); - final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds); if (pipTx == null) { pipTx = new SurfaceControl.Transaction(); } - pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top); - pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top); + + final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView); + final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView); + + if (pipBounds != null) { + final Rect menuDestBounds = calculateMenuSurfaceBounds(pipBounds); + pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top); + pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top); + updateMenuBounds(pipBounds); + } if (alpha != ALPHA_NO_CHANGE) { pipTx.setAlpha(frontSurface, alpha); pipTx.setAlpha(backSurface, alpha); } - // Synchronize drawing the content in the front and back surfaces together with the pip - // transaction and the position change for the front and back surfaces - final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip"); - syncGroup.add(mPipMenuView.getRootSurfaceControl(), null); - syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null); - updateMenuBounds(pipBounds); - syncGroup.addTransaction(pipTx); - syncGroup.markSyncReady(); + if (pipBounds != null) { + // Synchronize drawing the content in the front and back surfaces together with the pip + // transaction and the position change for the front and back surfaces + final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip"); + syncGroup.add(mPipMenuView.getRootSurfaceControl(), null); + syncGroup.add(mPipBackgroundView.getRootSurfaceControl(), null); + syncGroup.addTransaction(pipTx); + syncGroup.markSyncReady(); + } else { + pipTx.apply(); + } } private boolean isMenuAttached() { @@ -388,14 +407,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis final Rect menuBounds = calculateMenuSurfaceBounds(pipBounds); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString()); - mSystemWindows.updateViewLayout(mPipBackgroundView, - getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(), - menuBounds.height())); - mSystemWindows.updateViewLayout(mPipMenuView, - getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(), - menuBounds.height())); - if (mPipMenuView != null) { - mPipMenuView.setPipBounds(pipBounds); + + boolean needsRelayout = mPipBackgroundView.getLayoutParams().width != menuBounds.width() + || mPipBackgroundView.getLayoutParams().height != menuBounds.height(); + if (needsRelayout) { + mSystemWindows.updateViewLayout(mPipBackgroundView, + getPipMenuLayoutParams(mContext, BACKGROUND_WINDOW_TITLE, menuBounds.width(), + menuBounds.height())); + mSystemWindows.updateViewLayout(mPipMenuView, + getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(), + menuBounds.height())); + if (mPipMenuView != null) { + mPipMenuView.setPipBounds(pipBounds); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java index f86f987039ba..202d36f0dfbd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java @@ -168,6 +168,9 @@ class TvPipMenuEduTextDrawer extends FrameLayout { * that the edu text will be marqueed */ private boolean isEduTextMarqueed() { + if (mEduTextView.getLayout() == null) { + return false; + } final int availableWidth = (int) mEduTextView.getWidth() - mEduTextView.getCompoundPaddingLeft() - mEduTextView.getCompoundPaddingRight(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index f315afba9a03..21223c9ac362 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -35,7 +35,6 @@ import com.android.wm.shell.pip.PipMenuController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; -import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -46,6 +45,7 @@ import java.util.Optional; * TV specific changes to the PipTaskOrganizer. */ public class TvPipTaskOrganizer extends PipTaskOrganizer { + private final TvPipTransition mTvPipTransition; public TvPipTaskOrganizer(Context context, @NonNull SyncTransactionQueue syncTransactionQueue, @@ -56,7 +56,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { @NonNull PipMenuController pipMenuController, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, - @NonNull PipTransitionController pipTransitionController, + @NonNull TvPipTransition tvPipTransition, @NonNull PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @@ -65,9 +65,10 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { ShellExecutor mainExecutor) { super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController, - surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, + surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder, splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); + mTvPipTransition = tvPipTransition; } @Override @@ -105,4 +106,14 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { // when the menu alpha is 0 (e.g. when a fade-in animation starts). return true; } + + @Override + protected void cancelAnimationOnTaskVanished() { + mTvPipTransition.cancelAnimations(); + if (mLeash != null) { + mSurfaceControlTransactionFactory.getTransaction() + .setAlpha(mLeash, 0f) + .apply(); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index f24b2b385cad..571c839adf11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -16,43 +16,822 @@ package com.android.wm.shell.pip.tv; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_PIP; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.transitTypeToString; + +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; +import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; +import static com.android.wm.shell.pip.PipMenuController.ALPHA_NO_CHANGE; +import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP; +import static com.android.wm.shell.pip.PipTransitionState.ENTERING_PIP; +import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP; +import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED; +import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; + +import android.animation.AnimationHandler; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.IBinder; +import android.os.Trace; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; +import androidx.annotation.FloatRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; -import com.android.wm.shell.pip.PipTransition; +import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; -import java.util.Optional; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; /** * PiP Transition for TV. */ -public class TvPipTransition extends PipTransition { +public class TvPipTransition extends PipTransitionController { + private static final String TAG = "TvPipTransition"; + private static final float ZOOM_ANIMATION_SCALE_FACTOR = 0.97f; + + private final PipTransitionState mPipTransitionState; + private final PipAnimationController mPipAnimationController; + private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; + private final TvPipMenuController mTvPipMenuController; + private final PipDisplayLayoutState mPipDisplayLayoutState; + private final PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory + mTransactionFactory; + + private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = + ThreadLocal.withInitial(() -> { + AnimationHandler handler = new AnimationHandler(); + handler.setProvider(new SfVsyncFrameCallbackProvider()); + return handler; + }); + + private final long mEnterFadeOutDuration; + private final long mEnterFadeInDuration; + private final long mExitFadeOutDuration; + private final long mExitFadeInDuration; + + @Nullable + private Animator mCurrentAnimator; + + /** + * The Task window that is currently in PIP windowing mode. + */ + @Nullable + private WindowContainerToken mCurrentPipTaskToken; + + @Nullable + private IBinder mPendingExitTransition; public TvPipTransition(Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, TvPipBoundsState tvPipBoundsState, - PipDisplayLayoutState pipDisplayLayoutState, - PipTransitionState pipTransitionState, TvPipMenuController tvPipMenuController, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipTransitionState pipTransitionState, PipAnimationController pipAnimationController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, - Optional<SplitScreenController> splitScreenOptional) { - super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, - pipDisplayLayoutState, pipTransitionState, tvPipMenuController, - tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - splitScreenOptional); + PipDisplayLayoutState pipDisplayLayoutState) { + super(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, tvPipMenuController, + tvPipBoundsAlgorithm); + mPipTransitionState = pipTransitionState; + mPipAnimationController = pipAnimationController; + mSurfaceTransactionHelper = pipSurfaceTransactionHelper; + mTvPipMenuController = tvPipMenuController; + mPipDisplayLayoutState = pipDisplayLayoutState; + mTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); + + mEnterFadeOutDuration = context.getResources().getInteger( + R.integer.config_tvPipEnterFadeOutDuration); + mEnterFadeInDuration = context.getResources().getInteger( + R.integer.config_tvPipEnterFadeInDuration); + mExitFadeOutDuration = context.getResources().getInteger( + R.integer.config_tvPipExitFadeOutDuration); + mExitFadeInDuration = context.getResources().getInteger( + R.integer.config_tvPipExitFadeInDuration); + } + + @Override + public void startExitTransition(int type, WindowContainerTransaction out, + @Nullable Rect destinationBounds) { + cancelAnimations(); + mPendingExitTransition = mTransitions.startTransition(type, out, this); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + + if (isCloseTransition(info)) { + // PiP is closing (without reentering fullscreen activity) + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting close animation", TAG); + cancelAnimations(); + startCloseAnimation(info, startTransaction, finishTransaction, finishCallback); + mCurrentPipTaskToken = null; + return true; + + } else if (transition.equals(mPendingExitTransition)) { + // PiP is exiting (reentering fullscreen activity) + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting exit animation", TAG); + + final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); + mPendingExitTransition = null; + // PipTaskChange can be null if the PIP task has been detached, for example, when the + // task contains multiple activities, the PIP will be moved to a new PIP task when + // entering, and be moved back when exiting. In that case, the PIP task will be removed + // immediately. + final TaskInfo pipTaskInfo = currentPipTaskChange != null + ? currentPipTaskChange.getTaskInfo() + : mPipOrganizer.getTaskInfo(); + if (pipTaskInfo == null) { + throw new RuntimeException("Cannot find the pip task for exit-pip transition."); + } + + final int type = info.getType(); + switch (type) { + case TRANSIT_EXIT_PIP -> { + TransitionInfo.Change pipChange = currentPipTaskChange; + SurfaceControl activitySc = null; + if (mCurrentPipTaskToken == null) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG); + } else if (pipChange == null) { + // The pipTaskChange is null, this can happen if we are reparenting the + // PIP activity back to its original Task. In that case, we should animate + // the activity leash instead, which should be the change whose last parent + // is the recorded PiP Task. + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (mCurrentPipTaskToken.equals(change.getLastParent())) { + // Find the activity that is exiting PiP. + pipChange = change; + activitySc = change.getLeash(); + break; + } + } + } + if (pipChange == null) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: No window of exiting PIP is found. Can't play expand " + + "animation", + TAG); + removePipImmediately(info, pipTaskInfo, startTransaction, finishTransaction, + finishCallback); + return true; + } + final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info); + final SurfaceControl pipLeash; + if (activitySc != null) { + // Use a local leash to animate activity in case the activity has + // letterbox which may be broken by PiP animation, e.g. always end at 0,0 + // in parent and unable to include letterbox area in crop bounds. + final SurfaceControl activitySurface = pipChange.getLeash(); + pipLeash = new SurfaceControl.Builder() + .setName(activitySc + "_pip-leash") + .setContainerLayer() + .setHidden(false) + .setParent(root.getLeash()) + .build(); + startTransaction.reparent(activitySurface, pipLeash); + // Put the activity at local position with offset in case it is letterboxed. + final Point activityOffset = pipChange.getEndRelOffset(); + startTransaction.setPosition(activitySc, activityOffset.x, + activityOffset.y); + } else { + pipLeash = pipChange.getLeash(); + startTransaction.reparent(pipLeash, root.getLeash()); + } + startTransaction.setLayer(pipLeash, Integer.MAX_VALUE); + final Rect currentBounds = mPipBoundsState.getBounds(); + final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds()); + cancelAnimations(); + startExitAnimation(pipTaskInfo, pipLeash, currentBounds, destinationBounds, + startTransaction, + finishTransaction, finishCallback); + } + // pass through here is intended + case TRANSIT_TO_BACK, TRANSIT_REMOVE_PIP -> removePipImmediately(info, pipTaskInfo, + startTransaction, finishTransaction, + finishCallback + ); + default -> { + return false; + } + } + mCurrentPipTaskToken = null; + return true; + + } else if (isEnteringPip(info)) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Starting enter animation", TAG); + + // Search for an Enter PiP transition + TransitionInfo.Change enterPip = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { + enterPip = change; + } + } + if (enterPip == null) { + throw new IllegalStateException("Trying to start PiP animation without a pip" + + "participant"); + } + + // Make sure other open changes are visible as entering PIP. Some may be hidden in + // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change == enterPip) continue; + if (TransitionUtil.isOpeningType(change.getMode())) { + final SurfaceControl leash = change.getLeash(); + startTransaction.show(leash).setAlpha(leash, 1.f); + } + } + + cancelAnimations(); + startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback); + return true; + } + + return false; + } + + /** + * For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task. + */ + private void removePipImmediately(@NonNull TransitionInfo info, + @NonNull TaskInfo taskInfo, @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: removePipImmediately", TAG); + cancelAnimations(); + startTransaction.apply(); + finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), + mPipDisplayLayoutState.getDisplayBounds()); + mTvPipMenuController.detach(); + mPipOrganizer.onExitPipFinished(taskInfo); + finishCallback.onTransitionFinished(/* wct= */ null); + + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK); + } + + private void startCloseAnimation(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final TransitionInfo.Change pipTaskChange = findCurrentPipTaskChange(info); + final SurfaceControl pipLeash = pipTaskChange.getLeash(); + + final List<SurfaceControl> closeLeashes = new ArrayList<>(); + for (TransitionInfo.Change change : info.getChanges()) { + if (TransitionUtil.isClosingType(change.getMode()) && change != pipTaskChange) { + closeLeashes.add(change.getLeash()); + } + } + + final Rect pipBounds = mPipBoundsState.getBounds(); + mSurfaceTransactionHelper + .resetScale(startTransaction, pipLeash, pipBounds) + .crop(startTransaction, pipLeash, pipBounds) + .shadow(startTransaction, pipLeash, false); + + final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction(); + for (SurfaceControl leash : closeLeashes) { + startTransaction.setShadowRadius(leash, 0f); + } + + ValueAnimator closeFadeOutAnimator = createAnimator(); + closeFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + closeFadeOutAnimator.setDuration(mExitFadeOutDuration); + closeFadeOutAnimator.addUpdateListener( + animationUpdateListener(pipLeash).fadingOut().withMenu()); + for (SurfaceControl leash : closeLeashes) { + closeFadeOutAnimator.addUpdateListener(animationUpdateListener(leash).fadingOut()); + } + + closeFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: start", TAG); + for (SurfaceControl leash : closeLeashes) { + startTransaction.setShadowRadius(leash, 0f); + } + startTransaction.apply(); + + mPipTransitionState.setTransitionState(EXITING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_REMOVE_STACK); + } + + @Override + public void onAnimationCancel(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: cancel", TAG); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_REMOVE_STACK); + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: close animation: end", TAG); + mTvPipMenuController.detach(); + finishCallback.onTransitionFinished(null /* wct */); + transaction.close(); + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_REMOVE_STACK); + + mCurrentAnimator = null; + } + }); + + closeFadeOutAnimator.start(); + mCurrentAnimator = closeFadeOutAnimator; + } + + @Override + public void startEnterAnimation(@NonNull TransitionInfo.Change pipChange, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Keep track of the PIP task + mCurrentPipTaskToken = pipChange.getContainer(); + final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo(); + final SurfaceControl leash = pipChange.getLeash(); + + mTvPipMenuController.attach(leash); + setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, + taskInfo.topActivityInfo); + + final Rect pipBounds = + mPipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas(); + mPipBoundsState.setBounds(pipBounds); + mTvPipMenuController.movePipMenu(null, pipBounds, 0f); + + final WindowContainerTransaction resizePipWct = new WindowContainerTransaction(); + resizePipWct.setWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED); + resizePipWct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_PINNED); + resizePipWct.setBounds(taskInfo.token, pipBounds); + + mSurfaceTransactionHelper + .resetScale(finishTransaction, leash, pipBounds) + .crop(finishTransaction, leash, pipBounds) + .shadow(finishTransaction, leash, false); + + final Rect currentBounds = pipChange.getStartAbsBounds(); + final Rect fadeOutCurrentBounds = scaledRect(currentBounds, ZOOM_ANIMATION_SCALE_FACTOR); + + final ValueAnimator enterFadeOutAnimator = createAnimator(); + enterFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + enterFadeOutAnimator.setDuration(mEnterFadeOutDuration); + enterFadeOutAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingOut() + .animateBounds(currentBounds, fadeOutCurrentBounds, currentBounds)); + + enterFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @SuppressLint("MissingPermission") + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter fade out animation: end", TAG); + SurfaceControl.Transaction tx = mTransactionFactory.getTransaction(); + mSurfaceTransactionHelper + .resetScale(tx, leash, pipBounds) + .crop(tx, leash, pipBounds) + .shadow(tx, leash, false); + mShellTaskOrganizer.applyTransaction(resizePipWct); + tx.apply(); + } + }); + + final ValueAnimator enterFadeInAnimator = createAnimator(); + enterFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER); + enterFadeInAnimator.setDuration(mEnterFadeInDuration); + enterFadeInAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingIn() + .withMenu() + .atBounds(pipBounds)); + + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet + .play(enterFadeInAnimator) + .after(500) + .after(enterFadeOutAnimator); + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: start", TAG); + startTransaction.apply(); + mPipTransitionState.setTransitionState(ENTERING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); + } + + @Override + public void onAnimationCancel(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: cancel", TAG); + enterFadeInAnimator.setCurrentFraction(1f); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_TO_PIP); + } + + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: enter animation: end", TAG); + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setBounds(taskInfo.token, pipBounds); + finishCallback.onTransitionFinished(wct); + + mPipTransitionState.setTransitionState(ENTERED_PIP); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); + mCurrentAnimator = null; + } + }); + + animatorSet.start(); + mCurrentAnimator = animatorSet; } + private void startExitAnimation(@NonNull TaskInfo taskInfo, SurfaceControl leash, + Rect currentBounds, Rect destinationBounds, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final Rect fadeInStartBounds = scaledRect(destinationBounds, ZOOM_ANIMATION_SCALE_FACTOR); + + final ValueAnimator exitFadeOutAnimator = createAnimator(); + exitFadeOutAnimator.setInterpolator(TvPipInterpolators.EXIT); + exitFadeOutAnimator.setDuration(mExitFadeOutDuration); + exitFadeOutAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingOut() + .withMenu() + .atBounds(currentBounds)); + exitFadeOutAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit fade out animation: end", TAG); + startTransaction.apply(); + mPipMenuController.detach(); + } + }); + + final ValueAnimator exitFadeInAnimator = createAnimator(); + exitFadeInAnimator.setInterpolator(TvPipInterpolators.ENTER); + exitFadeInAnimator.setDuration(mExitFadeInDuration); + exitFadeInAnimator.addUpdateListener( + animationUpdateListener(leash) + .fadingIn() + .animateBounds(fadeInStartBounds, destinationBounds, destinationBounds)); + + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playSequentially( + exitFadeOutAnimator, + exitFadeInAnimator + ); + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: start", TAG); + mPipTransitionState.setTransitionState(EXITING_PIP); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_LEAVE_PIP); + } + + @Override + public void onAnimationCancel(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: cancel", TAG); + sendOnPipTransitionCancelled(TRANSITION_DIRECTION_LEAVE_PIP); + } + + @Override + public void onAnimationEnd(Animator animation) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: exit animation: end", TAG); + mPipOrganizer.onExitPipFinished(taskInfo); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.setBounds(taskInfo.token, destinationBounds); + finishCallback.onTransitionFinished(wct); + + mPipTransitionState.setTransitionState(UNDEFINED); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); + + mCurrentAnimator = null; + } + }); + + animatorSet.start(); + mCurrentAnimator = animatorSet; + } + + @NonNull + private ValueAnimator createAnimator() { + final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); + return animator; + } + + @NonNull + private TvPipTransitionAnimatorUpdateListener animationUpdateListener( + @NonNull SurfaceControl leash) { + return new TvPipTransitionAnimatorUpdateListener(leash, mTvPipMenuController, + mTransactionFactory.getTransaction(), mSurfaceTransactionHelper); + } + + @NonNull + private static Rect scaledRect(@NonNull Rect rect, float scale) { + final Rect out = new Rect(rect); + out.inset((int) (rect.width() * (1 - scale) / 2), (int) (rect.height() * (1 - scale) / 2)); + return out; + } + + private boolean isCloseTransition(TransitionInfo info) { + final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); + return currentPipTaskChange != null && info.getType() == TRANSIT_CLOSE; + } + + @Nullable + private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) { + if (mCurrentPipTaskToken == null) { + return null; + } + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (mCurrentPipTaskToken.equals(change.getContainer())) { + return change; + } + } + return null; + } + + /** + * Whether we should handle the given {@link TransitionInfo} animation as entering PIP. + */ + private boolean isEnteringPip(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (isEnteringPip(change, info.getType())) return true; + } + return false; + } + + /** + * Whether a particular change is a window that is entering pip. + */ + @Override + public boolean isEnteringPip(@NonNull TransitionInfo.Change change, + @WindowManager.TransitionType int transitType) { + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED + && !Objects.equals(change.getContainer(), mCurrentPipTaskToken)) { + if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN + || transitType == TRANSIT_CHANGE) { + return true; + } + // Please file a bug to handle the unexpected transition type. + android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type=" + + transitTypeToString(transitType), new Throwable()); + } + return false; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG); + if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { + mCurrentAnimator.end(); + } + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + if (requestHasPipEnter(request)) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: handle PiP enter request", TAG); + WindowContainerTransaction wct = new WindowContainerTransaction(); + augmentRequest(transition, request, wct); + return wct; + } else if (request.getType() == TRANSIT_TO_BACK && request.getTriggerTask() != null + && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_PINNED) { + // if we receive a TRANSIT_TO_BACK type of request while in PiP + mPendingExitTransition = transition; + + // update the transition state to avoid {@link PipTaskOrganizer#onTaskVanished()} calls + mPipTransitionState.setTransitionState(EXITING_PIP); + + // return an empty WindowContainerTransaction so that we don't check other handlers + return new WindowContainerTransaction(); + } else { + return null; + } + } + + @Override + public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request, + @NonNull WindowContainerTransaction outWCT) { + if (!requestHasPipEnter(request)) { + throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); + } + outWCT.setActivityWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_UNDEFINED); + } + + /** + * Cancel any ongoing PiP transitions/animations. + */ + public void cancelAnimations() { + if (mPipAnimationController.isAnimating()) { + mPipAnimationController.getCurrentAnimator().cancel(); + mPipAnimationController.resetAnimatorState(); + } + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + } + + @Override + public void end() { + if (mCurrentAnimator != null) { + mCurrentAnimator.end(); + } + } + + private static class TvPipTransitionAnimatorUpdateListener implements + ValueAnimator.AnimatorUpdateListener { + private final SurfaceControl mLeash; + private final TvPipMenuController mTvPipMenuController; + private final SurfaceControl.Transaction mTransaction; + private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; + private final RectF mTmpRectF = new RectF(); + private final Rect mTmpRect = new Rect(); + + private float mStartAlpha = ALPHA_NO_CHANGE; + private float mEndAlpha = ALPHA_NO_CHANGE; + + @Nullable + private Rect mStartBounds; + @Nullable + private Rect mEndBounds; + private Rect mWindowContainerBounds; + private boolean mShowMenu; + + TvPipTransitionAnimatorUpdateListener(@NonNull SurfaceControl leash, + @NonNull TvPipMenuController tvPipMenuController, + @NonNull SurfaceControl.Transaction transaction, + @NonNull PipSurfaceTransactionHelper pipSurfaceTransactionHelper) { + mLeash = leash; + mTvPipMenuController = tvPipMenuController; + mTransaction = transaction; + mSurfaceTransactionHelper = pipSurfaceTransactionHelper; + } + + public TvPipTransitionAnimatorUpdateListener animateAlpha( + @FloatRange(from = 0.0, to = 1.0) float startAlpha, + @FloatRange(from = 0.0, to = 1.0) float endAlpha) { + mStartAlpha = startAlpha; + mEndAlpha = endAlpha; + return this; + } + + public TvPipTransitionAnimatorUpdateListener animateBounds(@NonNull Rect startBounds, + @NonNull Rect endBounds, @NonNull Rect windowContainerBounds) { + mStartBounds = startBounds; + mEndBounds = endBounds; + mWindowContainerBounds = windowContainerBounds; + return this; + } + + public TvPipTransitionAnimatorUpdateListener atBounds(@NonNull Rect bounds) { + return animateBounds(bounds, bounds, bounds); + } + + public TvPipTransitionAnimatorUpdateListener fadingOut() { + return animateAlpha(1f, 0f); + } + + public TvPipTransitionAnimatorUpdateListener fadingIn() { + return animateAlpha(0f, 1f); + } + + public TvPipTransitionAnimatorUpdateListener withMenu() { + mShowMenu = true; + return this; + } + + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + final float fraction = animation.getAnimatedFraction(); + final float alpha = lerp(mStartAlpha, mEndAlpha, fraction); + if (mStartBounds != null && mEndBounds != null) { + lerp(mStartBounds, mEndBounds, fraction, mTmpRectF); + applyAnimatedValue(alpha, mTmpRectF); + } else { + applyAnimatedValue(alpha, null); + } + } + + private void applyAnimatedValue(float alpha, @Nullable RectF bounds) { + Trace.beginSection("applyAnimatedValue"); + final SurfaceControl.Transaction tx = mTransaction; + + Trace.beginSection("leash scale and alpha"); + if (alpha != ALPHA_NO_CHANGE) { + mSurfaceTransactionHelper.alpha(tx, mLeash, alpha); + } + if (bounds != null) { + mSurfaceTransactionHelper.scale(tx, mLeash, mWindowContainerBounds, bounds); + } + mSurfaceTransactionHelper.shadow(tx, mLeash, false); + tx.show(mLeash); + Trace.endSection(); + + if (mShowMenu) { + Trace.beginSection("movePipMenu"); + if (bounds != null) { + mTmpRect.set((int) bounds.left, (int) bounds.top, (int) bounds.right, + (int) bounds.bottom); + mTvPipMenuController.movePipMenu(tx, mTmpRect, alpha); + } else { + mTvPipMenuController.movePipMenu(tx, null, alpha); + } + Trace.endSection(); + } else { + mTvPipMenuController.movePipMenu(tx, null, 0f); + } + + tx.apply(); + Trace.endSection(); + } + + private float lerp(float start, float end, float fraction) { + return start * (1 - fraction) + end * fraction; + } + + private void lerp(@NonNull Rect start, @NonNull Rect end, float fraction, + @NonNull RectF out) { + out.set( + start.left * (1 - fraction) + end.left * fraction, + start.top * (1 - fraction) + end.top * fraction, + start.right * (1 - fraction) + end.right * fraction, + start.bottom * (1 - fraction) + end.bottom * fraction); + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 4e2b7f6d16b2..800f9e4e5371 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -66,6 +66,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -283,7 +284,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) .round(any(), any(), anyBoolean()); doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) - .scale(any(), any(), any(), any(), anyFloat()); + .scale(any(), any(), any(), ArgumentMatchers.<Rect>any(), anyFloat()); doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper) .alpha(any(), any(), anyFloat()); doNothing().when(mMockPipSurfaceTransactionHelper).onDensityOrFontScaleChanged(any()); |