Merge changes I49d32c18,I8e8736fc into tm-dev

* changes:
  Apply temporary PiP decor separately from overall placement
  Debounce TV PiP movements
This commit is contained in:
Robert Horvath
2022-05-11 07:59:53 +00:00
committed by Android (Google) Code Review
9 changed files with 620 additions and 184 deletions

View File

@@ -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

View File

@@ -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 adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
}
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
@@ -114,13 +112,27 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio);
}
return getTvPipBounds().getBounds();
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
}
Rect adjustBoundsForTemporaryDecor(Rect bounds) {
Rect boundsWithDecor = new Rect(bounds);
Insets decorInset = mTvPipBoundsState.getPipMenuTemporaryDecorInsets();
Insets pipDecorReverseInsets = Insets.subtract(Insets.NONE, decorInset);
boundsWithDecor.inset(decorInset);
Gravity.apply(mTvPipBoundsState.getTvPipGravity(),
boundsWithDecor.width(), boundsWithDecor.height(), bounds, boundsWithDecor);
// remove temporary decoration again
boundsWithDecor.inset(pipDecorReverseInsets);
return boundsWithDecor;
}
/**
* 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());
@@ -153,8 +165,6 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
mKeepClearAlgorithm.setPipPermanentDecorInsets(
mTvPipBoundsState.getPipMenuPermanentDecorInsets());
mKeepClearAlgorithm.setPipTemporaryDecorInsets(
mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
pipSize,
@@ -407,8 +417,4 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
TAG, expandedSize.getWidth(), expandedSize.getHeight());
}
}
void keepUnstashedForCurrentKeepClearAreas() {
mKeepClearAlgorithm.keepUnstashedForCurrentKeepClearAreas();
}
}

View File

@@ -0,0 +1,253 @@
/*
* 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 mCurrentPlacementBounds;
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, decors, and keep clear areas.
* Unless {@code immediate} is {@code true}, the PiP does not move immediately to avoid
* keep clear areas, but waits for a new position to stay uncontested for
* {@link #POSITION_DEBOUNCE_TIMEOUT_MILLIS} before moving to it.
* Temporary decor changes are applied immediately.
*
* @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 to avoid keep clear areas
*/
@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();
applyPlacementBounds(placement.getAnchorBounds(), animationDuration);
} else if (disallowStashing) {
cancelScheduledPlacement();
applyPlacementBounds(placement.getUnstashedBounds(), animationDuration);
} else if (immediate) {
cancelScheduledPlacement();
applyPlacementBounds(placement.getBounds(), animationDuration);
scheduleUnstashIfNeeded(placement);
} else {
applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
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 = () -> {
applyPlacementBounds(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
applyPlacementBounds(mPendingPlacement.getBounds(),
mPendingPlacementAnimationDuration);
} else {
applyPlacementBounds(mPendingPlacement.getUnstashedBounds(),
mPendingPlacementAnimationDuration);
}
}
mPendingPlacement = null;
}
void onPipDismissed() {
mCurrentPlacementBounds = null;
mPipTargetBounds = null;
cancelScheduledPlacement();
}
private void cancelScheduledPlacement() {
mMainHandler.removeCallbacks(mApplyPendingPlacementRunnable);
mPendingPlacement = null;
if (mUnstashRunnable != null) {
mMainHandler.removeCallbacks(mUnstashRunnable);
mUnstashRunnable = null;
}
}
private void applyPlacementBounds(Rect bounds, int animationDuration) {
if (bounds == null) {
return;
}
mCurrentPlacementBounds = bounds;
Rect adjustedBounds = mTvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(bounds);
movePipTo(adjustedBounds, animationDuration);
}
/** 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);
}
}

View File

@@ -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();
}
@@ -331,68 +327,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());
}
mTvPipBoundsController.recalculatePipBounds(stayAtAnchorPosition, disallowStashing,
animationDuration, immediate);
}
/** Animates the PiP to the given bounds. */
private void movePinnedStackTo(Rect bounds) {
movePinnedStackTo(bounds, mResizeAnimationDuration);
}
/** 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);
}
/**
@@ -431,7 +394,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void closeEduText() {
updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs);
updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs, false);
}
private void registerSessionListenerForCurrentUser() {
@@ -473,6 +436,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mPipNotificationController.dismiss();
mTvPipMenuController.closeMenu();
mTvPipBoundsState.resetTvPipState();
mTvPipBoundsController.onPipDismissed();
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}

View File

@@ -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,14 +84,10 @@ 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 */
private var pipPermanentDecorInsets = Insets.NONE
/** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP
* decorations are not relevant for calculating intersecting keep clear areas */
private var pipTemporaryDecorInsets = Insets.NONE
/**
* Calculates the position the PiP should be placed at, taking into consideration the
@@ -113,8 +100,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
@@ -130,13 +117,11 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
val pipSizeWithAllDecors = addDecors(pipSize)
val pipAnchorBoundsWithAllDecors =
val pipAnchorBoundsWithDecors =
getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
val pipAnchorBoundsWithPermanentDecors =
removeTemporaryDecorsTransformed(pipAnchorBoundsWithAllDecors)
val result = calculatePipPositionTransformed(
pipAnchorBoundsWithPermanentDecors,
pipAnchorBoundsWithDecors,
transformedRestrictedAreas,
transformedUnrestrictedAreas
)
@@ -152,7 +137,7 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
anchorBounds,
getStashType(pipBounds, unstashedDestBounds),
unstashedDestBounds,
result.unstashTime
result.triggerStash
)
}
@@ -213,26 +198,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
)
}
@@ -438,14 +410,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
} ?: bounds
}
/**
* 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.
*
@@ -492,10 +456,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
pipPermanentDecorInsets = insets
}
fun setPipTemporaryDecorInsets(insets: Insets) {
pipTemporaryDecorInsets = insets
}
/**
* @param open Whether this event marks the opening of an occupied segment
* @param pos The coordinate of this event
@@ -790,7 +750,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
private fun addDecors(size: Size): Size {
val bounds = Rect(0, 0, size.width, size.height)
bounds.inset(pipPermanentDecorInsets)
bounds.inset(pipTemporaryDecorInsets)
return Size(bounds.width(), bounds.height())
}
@@ -805,19 +764,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
return bounds
}
/**
* Removes the space that was reserved for temporary decorations around the PiP
* @param bounds the bounds (in base case) to remove the insets from
*/
private fun removeTemporaryDecorsTransformed(bounds: Rect): Rect {
if (pipTemporaryDecorInsets == Insets.NONE) return bounds
val reverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
val boundsInScreenSpace = fromTransformedSpace(bounds)
boundsInScreenSpace.inset(reverseInsets)
return toTransformedSpace(boundsInScreenSpace)
}
private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -0,0 +1,255 @@
/*
* 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.AdditionalAnswers.returnsFirstArg
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)
whenever(tvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(any()))
.then(returnsFirstArg<Rect>())
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()
}
}

View File

@@ -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
@@ -458,21 +453,9 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_PipInsets() {
val permInsets = Insets.of(-1, -2, -3, -4)
algorithm.setPipPermanentDecorInsets(permInsets)
testInsetsForAllPositions(permInsets)
val insets = Insets.of(-1, -2, -3, -4)
algorithm.setPipPermanentDecorInsets(insets)
val tempInsets = Insets.of(-4, -3, -2, -1)
algorithm.setPipPermanentDecorInsets(Insets.NONE)
algorithm.setPipTemporaryDecorInsets(tempInsets)
testInsetsForAllPositions(tempInsets)
algorithm.setPipPermanentDecorInsets(permInsets)
algorithm.setPipTemporaryDecorInsets(tempInsets)
testInsetsForAllPositions(Insets.add(permInsets, tempInsets))
}
private fun testInsetsForAllPositions(insets: Insets) {
gravity = Gravity.BOTTOM or Gravity.RIGHT
testAnchorPositionWithInsets(insets)
@@ -546,6 +529,6 @@ class TvPipKeepClearAlgorithmTest {
private fun assertNotStashed(actual: Placement) {
assertEquals(STASH_TYPE_NONE, actual.stashType)
assertNull(actual.unstashDestinationBounds)
assertEquals(0L, actual.unstashTime)
assertFalse(actual.triggerStash)
}
}