diff options
9 files changed, 586 insertions, 143 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index 145e52729e11..f1e24b426bcb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -18,6 +18,7 @@ package com.android.wm.shell.dagger; import android.content.Context; import android.os.Handler; +import android.os.SystemClock; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -39,6 +40,7 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm; +import com.android.wm.shell.pip.tv.TvPipBoundsController; import com.android.wm.shell.pip.tv.TvPipBoundsState; import com.android.wm.shell.pip.tv.TvPipController; import com.android.wm.shell.pip.tv.TvPipMenuController; @@ -64,6 +66,7 @@ public abstract class TvPipModule { Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, TvPipMenuController tvPipMenuController, @@ -74,13 +77,13 @@ public abstract class TvPipModule { PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper windowManagerShellWrapper, - @ShellMainThread ShellExecutor mainExecutor, - @ShellMainThread Handler mainHandler) { + @ShellMainThread ShellExecutor mainExecutor) { return Optional.of( TvPipController.create( context, tvPipBoundsState, tvPipBoundsAlgorithm, + tvPipBoundsController, pipAppOpsListener, pipTaskOrganizer, pipTransitionController, @@ -91,8 +94,22 @@ public abstract class TvPipModule { pipParamsChangedForwarder, displayController, windowManagerShellWrapper, - mainExecutor, - mainHandler)); + mainExecutor)); + } + + @WMSingleton + @Provides + static TvPipBoundsController provideTvPipBoundsController( + Context context, + @ShellMainThread Handler mainHandler, + TvPipBoundsState tvPipBoundsState, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm) { + return new TvPipBoundsController( + context, + SystemClock::uptimeMillis, + mainHandler, + tvPipBoundsState, + tvPipBoundsAlgorithm); } @WMSingleton 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 21d5d401835d..bacf2311e636 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 @@ -29,7 +29,6 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; -import android.os.SystemClock; import android.util.ArraySet; import android.util.Size; import android.view.Gravity; @@ -66,7 +65,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { @NonNull PipSnapAlgorithm pipSnapAlgorithm) { super(context, tvPipBoundsState, pipSnapAlgorithm); this.mTvPipBoundsState = tvPipBoundsState; - this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(SystemClock::uptimeMillis); + this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(); reloadResources(context); } @@ -80,7 +79,6 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { res.getDimensionPixelSize(R.dimen.pip_keep_clear_area_padding)); mKeepClearAlgorithm.setMaxRestrictedDistanceFraction( res.getFraction(R.fraction.config_pipMaxRestrictedMoveDistance, 1, 1)); - mKeepClearAlgorithm.setStashDuration(res.getInteger(R.integer.config_pipStashDuration)); } @Override @@ -104,7 +102,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true); } mTvPipBoundsState.setTvPipExpanded(isPipExpanded); - return getTvPipBounds().getBounds(); + return getTvPipPlacement().getBounds(); } /** Returns the current bounds adjusted to the new aspect ratio, if valid. */ @@ -114,13 +112,14 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio); } - return getTvPipBounds().getBounds(); + return getTvPipPlacement().getBounds(); } /** * Calculates the PiP bounds. */ - public Placement getTvPipBounds() { + @NonNull + public Placement getTvPipPlacement() { final Size pipSize = getPipSize(); final Rect displayBounds = mTvPipBoundsState.getDisplayBounds(); final Size screenSize = new Size(displayBounds.width(), displayBounds.height()); @@ -407,8 +406,4 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm { TAG, expandedSize.getWidth(), expandedSize.getHeight()); } } - - void keepUnstashedForCurrentKeepClearAreas() { - mKeepClearAlgorithm.keepUnstashedForCurrentKeepClearAreas(); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java new file mode 100644 index 000000000000..bb1cb3586f25 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.wm.shell.pip.tv; + +import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.os.Handler; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.R; +import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Controller managing the PiP's position. + * Manages debouncing of PiP movements and scheduling of unstashing. + */ +public class TvPipBoundsController { + private static final boolean DEBUG = false; + private static final String TAG = "TvPipBoundsController"; + + /** + * Time the calculated PiP position needs to be stable before PiP is moved there, + * to avoid erratic movement. + * Some changes will cause the PiP to be repositioned immediately, such as changes to + * unrestricted keep clear areas. + */ + @VisibleForTesting + static final long POSITION_DEBOUNCE_TIMEOUT_MILLIS = 300L; + + private final Context mContext; + private final Supplier<Long> mClock; + private final Handler mMainHandler; + private final TvPipBoundsState mTvPipBoundsState; + private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; + + @Nullable + private PipBoundsListener mListener; + + private int mResizeAnimationDuration; + private int mStashDurationMs; + private Rect mPipTargetBounds; + + private final Runnable mApplyPendingPlacementRunnable = this::applyPendingPlacement; + private boolean mPendingStash; + private Placement mPendingPlacement; + private int mPendingPlacementAnimationDuration; + private Runnable mUnstashRunnable; + + public TvPipBoundsController( + Context context, + Supplier<Long> clock, + Handler mainHandler, + TvPipBoundsState tvPipBoundsState, + TvPipBoundsAlgorithm tvPipBoundsAlgorithm) { + mContext = context; + mClock = clock; + mMainHandler = mainHandler; + mTvPipBoundsState = tvPipBoundsState; + mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm; + + loadConfigurations(); + } + + private void loadConfigurations() { + final Resources res = mContext.getResources(); + mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration); + mStashDurationMs = res.getInteger(R.integer.config_pipStashDuration); + } + + void setListener(PipBoundsListener listener) { + mListener = listener; + } + + /** + * Update the PiP bounds based on the state of the PiP and keep clear areas. + * Unless {@code immediate} is {@code true}, the PiP does not move immediately to its new + * position, but waits for a new position to stay uncontested for + * {@link #POSITION_DEBOUNCE_TIMEOUT_MILLIS} before moving to it. + * + * @param stayAtAnchorPosition If true, PiP will be placed at the anchor position + * @param disallowStashing If true, PiP will not be placed off-screen in a stashed position + * @param animationDuration Duration of the animation to the new position + * @param immediate If true, PiP will move immediately + */ + @VisibleForTesting + void recalculatePipBounds(boolean stayAtAnchorPosition, boolean disallowStashing, + int animationDuration, boolean immediate) { + final Placement placement = mTvPipBoundsAlgorithm.getTvPipPlacement(); + + final int stashType = disallowStashing ? STASH_TYPE_NONE : placement.getStashType(); + mTvPipBoundsState.setStashed(stashType); + if (stayAtAnchorPosition) { + cancelScheduledPlacement(); + movePipTo(placement.getAnchorBounds(), animationDuration); + } else if (disallowStashing) { + cancelScheduledPlacement(); + movePipTo(placement.getUnstashedBounds(), animationDuration); + } else if (immediate) { + cancelScheduledPlacement(); + movePipTo(placement.getBounds(), animationDuration); + scheduleUnstashIfNeeded(placement); + } else { + schedulePinnedStackPlacement(placement, animationDuration); + } + } + + private void schedulePinnedStackPlacement(@NonNull final Placement placement, + int animationDuration) { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: schedulePinnedStackPlacement() - pip bounds: %s", + TAG, placement.getBounds().toShortString()); + } + + if (mPendingPlacement != null && Objects.equals(mPendingPlacement.getBounds(), + placement.getBounds())) { + mPendingStash = mPendingStash || placement.getTriggerStash(); + return; + } + + mPendingStash = placement.getStashType() != STASH_TYPE_NONE + && (mPendingStash || placement.getTriggerStash()); + + mMainHandler.removeCallbacks(mApplyPendingPlacementRunnable); + mPendingPlacement = placement; + mPendingPlacementAnimationDuration = animationDuration; + mMainHandler.postAtTime(mApplyPendingPlacementRunnable, + mClock.get() + POSITION_DEBOUNCE_TIMEOUT_MILLIS); + } + + private void scheduleUnstashIfNeeded(final Placement placement) { + if (mUnstashRunnable != null) { + mMainHandler.removeCallbacks(mUnstashRunnable); + mUnstashRunnable = null; + } + if (placement.getUnstashDestinationBounds() != null) { + mUnstashRunnable = () -> { + movePipTo(placement.getUnstashDestinationBounds(), + mResizeAnimationDuration); + mUnstashRunnable = null; + }; + mMainHandler.postAtTime(mUnstashRunnable, mClock.get() + mStashDurationMs); + } + } + + private void applyPendingPlacement() { + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: applyPendingPlacement()", TAG); + } + if (mPendingPlacement != null) { + if (mPendingStash) { + mPendingStash = false; + scheduleUnstashIfNeeded(mPendingPlacement); + } + + if (mUnstashRunnable != null) { + // currently stashed, use stashed pos + movePipTo(mPendingPlacement.getBounds(), + mPendingPlacementAnimationDuration); + } else { + movePipTo(mPendingPlacement.getUnstashedBounds(), + mPendingPlacementAnimationDuration); + } + } + + mPendingPlacement = null; + } + + void onPipDismissed() { + mPipTargetBounds = null; + cancelScheduledPlacement(); + } + + private void cancelScheduledPlacement() { + mMainHandler.removeCallbacks(mApplyPendingPlacementRunnable); + mPendingPlacement = null; + + if (mUnstashRunnable != null) { + mMainHandler.removeCallbacks(mUnstashRunnable); + mUnstashRunnable = null; + } + } + + /** Animates the PiP to the given bounds with the given animation duration. */ + private void movePipTo(Rect bounds, int animationDuration) { + if (Objects.equals(mPipTargetBounds, bounds)) { + return; + } + + mPipTargetBounds = bounds; + if (DEBUG) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: movePipTo() - new pip bounds: %s", TAG, bounds.toShortString()); + } + + if (mListener != null) { + mListener.onPipTargetBoundsChange(bounds, animationDuration); + } + } + + /** + * Interface being notified of changes to the PiP bounds as calculated by + * @link TvPipBoundsController}. + */ + public interface PipBoundsListener { + /** + * Called when the calculated PiP bounds are changing. + * + * @param newTargetBounds The new bounds of the PiP. + * @param animationDuration The animation duration for the PiP movement. + */ + void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration); + } +} 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 fcd1f9583297..7e62ec1aa66b 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 @@ -25,12 +25,10 @@ import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.app.RemoteAction; import android.app.TaskInfo; -import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; -import android.os.Handler; import android.os.RemoteException; import android.view.Gravity; @@ -46,25 +44,24 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipAppOpsListener; -import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; 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.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; import java.util.Set; /** * Manages the picture-in-picture (PIP) UI and states. */ public class TvPipController implements PipTransitionController.PipTransitionCallback, - TvPipMenuController.Delegate, TvPipNotificationController.Delegate, - DisplayController.OnDisplaysChangedListener { + TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate, + TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener { private static final String TAG = "TvPipController"; static final boolean DEBUG = false; @@ -98,19 +95,18 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final TvPipBoundsState mTvPipBoundsState; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; + private final TvPipBoundsController mTvPipBoundsController; private final PipAppOpsListener mAppOpsListener; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; private final TvPipNotificationController mPipNotificationController; private final TvPipMenuController mTvPipMenuController; private final ShellExecutor mMainExecutor; - private final Handler mMainHandler; private final TvPipImpl mImpl = new TvPipImpl(); private @State int mState = STATE_NO_PIP; private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY; private int mPinnedTaskId = NONEXISTENT_TASK_ID; - private Runnable mUnstashRunnable; private RemoteAction mCloseAction; // How long the shell will wait for the app to close the PiP if a custom action is set. @@ -123,6 +119,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -133,12 +130,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper wmShell, - ShellExecutor mainExecutor, - Handler mainHandler) { + ShellExecutor mainExecutor) { return new TvPipController( context, tvPipBoundsState, tvPipBoundsAlgorithm, + tvPipBoundsController, pipAppOpsListener, pipTaskOrganizer, pipTransitionController, @@ -149,14 +146,14 @@ public class TvPipController implements PipTransitionController.PipTransitionCal pipParamsChangedForwarder, displayController, wmShell, - mainExecutor, - mainHandler).mImpl; + mainExecutor).mImpl; } private TvPipController( Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + TvPipBoundsController tvPipBoundsController, PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, @@ -167,16 +164,16 @@ public class TvPipController implements PipTransitionController.PipTransitionCal PipParamsChangedForwarder pipParamsChangedForwarder, DisplayController displayController, WindowManagerShellWrapper wmShell, - ShellExecutor mainExecutor, - Handler mainHandler) { + ShellExecutor mainExecutor) { mContext = context; mMainExecutor = mainExecutor; - mMainHandler = mainHandler; mTvPipBoundsState = tvPipBoundsState; mTvPipBoundsState.setDisplayId(context.getDisplayId()); mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay())); mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm; + mTvPipBoundsController = tvPipBoundsController; + mTvPipBoundsController.setListener(this); mPipMediaController = pipMediaController; @@ -227,7 +224,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal /** * Starts the process if bringing up the Pip menu if by issuing a command to move Pip * task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip - * task/window is properly positioned in {@link #onPipTransitionFinished(ComponentName, int)}. + * task/window is properly positioned in {@link #onPipTransitionFinished(int)}. */ @Override public void showPictureInPictureMenu() { @@ -256,7 +253,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal "%s: closeMenu(), state before=%s", TAG, stateToName(mState)); } setState(STATE_PIP); - mTvPipBoundsAlgorithm.keepUnstashedForCurrentKeepClearAreas(); updatePinnedStackBounds(); } @@ -329,68 +325,35 @@ public class TvPipController implements PipTransitionController.PipTransitionCal public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted, Set<Rect> unrestricted) { if (mTvPipBoundsState.getDisplayId() == displayId) { + boolean unrestrictedAreasChanged = !Objects.equals(unrestricted, + mTvPipBoundsState.getUnrestrictedKeepClearAreas()); mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted); - updatePinnedStackBounds(); + updatePinnedStackBounds(mResizeAnimationDuration, unrestrictedAreasChanged); } } private void updatePinnedStackBounds() { - updatePinnedStackBounds(mResizeAnimationDuration); + updatePinnedStackBounds(mResizeAnimationDuration, true); } /** * Update the PiP bounds based on the state of the PiP and keep clear areas. - * Animates to the current PiP bounds, and schedules unstashing the PiP if necessary. */ - private void updatePinnedStackBounds(int animationDuration) { + private void updatePinnedStackBounds(int animationDuration, boolean immediate) { if (mState == STATE_NO_PIP) { return; } - final boolean stayAtAnchorPosition = mTvPipMenuController.isInMoveMode(); final boolean disallowStashing = mState == STATE_PIP_MENU || stayAtAnchorPosition; - final Placement placement = mTvPipBoundsAlgorithm.getTvPipBounds(); - - int stashType = - disallowStashing ? PipBoundsState.STASH_TYPE_NONE : placement.getStashType(); - mTvPipBoundsState.setStashed(stashType); - - if (stayAtAnchorPosition) { - movePinnedStackTo(placement.getAnchorBounds()); - } else if (disallowStashing) { - movePinnedStackTo(placement.getUnstashedBounds()); - } else { - movePinnedStackTo(placement.getBounds()); - } - - if (mUnstashRunnable != null) { - mMainHandler.removeCallbacks(mUnstashRunnable); - mUnstashRunnable = null; - } - if (!disallowStashing && placement.getUnstashDestinationBounds() != null) { - mUnstashRunnable = () -> { - movePinnedStackTo(placement.getUnstashDestinationBounds(), animationDuration); - }; - mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime()); - } - } - - /** Animates the PiP to the given bounds. */ - private void movePinnedStackTo(Rect bounds) { - movePinnedStackTo(bounds, mResizeAnimationDuration); + mTvPipBoundsController.recalculatePipBounds(stayAtAnchorPosition, disallowStashing, + animationDuration, immediate); } - /** Animates the PiP to the given bounds with the given animation duration. */ - private void movePinnedStackTo(Rect bounds, int animationDuration) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString()); - } - mPipTaskOrganizer.scheduleAnimateResizePip(bounds, - animationDuration, rect -> { - mTvPipMenuController.updateExpansionState(); - }); - mTvPipMenuController.onPipTransitionStarted(bounds); + @Override + public void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration) { + mPipTaskOrganizer.scheduleAnimateResizePip(newTargetBounds, + animationDuration, rect -> mTvPipMenuController.updateExpansionState()); + mTvPipMenuController.onPipTransitionStarted(newTargetBounds); } /** @@ -429,7 +392,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void closeEduText() { - updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs); + updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs, false); } private void registerSessionListenerForCurrentUser() { @@ -471,6 +434,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mPipNotificationController.dismiss(); mTvPipMenuController.closeMenu(); mTvPipBoundsState.resetTvPipState(); + mTvPipBoundsController.onPipDismissed(); setState(STATE_NO_PIP); mPinnedTaskId = NONEXISTENT_TASK_ID; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt index 07dccd58abfd..29f34849fd7b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt @@ -33,17 +33,14 @@ import kotlin.math.min import kotlin.math.roundToInt private const val DEFAULT_PIP_MARGINS = 48 -private const val DEFAULT_STASH_DURATION = 5000L private const val RELAX_DEPTH = 1 private const val DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION = 0.15 /** * This class calculates an appropriate position for a Picture-In-Picture (PiP) window, taking * into account app defined keep clear areas. - * - * @param clock A function returning a current timestamp (in milliseconds) */ -class TvPipKeepClearAlgorithm(private val clock: () -> Long) { +class TvPipKeepClearAlgorithm() { /** * Result of the positioning algorithm. * @@ -51,17 +48,17 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { * @param anchorBounds The bounds of the PiP anchor position * (where the PiP would be placed if there were no keep clear areas) * @param stashType Where the PiP has been stashed, if at all - * @param unstashDestinationBounds If stashed, the PiP should move to this position after - * [stashDuration] has passed. - * @param unstashTime If stashed, the time at which the PiP should move - * to [unstashDestinationBounds] + * @param unstashDestinationBounds If stashed, the PiP should move to this position when + * unstashing. + * @param triggerStash Whether this placement should trigger the PiP to stash, or extend + * the unstash timeout if already stashed. */ data class Placement( val bounds: Rect, val anchorBounds: Rect, @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE, val unstashDestinationBounds: Rect? = null, - val unstashTime: Long = 0L + val triggerStash: Boolean = false ) { /** Bounds to use if the PiP should not be stashed. */ fun getUnstashedBounds() = unstashDestinationBounds ?: bounds @@ -79,12 +76,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { /** The distance the PiP peeks into the screen when stashed */ var stashOffset = DEFAULT_PIP_MARGINS - /** - * How long (in milliseconds) the PiP should stay stashed for after the last time the - * keep clear areas causing the PiP to stash have changed. - */ - var stashDuration = DEFAULT_STASH_DURATION - /** The fraction of screen width/height restricted keep clear areas can move the PiP */ var maxRestrictedDistanceFraction = DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION @@ -93,7 +84,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { private var transformedMovementBounds = Rect() private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet() - private var lastStashTime: Long = Long.MIN_VALUE /** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP * decorations are relevant for calculating intersecting keep clear areas */ @@ -113,8 +103,8 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { * always try to respect these areas. * * If no free space the PiP is allowed to move to can be found, a stashed position is returned - * as [Placement.bounds], along with a position to move to once [Placement.unstashTime] has - * passed as [Placement.unstashDestinationBounds]. + * as [Placement.bounds], along with a position to move to when the PiP unstashes + * as [Placement.unstashDestinationBounds]. * * @param pipSize The size of the PiP window * @param restrictedAreas The restricted keep clear areas @@ -152,7 +142,7 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { anchorBounds, getStashType(pipBounds, unstashedDestBounds), unstashedDestBounds, - result.unstashTime + result.triggerStash ) } @@ -213,26 +203,13 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { !lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition) lastAreasOverlappingUnstashPosition = areasOverlappingUnstashPosition - val now = clock() - if (areasOverlappingUnstashPositionChanged) { - lastStashTime = now - } - - // If overlapping areas haven't changed and the stash duration has passed, we can - // place the PiP at the unstash position - val unstashTime = lastStashTime + stashDuration - if (now >= unstashTime) { - return Placement(unstashBounds, pipAnchorBounds) - } - - // Otherwise, we'll stash it close to the unstash position val stashedBounds = getNearbyStashedPosition(unstashBounds, keepClearAreas) return Placement( stashedBounds, pipAnchorBounds, getStashType(stashedBounds, unstashBounds), unstashBounds, - unstashTime + areasOverlappingUnstashPositionChanged ) } @@ -439,14 +416,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) { } /** - * Prevents the PiP from being stashed for the current set of keep clear areas. - * The PiP may stash again if keep clear areas change. - */ - fun keepUnstashedForCurrentKeepClearAreas() { - lastStashTime = Long.MIN_VALUE - } - - /** * Updates the size of the screen. * * @param size The new size of the screen 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 132c04481bce..4ce45e142c64 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 @@ -209,7 +209,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMovementMenuOnly()", TAG); } - mInMoveMode = true; + setInMoveMode(true); mCloseAfterExitMoveMenu = true; showMenuInternal(); } @@ -219,7 +219,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG); } - mInMoveMode = false; + setInMoveMode(false); mCloseAfterExitMoveMenu = false; showMenuInternal(); } @@ -293,6 +293,17 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis return mInMoveMode; } + private void setInMoveMode(boolean moveMode) { + if (mInMoveMode == moveMode) { + return; + } + + mInMoveMode = moveMode; + if (mDelegate != null) { + mDelegate.onInMoveModeChanged(); + } + } + @Override public void onEnterMoveMode() { if (DEBUG) { @@ -300,7 +311,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode, mCloseAfterExitMoveMenu); } - mInMoveMode = true; + setInMoveMode(true); mPipMenuView.showMoveMenu(mDelegate.getPipGravity()); } @@ -312,13 +323,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis mCloseAfterExitMoveMenu); } if (mCloseAfterExitMoveMenu) { - mInMoveMode = false; + setInMoveMode(false); mCloseAfterExitMoveMenu = false; closeMenu(); return true; } if (mInMoveMode) { - mInMoveMode = false; + setInMoveMode(false); mPipMenuView.showButtonsMenu(); return true; } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index a899709c1405..ea10be564351 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -37,6 +37,7 @@ android_test { "androidx.test.ext.junit", "androidx.dynamicanimation_dynamicanimation", "dagger2", + "frameworks-base-testutils", "kotlinx-coroutines-android", "kotlinx-coroutines-core", "mockito-target-extended-minus-junit4", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt new file mode 100644 index 000000000000..dae5dfdc6d8d --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.android.wm.shell.pip.tv + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.os.Handler +import android.os.test.TestLooper +import android.testing.AndroidTestingRunner + +import com.android.wm.shell.R +import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT +import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS +import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.eq +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +class TvPipBoundsControllerTest { + val ANIMATION_DURATION = 100 + val STASH_DURATION = 5000 + val FAR_FUTURE = 60 * 60000L + val ANCHOR_BOUNDS = Rect(90, 90, 100, 100) + val STASHED_BOUNDS = Rect(99, 90, 109, 100) + val MOVED_BOUNDS = Rect(90, 80, 100, 90) + val STASHED_MOVED_BOUNDS = Rect(99, 80, 109, 90) + val ANCHOR_PLACEMENT = Placement(ANCHOR_BOUNDS, ANCHOR_BOUNDS) + val STASHED_PLACEMENT = Placement(STASHED_BOUNDS, ANCHOR_BOUNDS, + STASH_TYPE_RIGHT, ANCHOR_BOUNDS, false) + val STASHED_PLACEMENT_RESTASH = Placement(STASHED_BOUNDS, ANCHOR_BOUNDS, + STASH_TYPE_RIGHT, ANCHOR_BOUNDS, true) + val MOVED_PLACEMENT = Placement(MOVED_BOUNDS, ANCHOR_BOUNDS) + val STASHED_MOVED_PLACEMENT = Placement(STASHED_MOVED_BOUNDS, ANCHOR_BOUNDS, + STASH_TYPE_RIGHT, MOVED_BOUNDS, false) + val STASHED_MOVED_PLACEMENT_RESTASH = Placement(STASHED_MOVED_BOUNDS, ANCHOR_BOUNDS, + STASH_TYPE_RIGHT, MOVED_BOUNDS, true) + + lateinit var boundsController: TvPipBoundsController + var time = 0L + lateinit var testLooper: TestLooper + lateinit var mainHandler: Handler + + var inMenu = false + var inMoveMode = false + + @Mock + lateinit var context: Context + @Mock + lateinit var resources: Resources + @Mock + lateinit var tvPipBoundsState: TvPipBoundsState + @Mock + lateinit var tvPipBoundsAlgorithm: TvPipBoundsAlgorithm + @Mock + lateinit var listener: TvPipBoundsController.PipBoundsListener + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + time = 0L + inMenu = false + inMoveMode = false + + testLooper = TestLooper { time } + mainHandler = Handler(testLooper.getLooper()) + + whenever(context.resources).thenReturn(resources) + whenever(resources.getInteger(R.integer.config_pipStashDuration)).thenReturn(STASH_DURATION) + + boundsController = TvPipBoundsController( + context, + { time }, + mainHandler, + tvPipBoundsState, + tvPipBoundsAlgorithm) + boundsController.setListener(listener) + } + + @Test + fun testPlacement_MovedAfterDebounceTimeout() { + triggerPlacement(MOVED_PLACEMENT) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + @Test + fun testStashedPlacement_MovedAfterDebounceTimeout_Unstashes() { + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS + STASH_DURATION, ANCHOR_BOUNDS) + } + + @Test + fun testDebounceSamePlacement_MovesDebounceTimeoutAfterFirstPlacement() { + triggerPlacement(MOVED_PLACEMENT) + advanceTimeTo(POSITION_DEBOUNCE_TIMEOUT_MILLIS / 2) + triggerPlacement(MOVED_PLACEMENT) + + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS) + } + + @Test + fun testNoMovementUntilPlacementStabilizes() { + triggerPlacement(ANCHOR_PLACEMENT) + advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10) + triggerPlacement(MOVED_PLACEMENT) + advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10) + triggerPlacement(ANCHOR_PLACEMENT) + advanceTimeTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS / 10) + triggerPlacement(MOVED_PLACEMENT) + + assertMovementAt(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS, MOVED_BOUNDS) + } + + @Test + fun testUnstashIfStashNoLongerNecessary() { + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) + + triggerPlacement(ANCHOR_PLACEMENT) + assertMovementAt(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS, ANCHOR_BOUNDS) + } + + @Test + fun testRestashingPlacementDelaysUnstash() { + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) + + assertNoMovementUpTo(time + STASH_DURATION / 2) + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertNoMovementUpTo(time + POSITION_DEBOUNCE_TIMEOUT_MILLIS) + assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS) + } + + @Test + fun testNonRestashingPlacementDoesNotDelayUnstash() { + triggerPlacement(STASHED_PLACEMENT_RESTASH) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS, STASHED_BOUNDS) + + assertNoMovementUpTo(time + STASH_DURATION / 2) + triggerPlacement(STASHED_PLACEMENT) + assertMovementAt(POSITION_DEBOUNCE_TIMEOUT_MILLIS + STASH_DURATION, ANCHOR_BOUNDS) + } + + @Test + fun testImmediatePlacement() { + triggerImmediatePlacement(STASHED_PLACEMENT_RESTASH) + assertMovement(STASHED_BOUNDS) + assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS) + } + + @Test + fun testInMoveMode_KeepAtAnchor() { + startMoveMode() + triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) + assertMovement(ANCHOR_BOUNDS) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + @Test + fun testInMenu_Unstashed() { + openPipMenu() + triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) + assertMovement(MOVED_BOUNDS) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + @Test + fun testCloseMenu_DoNotRestash() { + openPipMenu() + triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH) + assertMovement(MOVED_BOUNDS) + + closePipMenu() + triggerPlacement(STASHED_MOVED_PLACEMENT) + assertNoMovementUpTo(time + FAR_FUTURE) + } + + fun assertMovement(bounds: Rect) { + verify(listener).onPipTargetBoundsChange(eq(bounds), anyInt()) + reset(listener) + } + + fun assertMovementAt(timeMs: Long, bounds: Rect) { + assertNoMovementUpTo(timeMs - 1) + advanceTimeTo(timeMs) + assertMovement(bounds) + } + + fun assertNoMovementUpTo(timeMs: Long) { + advanceTimeTo(timeMs) + verify(listener, never()).onPipTargetBoundsChange(any(), anyInt()) + } + + fun triggerPlacement(placement: Placement, immediate: Boolean = false) { + whenever(tvPipBoundsAlgorithm.getTvPipPlacement()).thenReturn(placement) + val stayAtAnchorPosition = inMoveMode + val disallowStashing = inMenu || stayAtAnchorPosition + boundsController.recalculatePipBounds(stayAtAnchorPosition, disallowStashing, + ANIMATION_DURATION, immediate) + } + + fun triggerImmediatePlacement(placement: Placement) { + triggerPlacement(placement, true) + } + + fun openPipMenu() { + inMenu = true + inMoveMode = false + } + + fun closePipMenu() { + inMenu = false + inMoveMode = false + } + + fun startMoveMode() { + inMenu = true + inMoveMode = true + } + + fun advanceTimeTo(ms: Long) { + time = ms + testLooper.dispatchAll() + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt index 46f388d0ce0e..e21b61596dad 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt @@ -30,7 +30,9 @@ import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement import org.junit.Before import org.junit.Test import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNull +import junit.framework.Assert.assertTrue @RunWith(AndroidTestingRunner::class) class TvPipKeepClearAlgorithmTest { @@ -46,7 +48,6 @@ class TvPipKeepClearAlgorithmTest { private lateinit var pipSize: Size private lateinit var movementBounds: Rect private lateinit var algorithm: TvPipKeepClearAlgorithm - private var currentTime = 0L private var restrictedAreas = mutableSetOf<Rect>() private var unrestrictedAreas = mutableSetOf<Rect>() private var gravity: Int = 0 @@ -58,16 +59,14 @@ class TvPipKeepClearAlgorithmTest { restrictedAreas.clear() unrestrictedAreas.clear() - currentTime = 0L pipSize = DEFAULT_PIP_SIZE gravity = Gravity.BOTTOM or Gravity.RIGHT - algorithm = TvPipKeepClearAlgorithm({ currentTime }) + algorithm = TvPipKeepClearAlgorithm() algorithm.setScreenSize(SCREEN_SIZE) algorithm.setMovementBounds(movementBounds) algorithm.pipAreaPadding = PADDING algorithm.stashOffset = STASH_OFFSET - algorithm.stashDuration = 5000L algorithm.setGravity(gravity) algorithm.maxRestrictedDistanceFraction = 0.3 } @@ -265,7 +264,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_BOTTOM, placement.stashType) assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) + assertTrue(placement.triggerStash) } @Test @@ -305,7 +304,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) + assertTrue(placement.triggerStash) } @Test @@ -352,9 +351,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) - - currentTime += 1000 + assertTrue(placement.triggerStash) restrictedAreas.remove(sideBar) placement = getActualPlacement() @@ -363,7 +360,7 @@ class TvPipKeepClearAlgorithmTest { } @Test - fun test_Stashed_UnstashBoundsStaysObstructed_UnstashesAfterTimeout() { + fun test_Stashed_UnstashBoundsStaysObstructed_DoesNotTriggerStash() { gravity = Gravity.BOTTOM or Gravity.RIGHT val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT) @@ -384,13 +381,13 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) - - currentTime += algorithm.stashDuration + assertTrue(placement.triggerStash) placement = getActualPlacement() - assertEquals(expectedUnstashBounds, placement.bounds) - assertNotStashed(placement) + assertEquals(expectedBounds, placement.bounds) + assertEquals(STASH_TYPE_RIGHT, placement.stashType) + assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) + assertFalse(placement.triggerStash) } @Test @@ -415,9 +412,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(algorithm.stashDuration, placement.unstashTime) - - currentTime += 1000 + assertTrue(placement.triggerStash) val newObstruction = Rect( 0, @@ -431,7 +426,7 @@ class TvPipKeepClearAlgorithmTest { assertEquals(expectedBounds, placement.bounds) assertEquals(STASH_TYPE_RIGHT, placement.stashType) assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds) - assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime) + assertTrue(placement.triggerStash) } @Test @@ -546,6 +541,6 @@ class TvPipKeepClearAlgorithmTest { private fun assertNotStashed(actual: Placement) { assertEquals(STASH_TYPE_NONE, actual.stashType) assertNull(actual.unstashDestinationBounds) - assertEquals(0L, actual.unstashTime) + assertFalse(actual.triggerStash) } } |