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:
committed by
Android (Google) Code Review
commit
d338520c9f
@@ -18,6 +18,7 @@ package com.android.wm.shell.dagger;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
import com.android.wm.shell.ShellTaskOrganizer;
|
import com.android.wm.shell.ShellTaskOrganizer;
|
||||||
import com.android.wm.shell.WindowManagerShellWrapper;
|
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.PipTransitionState;
|
||||||
import com.android.wm.shell.pip.PipUiEventLogger;
|
import com.android.wm.shell.pip.PipUiEventLogger;
|
||||||
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
|
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.TvPipBoundsState;
|
||||||
import com.android.wm.shell.pip.tv.TvPipController;
|
import com.android.wm.shell.pip.tv.TvPipController;
|
||||||
import com.android.wm.shell.pip.tv.TvPipMenuController;
|
import com.android.wm.shell.pip.tv.TvPipMenuController;
|
||||||
@@ -64,6 +66,7 @@ public abstract class TvPipModule {
|
|||||||
Context context,
|
Context context,
|
||||||
TvPipBoundsState tvPipBoundsState,
|
TvPipBoundsState tvPipBoundsState,
|
||||||
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
|
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
|
||||||
|
TvPipBoundsController tvPipBoundsController,
|
||||||
PipAppOpsListener pipAppOpsListener,
|
PipAppOpsListener pipAppOpsListener,
|
||||||
PipTaskOrganizer pipTaskOrganizer,
|
PipTaskOrganizer pipTaskOrganizer,
|
||||||
TvPipMenuController tvPipMenuController,
|
TvPipMenuController tvPipMenuController,
|
||||||
@@ -74,13 +77,13 @@ public abstract class TvPipModule {
|
|||||||
PipParamsChangedForwarder pipParamsChangedForwarder,
|
PipParamsChangedForwarder pipParamsChangedForwarder,
|
||||||
DisplayController displayController,
|
DisplayController displayController,
|
||||||
WindowManagerShellWrapper windowManagerShellWrapper,
|
WindowManagerShellWrapper windowManagerShellWrapper,
|
||||||
@ShellMainThread ShellExecutor mainExecutor,
|
@ShellMainThread ShellExecutor mainExecutor) {
|
||||||
@ShellMainThread Handler mainHandler) {
|
|
||||||
return Optional.of(
|
return Optional.of(
|
||||||
TvPipController.create(
|
TvPipController.create(
|
||||||
context,
|
context,
|
||||||
tvPipBoundsState,
|
tvPipBoundsState,
|
||||||
tvPipBoundsAlgorithm,
|
tvPipBoundsAlgorithm,
|
||||||
|
tvPipBoundsController,
|
||||||
pipAppOpsListener,
|
pipAppOpsListener,
|
||||||
pipTaskOrganizer,
|
pipTaskOrganizer,
|
||||||
pipTransitionController,
|
pipTransitionController,
|
||||||
@@ -91,8 +94,22 @@ public abstract class TvPipModule {
|
|||||||
pipParamsChangedForwarder,
|
pipParamsChangedForwarder,
|
||||||
displayController,
|
displayController,
|
||||||
windowManagerShellWrapper,
|
windowManagerShellWrapper,
|
||||||
mainExecutor,
|
mainExecutor));
|
||||||
mainHandler));
|
}
|
||||||
|
|
||||||
|
@WMSingleton
|
||||||
|
@Provides
|
||||||
|
static TvPipBoundsController provideTvPipBoundsController(
|
||||||
|
Context context,
|
||||||
|
@ShellMainThread Handler mainHandler,
|
||||||
|
TvPipBoundsState tvPipBoundsState,
|
||||||
|
TvPipBoundsAlgorithm tvPipBoundsAlgorithm) {
|
||||||
|
return new TvPipBoundsController(
|
||||||
|
context,
|
||||||
|
SystemClock::uptimeMillis,
|
||||||
|
mainHandler,
|
||||||
|
tvPipBoundsState,
|
||||||
|
tvPipBoundsAlgorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@WMSingleton
|
@WMSingleton
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import android.content.Context;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Insets;
|
import android.graphics.Insets;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.Size;
|
import android.util.Size;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
@@ -66,7 +65,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
|
|||||||
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
|
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
|
||||||
super(context, tvPipBoundsState, pipSnapAlgorithm);
|
super(context, tvPipBoundsState, pipSnapAlgorithm);
|
||||||
this.mTvPipBoundsState = tvPipBoundsState;
|
this.mTvPipBoundsState = tvPipBoundsState;
|
||||||
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm(SystemClock::uptimeMillis);
|
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
|
||||||
reloadResources(context);
|
reloadResources(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +79,6 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
|
|||||||
res.getDimensionPixelSize(R.dimen.pip_keep_clear_area_padding));
|
res.getDimensionPixelSize(R.dimen.pip_keep_clear_area_padding));
|
||||||
mKeepClearAlgorithm.setMaxRestrictedDistanceFraction(
|
mKeepClearAlgorithm.setMaxRestrictedDistanceFraction(
|
||||||
res.getFraction(R.fraction.config_pipMaxRestrictedMoveDistance, 1, 1));
|
res.getFraction(R.fraction.config_pipMaxRestrictedMoveDistance, 1, 1));
|
||||||
mKeepClearAlgorithm.setStashDuration(res.getInteger(R.integer.config_pipStashDuration));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -104,7 +102,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
|
|||||||
updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true);
|
updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true);
|
||||||
}
|
}
|
||||||
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
|
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
|
||||||
return getTvPipBounds().getBounds();
|
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
|
/** 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,
|
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||||
"%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio);
|
"%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.
|
* Calculates the PiP bounds.
|
||||||
*/
|
*/
|
||||||
public Placement getTvPipBounds() {
|
@NonNull
|
||||||
|
public Placement getTvPipPlacement() {
|
||||||
final Size pipSize = getPipSize();
|
final Size pipSize = getPipSize();
|
||||||
final Rect displayBounds = mTvPipBoundsState.getDisplayBounds();
|
final Rect displayBounds = mTvPipBoundsState.getDisplayBounds();
|
||||||
final Size screenSize = new Size(displayBounds.width(), displayBounds.height());
|
final Size screenSize = new Size(displayBounds.width(), displayBounds.height());
|
||||||
@@ -153,8 +165,6 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
|
|||||||
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
|
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
|
||||||
mKeepClearAlgorithm.setPipPermanentDecorInsets(
|
mKeepClearAlgorithm.setPipPermanentDecorInsets(
|
||||||
mTvPipBoundsState.getPipMenuPermanentDecorInsets());
|
mTvPipBoundsState.getPipMenuPermanentDecorInsets());
|
||||||
mKeepClearAlgorithm.setPipTemporaryDecorInsets(
|
|
||||||
mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
|
|
||||||
|
|
||||||
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
|
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
|
||||||
pipSize,
|
pipSize,
|
||||||
@@ -407,8 +417,4 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
|
|||||||
TAG, expandedSize.getWidth(), expandedSize.getHeight());
|
TAG, expandedSize.getWidth(), expandedSize.getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void keepUnstashedForCurrentKeepClearAreas() {
|
|
||||||
mKeepClearAlgorithm.keepUnstashedForCurrentKeepClearAreas();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,12 +25,10 @@ import android.app.ActivityTaskManager;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.RemoteAction;
|
import android.app.RemoteAction;
|
||||||
import android.app.TaskInfo;
|
import android.app.TaskInfo;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.view.Gravity;
|
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.Pip;
|
||||||
import com.android.wm.shell.pip.PipAnimationController;
|
import com.android.wm.shell.pip.PipAnimationController;
|
||||||
import com.android.wm.shell.pip.PipAppOpsListener;
|
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.PipMediaController;
|
||||||
import com.android.wm.shell.pip.PipParamsChangedForwarder;
|
import com.android.wm.shell.pip.PipParamsChangedForwarder;
|
||||||
import com.android.wm.shell.pip.PipTaskOrganizer;
|
import com.android.wm.shell.pip.PipTaskOrganizer;
|
||||||
import com.android.wm.shell.pip.PipTransitionController;
|
import com.android.wm.shell.pip.PipTransitionController;
|
||||||
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
|
|
||||||
import com.android.wm.shell.protolog.ShellProtoLogGroup;
|
import com.android.wm.shell.protolog.ShellProtoLogGroup;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the picture-in-picture (PIP) UI and states.
|
* Manages the picture-in-picture (PIP) UI and states.
|
||||||
*/
|
*/
|
||||||
public class TvPipController implements PipTransitionController.PipTransitionCallback,
|
public class TvPipController implements PipTransitionController.PipTransitionCallback,
|
||||||
TvPipMenuController.Delegate, TvPipNotificationController.Delegate,
|
TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
|
||||||
DisplayController.OnDisplaysChangedListener {
|
TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener {
|
||||||
private static final String TAG = "TvPipController";
|
private static final String TAG = "TvPipController";
|
||||||
static final boolean DEBUG = false;
|
static final boolean DEBUG = false;
|
||||||
|
|
||||||
@@ -98,19 +95,18 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
|||||||
|
|
||||||
private final TvPipBoundsState mTvPipBoundsState;
|
private final TvPipBoundsState mTvPipBoundsState;
|
||||||
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
|
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
|
||||||
|
private final TvPipBoundsController mTvPipBoundsController;
|
||||||
private final PipAppOpsListener mAppOpsListener;
|
private final PipAppOpsListener mAppOpsListener;
|
||||||
private final PipTaskOrganizer mPipTaskOrganizer;
|
private final PipTaskOrganizer mPipTaskOrganizer;
|
||||||
private final PipMediaController mPipMediaController;
|
private final PipMediaController mPipMediaController;
|
||||||
private final TvPipNotificationController mPipNotificationController;
|
private final TvPipNotificationController mPipNotificationController;
|
||||||
private final TvPipMenuController mTvPipMenuController;
|
private final TvPipMenuController mTvPipMenuController;
|
||||||
private final ShellExecutor mMainExecutor;
|
private final ShellExecutor mMainExecutor;
|
||||||
private final Handler mMainHandler;
|
|
||||||
private final TvPipImpl mImpl = new TvPipImpl();
|
private final TvPipImpl mImpl = new TvPipImpl();
|
||||||
|
|
||||||
private @State int mState = STATE_NO_PIP;
|
private @State int mState = STATE_NO_PIP;
|
||||||
private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
|
private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
|
||||||
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
|
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
|
||||||
private Runnable mUnstashRunnable;
|
|
||||||
|
|
||||||
private RemoteAction mCloseAction;
|
private RemoteAction mCloseAction;
|
||||||
// How long the shell will wait for the app to close the PiP if a custom action is set.
|
// 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,
|
Context context,
|
||||||
TvPipBoundsState tvPipBoundsState,
|
TvPipBoundsState tvPipBoundsState,
|
||||||
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
|
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
|
||||||
|
TvPipBoundsController tvPipBoundsController,
|
||||||
PipAppOpsListener pipAppOpsListener,
|
PipAppOpsListener pipAppOpsListener,
|
||||||
PipTaskOrganizer pipTaskOrganizer,
|
PipTaskOrganizer pipTaskOrganizer,
|
||||||
PipTransitionController pipTransitionController,
|
PipTransitionController pipTransitionController,
|
||||||
@@ -133,12 +130,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
|||||||
PipParamsChangedForwarder pipParamsChangedForwarder,
|
PipParamsChangedForwarder pipParamsChangedForwarder,
|
||||||
DisplayController displayController,
|
DisplayController displayController,
|
||||||
WindowManagerShellWrapper wmShell,
|
WindowManagerShellWrapper wmShell,
|
||||||
ShellExecutor mainExecutor,
|
ShellExecutor mainExecutor) {
|
||||||
Handler mainHandler) {
|
|
||||||
return new TvPipController(
|
return new TvPipController(
|
||||||
context,
|
context,
|
||||||
tvPipBoundsState,
|
tvPipBoundsState,
|
||||||
tvPipBoundsAlgorithm,
|
tvPipBoundsAlgorithm,
|
||||||
|
tvPipBoundsController,
|
||||||
pipAppOpsListener,
|
pipAppOpsListener,
|
||||||
pipTaskOrganizer,
|
pipTaskOrganizer,
|
||||||
pipTransitionController,
|
pipTransitionController,
|
||||||
@@ -149,14 +146,14 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
|||||||
pipParamsChangedForwarder,
|
pipParamsChangedForwarder,
|
||||||
displayController,
|
displayController,
|
||||||
wmShell,
|
wmShell,
|
||||||
mainExecutor,
|
mainExecutor).mImpl;
|
||||||
mainHandler).mImpl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TvPipController(
|
private TvPipController(
|
||||||
Context context,
|
Context context,
|
||||||
TvPipBoundsState tvPipBoundsState,
|
TvPipBoundsState tvPipBoundsState,
|
||||||
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
|
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
|
||||||
|
TvPipBoundsController tvPipBoundsController,
|
||||||
PipAppOpsListener pipAppOpsListener,
|
PipAppOpsListener pipAppOpsListener,
|
||||||
PipTaskOrganizer pipTaskOrganizer,
|
PipTaskOrganizer pipTaskOrganizer,
|
||||||
PipTransitionController pipTransitionController,
|
PipTransitionController pipTransitionController,
|
||||||
@@ -167,16 +164,16 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
|||||||
PipParamsChangedForwarder pipParamsChangedForwarder,
|
PipParamsChangedForwarder pipParamsChangedForwarder,
|
||||||
DisplayController displayController,
|
DisplayController displayController,
|
||||||
WindowManagerShellWrapper wmShell,
|
WindowManagerShellWrapper wmShell,
|
||||||
ShellExecutor mainExecutor,
|
ShellExecutor mainExecutor) {
|
||||||
Handler mainHandler) {
|
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mMainExecutor = mainExecutor;
|
mMainExecutor = mainExecutor;
|
||||||
mMainHandler = mainHandler;
|
|
||||||
|
|
||||||
mTvPipBoundsState = tvPipBoundsState;
|
mTvPipBoundsState = tvPipBoundsState;
|
||||||
mTvPipBoundsState.setDisplayId(context.getDisplayId());
|
mTvPipBoundsState.setDisplayId(context.getDisplayId());
|
||||||
mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
|
mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
|
||||||
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
|
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
|
||||||
|
mTvPipBoundsController = tvPipBoundsController;
|
||||||
|
mTvPipBoundsController.setListener(this);
|
||||||
|
|
||||||
mPipMediaController = pipMediaController;
|
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
|
* 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 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
|
@Override
|
||||||
public void showPictureInPictureMenu() {
|
public void showPictureInPictureMenu() {
|
||||||
@@ -256,7 +253,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
|||||||
"%s: closeMenu(), state before=%s", TAG, stateToName(mState));
|
"%s: closeMenu(), state before=%s", TAG, stateToName(mState));
|
||||||
}
|
}
|
||||||
setState(STATE_PIP);
|
setState(STATE_PIP);
|
||||||
mTvPipBoundsAlgorithm.keepUnstashedForCurrentKeepClearAreas();
|
|
||||||
updatePinnedStackBounds();
|
updatePinnedStackBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,68 +327,35 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
|||||||
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
|
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
|
||||||
Set<Rect> unrestricted) {
|
Set<Rect> unrestricted) {
|
||||||
if (mTvPipBoundsState.getDisplayId() == displayId) {
|
if (mTvPipBoundsState.getDisplayId() == displayId) {
|
||||||
|
boolean unrestrictedAreasChanged = !Objects.equals(unrestricted,
|
||||||
|
mTvPipBoundsState.getUnrestrictedKeepClearAreas());
|
||||||
mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted);
|
mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted);
|
||||||
updatePinnedStackBounds();
|
updatePinnedStackBounds(mResizeAnimationDuration, unrestrictedAreasChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePinnedStackBounds() {
|
private void updatePinnedStackBounds() {
|
||||||
updatePinnedStackBounds(mResizeAnimationDuration);
|
updatePinnedStackBounds(mResizeAnimationDuration, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the PiP bounds based on the state of the PiP and keep clear areas.
|
* 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) {
|
if (mState == STATE_NO_PIP) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean stayAtAnchorPosition = mTvPipMenuController.isInMoveMode();
|
final boolean stayAtAnchorPosition = mTvPipMenuController.isInMoveMode();
|
||||||
final boolean disallowStashing = mState == STATE_PIP_MENU || stayAtAnchorPosition;
|
final boolean disallowStashing = mState == STATE_PIP_MENU || stayAtAnchorPosition;
|
||||||
final Placement placement = mTvPipBoundsAlgorithm.getTvPipBounds();
|
mTvPipBoundsController.recalculatePipBounds(stayAtAnchorPosition, disallowStashing,
|
||||||
|
animationDuration, immediate);
|
||||||
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. */
|
@Override
|
||||||
private void movePinnedStackTo(Rect bounds) {
|
public void onPipTargetBoundsChange(Rect newTargetBounds, int animationDuration) {
|
||||||
movePinnedStackTo(bounds, mResizeAnimationDuration);
|
mPipTaskOrganizer.scheduleAnimateResizePip(newTargetBounds,
|
||||||
}
|
animationDuration, rect -> mTvPipMenuController.updateExpansionState());
|
||||||
|
mTvPipMenuController.onPipTransitionStarted(newTargetBounds);
|
||||||
/** 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -431,7 +394,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeEduText() {
|
public void closeEduText() {
|
||||||
updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs);
|
updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerSessionListenerForCurrentUser() {
|
private void registerSessionListenerForCurrentUser() {
|
||||||
@@ -473,6 +436,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
|
|||||||
mPipNotificationController.dismiss();
|
mPipNotificationController.dismiss();
|
||||||
mTvPipMenuController.closeMenu();
|
mTvPipMenuController.closeMenu();
|
||||||
mTvPipBoundsState.resetTvPipState();
|
mTvPipBoundsState.resetTvPipState();
|
||||||
|
mTvPipBoundsController.onPipDismissed();
|
||||||
setState(STATE_NO_PIP);
|
setState(STATE_NO_PIP);
|
||||||
mPinnedTaskId = NONEXISTENT_TASK_ID;
|
mPinnedTaskId = NONEXISTENT_TASK_ID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,17 +33,14 @@ import kotlin.math.min
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val DEFAULT_PIP_MARGINS = 48
|
private const val DEFAULT_PIP_MARGINS = 48
|
||||||
private const val DEFAULT_STASH_DURATION = 5000L
|
|
||||||
private const val RELAX_DEPTH = 1
|
private const val RELAX_DEPTH = 1
|
||||||
private const val DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION = 0.15
|
private const val DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION = 0.15
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class calculates an appropriate position for a Picture-In-Picture (PiP) window, taking
|
* This class calculates an appropriate position for a Picture-In-Picture (PiP) window, taking
|
||||||
* into account app defined keep clear areas.
|
* 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.
|
* Result of the positioning algorithm.
|
||||||
*
|
*
|
||||||
@@ -51,17 +48,17 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
* @param anchorBounds The bounds of the PiP anchor position
|
* @param anchorBounds The bounds of the PiP anchor position
|
||||||
* (where the PiP would be placed if there were no keep clear areas)
|
* (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 stashType Where the PiP has been stashed, if at all
|
||||||
* @param unstashDestinationBounds If stashed, the PiP should move to this position after
|
* @param unstashDestinationBounds If stashed, the PiP should move to this position when
|
||||||
* [stashDuration] has passed.
|
* unstashing.
|
||||||
* @param unstashTime If stashed, the time at which the PiP should move
|
* @param triggerStash Whether this placement should trigger the PiP to stash, or extend
|
||||||
* to [unstashDestinationBounds]
|
* the unstash timeout if already stashed.
|
||||||
*/
|
*/
|
||||||
data class Placement(
|
data class Placement(
|
||||||
val bounds: Rect,
|
val bounds: Rect,
|
||||||
val anchorBounds: Rect,
|
val anchorBounds: Rect,
|
||||||
@PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
|
@PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
|
||||||
val unstashDestinationBounds: Rect? = null,
|
val unstashDestinationBounds: Rect? = null,
|
||||||
val unstashTime: Long = 0L
|
val triggerStash: Boolean = false
|
||||||
) {
|
) {
|
||||||
/** Bounds to use if the PiP should not be stashed. */
|
/** Bounds to use if the PiP should not be stashed. */
|
||||||
fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
|
fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
|
||||||
@@ -79,12 +76,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
/** The distance the PiP peeks into the screen when stashed */
|
/** The distance the PiP peeks into the screen when stashed */
|
||||||
var stashOffset = DEFAULT_PIP_MARGINS
|
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 */
|
/** The fraction of screen width/height restricted keep clear areas can move the PiP */
|
||||||
var maxRestrictedDistanceFraction = DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION
|
var maxRestrictedDistanceFraction = DEFAULT_MAX_RESTRICTED_DISTANCE_FRACTION
|
||||||
|
|
||||||
@@ -93,14 +84,10 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
private var transformedMovementBounds = Rect()
|
private var transformedMovementBounds = Rect()
|
||||||
|
|
||||||
private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
|
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
|
/** 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 */
|
* decorations are relevant for calculating intersecting keep clear areas */
|
||||||
private var pipPermanentDecorInsets = Insets.NONE
|
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
|
* 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.
|
* 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
|
* 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
|
* as [Placement.bounds], along with a position to move to when the PiP unstashes
|
||||||
* passed as [Placement.unstashDestinationBounds].
|
* as [Placement.unstashDestinationBounds].
|
||||||
*
|
*
|
||||||
* @param pipSize The size of the PiP window
|
* @param pipSize The size of the PiP window
|
||||||
* @param restrictedAreas The restricted keep clear areas
|
* @param restrictedAreas The restricted keep clear areas
|
||||||
@@ -130,13 +117,11 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
|
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
|
||||||
|
|
||||||
val pipSizeWithAllDecors = addDecors(pipSize)
|
val pipSizeWithAllDecors = addDecors(pipSize)
|
||||||
val pipAnchorBoundsWithAllDecors =
|
val pipAnchorBoundsWithDecors =
|
||||||
getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
|
getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
|
||||||
|
|
||||||
val pipAnchorBoundsWithPermanentDecors =
|
|
||||||
removeTemporaryDecorsTransformed(pipAnchorBoundsWithAllDecors)
|
|
||||||
val result = calculatePipPositionTransformed(
|
val result = calculatePipPositionTransformed(
|
||||||
pipAnchorBoundsWithPermanentDecors,
|
pipAnchorBoundsWithDecors,
|
||||||
transformedRestrictedAreas,
|
transformedRestrictedAreas,
|
||||||
transformedUnrestrictedAreas
|
transformedUnrestrictedAreas
|
||||||
)
|
)
|
||||||
@@ -152,7 +137,7 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
anchorBounds,
|
anchorBounds,
|
||||||
getStashType(pipBounds, unstashedDestBounds),
|
getStashType(pipBounds, unstashedDestBounds),
|
||||||
unstashedDestBounds,
|
unstashedDestBounds,
|
||||||
result.unstashTime
|
result.triggerStash
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,26 +198,13 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
!lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition)
|
!lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition)
|
||||||
lastAreasOverlappingUnstashPosition = 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)
|
val stashedBounds = getNearbyStashedPosition(unstashBounds, keepClearAreas)
|
||||||
return Placement(
|
return Placement(
|
||||||
stashedBounds,
|
stashedBounds,
|
||||||
pipAnchorBounds,
|
pipAnchorBounds,
|
||||||
getStashType(stashedBounds, unstashBounds),
|
getStashType(stashedBounds, unstashBounds),
|
||||||
unstashBounds,
|
unstashBounds,
|
||||||
unstashTime
|
areasOverlappingUnstashPositionChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,14 +410,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
} ?: bounds
|
} ?: 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.
|
* Updates the size of the screen.
|
||||||
*
|
*
|
||||||
@@ -492,10 +456,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
pipPermanentDecorInsets = insets
|
pipPermanentDecorInsets = insets
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPipTemporaryDecorInsets(insets: Insets) {
|
|
||||||
pipTemporaryDecorInsets = insets
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param open Whether this event marks the opening of an occupied segment
|
* @param open Whether this event marks the opening of an occupied segment
|
||||||
* @param pos The coordinate of this event
|
* @param pos The coordinate of this event
|
||||||
@@ -790,7 +750,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
private fun addDecors(size: Size): Size {
|
private fun addDecors(size: Size): Size {
|
||||||
val bounds = Rect(0, 0, size.width, size.height)
|
val bounds = Rect(0, 0, size.width, size.height)
|
||||||
bounds.inset(pipPermanentDecorInsets)
|
bounds.inset(pipPermanentDecorInsets)
|
||||||
bounds.inset(pipTemporaryDecorInsets)
|
|
||||||
|
|
||||||
return Size(bounds.width(), bounds.height())
|
return Size(bounds.width(), bounds.height())
|
||||||
}
|
}
|
||||||
@@ -805,19 +764,6 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
|
|||||||
return bounds
|
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.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.intersectsX(other: Rect) = right >= other.left && left <= other.right
|
||||||
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
|
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
|||||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||||
"%s: showMovementMenuOnly()", TAG);
|
"%s: showMovementMenuOnly()", TAG);
|
||||||
}
|
}
|
||||||
mInMoveMode = true;
|
setInMoveMode(true);
|
||||||
mCloseAfterExitMoveMenu = true;
|
mCloseAfterExitMoveMenu = true;
|
||||||
showMenuInternal();
|
showMenuInternal();
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
|
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
|
||||||
}
|
}
|
||||||
mInMoveMode = false;
|
setInMoveMode(false);
|
||||||
mCloseAfterExitMoveMenu = false;
|
mCloseAfterExitMoveMenu = false;
|
||||||
showMenuInternal();
|
showMenuInternal();
|
||||||
}
|
}
|
||||||
@@ -293,6 +293,17 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
|||||||
return mInMoveMode;
|
return mInMoveMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setInMoveMode(boolean moveMode) {
|
||||||
|
if (mInMoveMode == moveMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mInMoveMode = moveMode;
|
||||||
|
if (mDelegate != null) {
|
||||||
|
mDelegate.onInMoveModeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnterMoveMode() {
|
public void onEnterMoveMode() {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -300,7 +311,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
|||||||
"%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
|
"%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
|
||||||
mCloseAfterExitMoveMenu);
|
mCloseAfterExitMoveMenu);
|
||||||
}
|
}
|
||||||
mInMoveMode = true;
|
setInMoveMode(true);
|
||||||
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
|
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,13 +323,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
|
|||||||
mCloseAfterExitMoveMenu);
|
mCloseAfterExitMoveMenu);
|
||||||
}
|
}
|
||||||
if (mCloseAfterExitMoveMenu) {
|
if (mCloseAfterExitMoveMenu) {
|
||||||
mInMoveMode = false;
|
setInMoveMode(false);
|
||||||
mCloseAfterExitMoveMenu = false;
|
mCloseAfterExitMoveMenu = false;
|
||||||
closeMenu();
|
closeMenu();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (mInMoveMode) {
|
if (mInMoveMode) {
|
||||||
mInMoveMode = false;
|
setInMoveMode(false);
|
||||||
mPipMenuView.showButtonsMenu();
|
mPipMenuView.showButtonsMenu();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ android_test {
|
|||||||
"androidx.test.ext.junit",
|
"androidx.test.ext.junit",
|
||||||
"androidx.dynamicanimation_dynamicanimation",
|
"androidx.dynamicanimation_dynamicanimation",
|
||||||
"dagger2",
|
"dagger2",
|
||||||
|
"frameworks-base-testutils",
|
||||||
"kotlinx-coroutines-android",
|
"kotlinx-coroutines-android",
|
||||||
"kotlinx-coroutines-core",
|
"kotlinx-coroutines-core",
|
||||||
"mockito-target-extended-minus-junit4",
|
"mockito-target-extended-minus-junit4",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,9 @@ import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import junit.framework.Assert.assertEquals
|
import junit.framework.Assert.assertEquals
|
||||||
|
import junit.framework.Assert.assertFalse
|
||||||
import junit.framework.Assert.assertNull
|
import junit.framework.Assert.assertNull
|
||||||
|
import junit.framework.Assert.assertTrue
|
||||||
|
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
class TvPipKeepClearAlgorithmTest {
|
class TvPipKeepClearAlgorithmTest {
|
||||||
@@ -46,7 +48,6 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
private lateinit var pipSize: Size
|
private lateinit var pipSize: Size
|
||||||
private lateinit var movementBounds: Rect
|
private lateinit var movementBounds: Rect
|
||||||
private lateinit var algorithm: TvPipKeepClearAlgorithm
|
private lateinit var algorithm: TvPipKeepClearAlgorithm
|
||||||
private var currentTime = 0L
|
|
||||||
private var restrictedAreas = mutableSetOf<Rect>()
|
private var restrictedAreas = mutableSetOf<Rect>()
|
||||||
private var unrestrictedAreas = mutableSetOf<Rect>()
|
private var unrestrictedAreas = mutableSetOf<Rect>()
|
||||||
private var gravity: Int = 0
|
private var gravity: Int = 0
|
||||||
@@ -58,16 +59,14 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
|
|
||||||
restrictedAreas.clear()
|
restrictedAreas.clear()
|
||||||
unrestrictedAreas.clear()
|
unrestrictedAreas.clear()
|
||||||
currentTime = 0L
|
|
||||||
pipSize = DEFAULT_PIP_SIZE
|
pipSize = DEFAULT_PIP_SIZE
|
||||||
gravity = Gravity.BOTTOM or Gravity.RIGHT
|
gravity = Gravity.BOTTOM or Gravity.RIGHT
|
||||||
|
|
||||||
algorithm = TvPipKeepClearAlgorithm({ currentTime })
|
algorithm = TvPipKeepClearAlgorithm()
|
||||||
algorithm.setScreenSize(SCREEN_SIZE)
|
algorithm.setScreenSize(SCREEN_SIZE)
|
||||||
algorithm.setMovementBounds(movementBounds)
|
algorithm.setMovementBounds(movementBounds)
|
||||||
algorithm.pipAreaPadding = PADDING
|
algorithm.pipAreaPadding = PADDING
|
||||||
algorithm.stashOffset = STASH_OFFSET
|
algorithm.stashOffset = STASH_OFFSET
|
||||||
algorithm.stashDuration = 5000L
|
|
||||||
algorithm.setGravity(gravity)
|
algorithm.setGravity(gravity)
|
||||||
algorithm.maxRestrictedDistanceFraction = 0.3
|
algorithm.maxRestrictedDistanceFraction = 0.3
|
||||||
}
|
}
|
||||||
@@ -265,7 +264,7 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
assertEquals(expectedBounds, placement.bounds)
|
assertEquals(expectedBounds, placement.bounds)
|
||||||
assertEquals(STASH_TYPE_BOTTOM, placement.stashType)
|
assertEquals(STASH_TYPE_BOTTOM, placement.stashType)
|
||||||
assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds)
|
assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds)
|
||||||
assertEquals(algorithm.stashDuration, placement.unstashTime)
|
assertTrue(placement.triggerStash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -305,7 +304,7 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
assertEquals(expectedBounds, placement.bounds)
|
assertEquals(expectedBounds, placement.bounds)
|
||||||
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
||||||
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
||||||
assertEquals(algorithm.stashDuration, placement.unstashTime)
|
assertTrue(placement.triggerStash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -352,9 +351,7 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
assertEquals(expectedBounds, placement.bounds)
|
assertEquals(expectedBounds, placement.bounds)
|
||||||
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
||||||
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
||||||
assertEquals(algorithm.stashDuration, placement.unstashTime)
|
assertTrue(placement.triggerStash)
|
||||||
|
|
||||||
currentTime += 1000
|
|
||||||
|
|
||||||
restrictedAreas.remove(sideBar)
|
restrictedAreas.remove(sideBar)
|
||||||
placement = getActualPlacement()
|
placement = getActualPlacement()
|
||||||
@@ -363,7 +360,7 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_Stashed_UnstashBoundsStaysObstructed_UnstashesAfterTimeout() {
|
fun test_Stashed_UnstashBoundsStaysObstructed_DoesNotTriggerStash() {
|
||||||
gravity = Gravity.BOTTOM or Gravity.RIGHT
|
gravity = Gravity.BOTTOM or Gravity.RIGHT
|
||||||
|
|
||||||
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
|
val bottomBar = makeBottomBar(BOTTOM_SHEET_HEIGHT)
|
||||||
@@ -384,13 +381,13 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
assertEquals(expectedBounds, placement.bounds)
|
assertEquals(expectedBounds, placement.bounds)
|
||||||
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
||||||
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
||||||
assertEquals(algorithm.stashDuration, placement.unstashTime)
|
assertTrue(placement.triggerStash)
|
||||||
|
|
||||||
currentTime += algorithm.stashDuration
|
|
||||||
|
|
||||||
placement = getActualPlacement()
|
placement = getActualPlacement()
|
||||||
assertEquals(expectedUnstashBounds, placement.bounds)
|
assertEquals(expectedBounds, placement.bounds)
|
||||||
assertNotStashed(placement)
|
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
||||||
|
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
||||||
|
assertFalse(placement.triggerStash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -415,9 +412,7 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
assertEquals(expectedBounds, placement.bounds)
|
assertEquals(expectedBounds, placement.bounds)
|
||||||
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
||||||
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
||||||
assertEquals(algorithm.stashDuration, placement.unstashTime)
|
assertTrue(placement.triggerStash)
|
||||||
|
|
||||||
currentTime += 1000
|
|
||||||
|
|
||||||
val newObstruction = Rect(
|
val newObstruction = Rect(
|
||||||
0,
|
0,
|
||||||
@@ -431,7 +426,7 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
assertEquals(expectedBounds, placement.bounds)
|
assertEquals(expectedBounds, placement.bounds)
|
||||||
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
assertEquals(STASH_TYPE_RIGHT, placement.stashType)
|
||||||
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
assertEquals(expectedUnstashBounds, placement.unstashDestinationBounds)
|
||||||
assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime)
|
assertTrue(placement.triggerStash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -458,21 +453,9 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_PipInsets() {
|
fun test_PipInsets() {
|
||||||
val permInsets = Insets.of(-1, -2, -3, -4)
|
val insets = Insets.of(-1, -2, -3, -4)
|
||||||
algorithm.setPipPermanentDecorInsets(permInsets)
|
algorithm.setPipPermanentDecorInsets(insets)
|
||||||
testInsetsForAllPositions(permInsets)
|
|
||||||
|
|
||||||
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
|
gravity = Gravity.BOTTOM or Gravity.RIGHT
|
||||||
testAnchorPositionWithInsets(insets)
|
testAnchorPositionWithInsets(insets)
|
||||||
|
|
||||||
@@ -546,6 +529,6 @@ class TvPipKeepClearAlgorithmTest {
|
|||||||
private fun assertNotStashed(actual: Placement) {
|
private fun assertNotStashed(actual: Placement) {
|
||||||
assertEquals(STASH_TYPE_NONE, actual.stashType)
|
assertEquals(STASH_TYPE_NONE, actual.stashType)
|
||||||
assertNull(actual.unstashDestinationBounds)
|
assertNull(actual.unstashDestinationBounds)
|
||||||
assertEquals(0L, actual.unstashTime)
|
assertFalse(actual.triggerStash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user