Move PiP in response to keep clear areas changed events.

PiP window will gravitate towards bottom of the screen if it's in the bottom half of it.
PiP window will gravitate towards left/right edge of screen, whichever is closer.
PiP window will avoid occluding any reported keep clear areas.

Test: manually, existing tests pass
Bug: 183746978

Change-Id: Idb481fee7ead67734c5de0d833b281ca2a2aaaa8
This commit is contained in:
Mateusz Cicheński
2022-06-21 20:00:45 +00:00
parent c089415100
commit 835dd73120
17 changed files with 321 additions and 136 deletions

View File

@@ -81,6 +81,9 @@
<!-- The width and height of the background for custom action in PiP menu. -->
<dimen name="pip_custom_close_bg_size">32dp</dimen>
<!-- Extra padding between picture-in-picture windows and any registered keep clear areas. -->
<dimen name="pip_keep_clear_areas_padding">16dp</dimen>
<dimen name="dismiss_target_x_size">24dp</dimen>
<dimen name="floating_dismiss_bottom_margin">50dp</dimen>

View File

@@ -72,9 +72,9 @@ import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
@@ -320,7 +320,7 @@ public abstract class WMShellModule {
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithm pipKeepClearAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
@@ -357,15 +357,17 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
return new PipKeepClearAlgorithm();
static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
return new PhonePipKeepClearAlgorithm(context);
}
@WMSingleton
@Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
pipKeepClearAlgorithm);
}
// Handler is used by Icon.loadDrawableAsync

View File

@@ -37,12 +37,13 @@ interface IPip {
* @param activityInfo ActivityInfo tied to the Activity
* @param pictureInPictureParams PictureInPictureParams tied to the Activity
* @param launcherRotation Launcher rotation to calculate the PiP destination bounds
* @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds
* @param hotseatKeepClearArea Bounds of Hotseat to avoid used to calculate PiP destination
bounds
* @return destination bounds the PiP window should land into
*/
Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
in PictureInPictureParams pictureInPictureParams,
int launcherRotation, int shelfHeight) = 1;
int launcherRotation, in Rect hotseatKeepClearArea) = 1;
/**
* Notifies the swiping Activity to PiP onto home transition is finished

View File

@@ -47,6 +47,7 @@ public class PipBoundsAlgorithm {
private final @NonNull PipBoundsState mPipBoundsState;
private final PipSnapAlgorithm mSnapAlgorithm;
private final PipKeepClearAlgorithm mPipKeepClearAlgorithm;
private float mDefaultSizePercent;
private float mMinAspectRatioForMinSize;
@@ -60,9 +61,11 @@ public class PipBoundsAlgorithm {
protected Point mScreenEdgeInsets;
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
@NonNull PipSnapAlgorithm pipSnapAlgorithm,
@NonNull PipKeepClearAlgorithm pipKeepClearAlgorithm) {
mPipBoundsState = pipBoundsState;
mSnapAlgorithm = pipSnapAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -129,8 +132,21 @@ public class PipBoundsAlgorithm {
return getDefaultBounds(INVALID_SNAP_FRACTION, null /* size */);
}
/** Returns the destination bounds to place the PIP window on entry. */
/**
* Returns the destination bounds to place the PIP window on entry.
* If there are any keep clear areas registered, the position will try to avoid occluding them.
*/
public Rect getEntryDestinationBounds() {
Rect entryBounds = getEntryDestinationBoundsIgnoringKeepClearAreas();
Rect insets = new Rect();
getInsetBounds(insets);
return mPipKeepClearAlgorithm.findUnoccludedPosition(entryBounds,
mPipBoundsState.getRestrictedKeepClearAreas(),
mPipBoundsState.getUnrestrictedKeepClearAreas(), insets);
}
/** Returns the destination bounds to place the PIP window on entry. */
public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
final PipBoundsState.PipReentryState reentryState = mPipBoundsState.getReentryState();
final Rect destinationBounds = reentryState != null
@@ -138,9 +154,10 @@ public class PipBoundsAlgorithm {
: getDefaultBounds();
final boolean useCurrentSize = reentryState != null && reentryState.getSize() != null;
return transformBoundsToAspectRatioIfValid(destinationBounds,
Rect aspectRatioBounds = transformBoundsToAspectRatioIfValid(destinationBounds,
mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
useCurrentSize);
return aspectRatioBounds;
}
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */

View File

@@ -0,0 +1,53 @@
/*
* 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;
import android.graphics.Rect;
import java.util.Set;
/**
* Interface for interacting with keep clear algorithm used to move PiP window out of the way of
* keep clear areas.
*/
public interface PipKeepClearAlgorithm {
/**
* Adjust the position of picture in picture window based on the registered keep clear areas.
* @param pipBoundsState state of the PiP to use for the calculations
* @param pipBoundsAlgorithm algorithm implementation used to get the entry destination bounds
* @return
*/
default Rect adjust(PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm) {
return pipBoundsState.getBounds();
}
/**
* Calculate the bounds so that none of the keep clear areas are occluded, while the bounds stay
* within the allowed bounds. If such position is not feasible, return original bounds.
* @param defaultBounds initial bounds used in the calculation
* @param restrictedKeepClearAreas registered restricted keep clear areas
* @param unrestrictedKeepClearAreas registered unrestricted keep clear areas
* @param allowedBounds bounds that define the allowed space for the output, result will always
* be inside those bounds
* @return bounds that don't cover any of the keep clear areas and are within allowed bounds
*/
default Rect findUnoccludedPosition(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
Set<Rect> unrestrictedKeepClearAreas, Rect allowedBounds) {
return defaultBounds;
}
}

View File

@@ -88,6 +88,11 @@ public class PipTransitionState {
return isInPip(mState);
}
/** Returns true if activity has fully entered PiP mode. */
public boolean hasEnteredPip() {
return hasEnteredPip(mState);
}
public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
mInSwipePipToHomeTransition = inSwipePipToHomeTransition;
}
@@ -120,6 +125,11 @@ public class PipTransitionState {
return state >= TASK_APPEARED && state != EXITING_PIP;
}
/** Returns true if activity has fully entered PiP mode. */
public static boolean hasEnteredPip(@TransitionState int state) {
return state == ENTERED_PIP;
}
public interface OnPipTransitionStateChangedListener {
void onPipTransitionStateChanged(@TransitionState int oldState,
@TransitionState int newState);

View File

@@ -0,0 +1,147 @@
/*
* 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.phone;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.ArraySet;
import android.view.Gravity;
import com.android.wm.shell.R;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import java.util.Set;
/**
* Calculates the adjusted position that does not occlude keep clear areas.
*/
public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithm {
protected int mKeepClearAreasPadding;
public PhonePipKeepClearAlgorithm(Context context) {
reloadResources(context);
}
private void reloadResources(Context context) {
final Resources res = context.getResources();
mKeepClearAreasPadding = res.getDimensionPixelSize(R.dimen.pip_keep_clear_areas_padding);
}
/**
* Adjusts the current position of PiP to avoid occluding keep clear areas. This will push PiP
* towards the closest edge and then apply calculations to avoid occluding keep clear areas.
*/
public Rect adjust(PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm) {
Rect startingBounds = pipBoundsState.getBounds().isEmpty()
? pipBoundsAlgorithm.getEntryDestinationBoundsIgnoringKeepClearAreas()
: pipBoundsState.getBounds();
float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
int verticalGravity;
int horizontalGravity;
if (snapFraction < 1.5f || snapFraction >= 3.5f) {
verticalGravity = Gravity.NO_GRAVITY;
} else {
verticalGravity = Gravity.BOTTOM;
}
if (snapFraction >= 0.5f && snapFraction < 2.5f) {
horizontalGravity = Gravity.RIGHT;
} else {
horizontalGravity = Gravity.LEFT;
}
// push the bounds based on the gravity
Rect insets = new Rect();
pipBoundsAlgorithm.getInsetBounds(insets);
if (pipBoundsState.isImeShowing()) {
insets.bottom -= pipBoundsState.getImeHeight();
}
Rect pushedBounds = new Rect(startingBounds);
if (verticalGravity == Gravity.BOTTOM) {
pushedBounds.offsetTo(pushedBounds.left,
insets.bottom - pushedBounds.height());
}
if (horizontalGravity == Gravity.RIGHT) {
pushedBounds.offsetTo(insets.right - pushedBounds.width(), pushedBounds.top);
} else {
pushedBounds.offsetTo(insets.left, pushedBounds.top);
}
return findUnoccludedPosition(pushedBounds, pipBoundsState.getRestrictedKeepClearAreas(),
pipBoundsState.getUnrestrictedKeepClearAreas(), insets);
}
/** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
public Rect findUnoccludedPosition(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
Set<Rect> unrestrictedKeepClearAreas, Rect allowedBounds) {
if (restrictedKeepClearAreas.isEmpty() && unrestrictedKeepClearAreas.isEmpty()) {
return defaultBounds;
}
Set<Rect> keepClearAreas = new ArraySet<>();
if (!restrictedKeepClearAreas.isEmpty()) {
keepClearAreas.addAll(restrictedKeepClearAreas);
}
if (!unrestrictedKeepClearAreas.isEmpty()) {
keepClearAreas.addAll(unrestrictedKeepClearAreas);
}
Rect outBounds = new Rect(defaultBounds);
for (Rect r : keepClearAreas) {
Rect tmpRect = new Rect(r);
// add extra padding to the keep clear area
tmpRect.inset(-mKeepClearAreasPadding, -mKeepClearAreasPadding);
if (Rect.intersects(r, outBounds)) {
if (tryOffsetUp(outBounds, tmpRect, allowedBounds)) continue;
if (tryOffsetLeft(outBounds, tmpRect, allowedBounds)) continue;
if (tryOffsetDown(outBounds, tmpRect, allowedBounds)) continue;
if (tryOffsetRight(outBounds, tmpRect, allowedBounds)) continue;
}
}
return outBounds;
}
private static boolean tryOffsetLeft(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds) {
return tryOffset(rectToMove, rectToAvoid, allowedBounds,
rectToAvoid.left - rectToMove.right, 0);
}
private static boolean tryOffsetRight(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds) {
return tryOffset(rectToMove, rectToAvoid, allowedBounds,
rectToAvoid.right - rectToMove.left, 0);
}
private static boolean tryOffsetUp(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds) {
return tryOffset(rectToMove, rectToAvoid, allowedBounds,
0, rectToAvoid.top - rectToMove.bottom);
}
private static boolean tryOffsetDown(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds) {
return tryOffset(rectToMove, rectToAvoid, allowedBounds,
0, rectToAvoid.bottom - rectToMove.top);
}
private static boolean tryOffset(Rect rectToMove, Rect rectToAvoid, Rect allowedBounds,
int dx, int dy) {
Rect tmp = new Rect(rectToMove);
tmp.offset(dx, dy);
if (!Rect.intersects(rectToAvoid, tmp) && allowedBounds.contains(tmp)) {
rectToMove.offsetTo(tmp.left, tmp.top);
return true;
}
return false;
}
}

View File

@@ -43,6 +43,7 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Pair;
@@ -79,6 +80,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -110,6 +112,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
UserChangeListener {
private static final String TAG = "PipController";
private boolean mEnablePipKeepClearAlgorithm =
SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", false);
@VisibleForTesting
void setEnablePipKeepClearAlgorithm(boolean value) {
mEnablePipKeepClearAlgorithm = value;
}
private Context mContext;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
@@ -262,7 +272,17 @@ public class PipController implements PipTransitionController.PipTransitionCallb
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
if (mPipBoundsState.getDisplayId() == displayId) {
mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
if (mEnablePipKeepClearAlgorithm) {
mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
// only move if already in pip, other transitions account for keep clear
// areas
if (mPipTransitionState.hasEnteredPip()) {
Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
mPipBoundsAlgorithm);
mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
mEnterAnimationDuration, null);
}
}
}
}
};
@@ -759,8 +779,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams,
int launcherRotation, int shelfHeight) {
setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
int launcherRotation, Rect hotseatKeepClearArea) {
if (mEnablePipKeepClearAlgorithm) {
// pre-emptively add the keep clear area for Hotseat, so that it is taken into account
// when calculating the entry destination bounds of PiP window
mPipBoundsState.getRestrictedKeepClearAreas().add(hotseatKeepClearArea);
} else {
int shelfHeight = hotseatKeepClearArea.height();
setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
}
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
@@ -1059,12 +1087,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams, int launcherRotation,
int shelfHeight) {
Rect keepClearArea) {
Rect[] result = new Rect[1];
executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
(controller) -> {
result[0] = controller.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams, launcherRotation, shelfHeight);
pictureInPictureParams, launcherRotation, keepClearArea);
}, true /* blocking */);
return result[0];
}

View File

@@ -1,97 +0,0 @@
/*
* 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.phone;
import android.graphics.Rect;
import android.util.ArraySet;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import java.util.Set;
/**
* Calculates the adjusted position that does not occlude keep clear areas.
*/
public class PipKeepClearAlgorithm {
/**
* Adjusts the current position of PiP to avoid occluding keep clear areas. If the user has
* moved PiP manually, the unmodified current position will be returned instead.
*/
public Rect adjust(PipBoundsState boundsState, PipBoundsAlgorithm boundsAlgorithm) {
if (boundsState.hasUserResizedPip()) {
return boundsState.getBounds();
}
return adjust(boundsAlgorithm.getEntryDestinationBounds(),
boundsState.getRestrictedKeepClearAreas(),
boundsState.getUnrestrictedKeepClearAreas(), boundsState.getDisplayBounds());
}
/** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
Set<Rect> unrestrictedKeepClearAreas, Rect displayBounds) {
if (restrictedKeepClearAreas.isEmpty()) {
return defaultBounds;
}
Set<Rect> keepClearAreas = new ArraySet<>();
if (!restrictedKeepClearAreas.isEmpty()) {
keepClearAreas.addAll(restrictedKeepClearAreas);
}
Rect outBounds = new Rect(defaultBounds);
for (Rect r : keepClearAreas) {
if (Rect.intersects(r, outBounds)) {
if (tryOffsetUp(outBounds, r, displayBounds)) continue;
if (tryOffsetLeft(outBounds, r, displayBounds)) continue;
if (tryOffsetDown(outBounds, r, displayBounds)) continue;
if (tryOffsetRight(outBounds, r, displayBounds)) continue;
}
}
return outBounds;
}
private boolean tryOffsetLeft(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
return tryOffset(rectToMove, rectToAvoid, displayBounds,
rectToAvoid.left - rectToMove.right, 0);
}
private boolean tryOffsetRight(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
return tryOffset(rectToMove, rectToAvoid, displayBounds,
rectToAvoid.right - rectToMove.left, 0);
}
private boolean tryOffsetUp(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
return tryOffset(rectToMove, rectToAvoid, displayBounds,
0, rectToAvoid.top - rectToMove.bottom);
}
private boolean tryOffsetDown(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
return tryOffset(rectToMove, rectToAvoid, displayBounds,
0, rectToAvoid.bottom - rectToMove.top);
}
private boolean tryOffset(Rect rectToMove, Rect rectToAvoid, Rect displayBounds,
int dx, int dy) {
Rect tmp = new Rect(rectToMove);
tmp.offset(dx, dy);
if (!Rect.intersects(rectToAvoid, tmp) && displayBounds.contains(tmp)) {
rectToMove.offsetTo(tmp.left, tmp.top);
return true;
}
return false;
}
}

View File

@@ -39,6 +39,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -63,7 +64,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
public TvPipBoundsAlgorithm(Context context,
@NonNull TvPipBoundsState tvPipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm) {
super(context, tvPipBoundsState, pipSnapAlgorithm);
super(context, tvPipBoundsState, pipSnapAlgorithm,
new PipKeepClearAlgorithm() {});
this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);

View File

@@ -132,7 +132,7 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
testSpec.assertLayers {
val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtMost(previous.visibleRegion.region)
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
}
}
}

View File

@@ -64,7 +64,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
initializeMockResources();
mPipBoundsState = new PipBoundsState(mContext);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm());
new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {});
mPipBoundsState.setDisplayLayout(
new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true));

View File

@@ -98,7 +98,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mPipBoundsState = new PipBoundsState(mContext);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm());
new PipSnapAlgorithm(), new PipKeepClearAlgorithm() {});
mMainExecutor = new TestShellExecutor();
mPipTaskOrganizer = new PipTaskOrganizer(mContext,
mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,

View File

@@ -34,61 +34,61 @@ import org.junit.runner.RunWith;
import java.util.Set;
/**
* Unit tests against {@link PipKeepClearAlgorithm}.
* Unit tests against {@link PhonePipKeepClearAlgorithm}.
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class PipKeepClearAlgorithmTest extends ShellTestCase {
public class PhonePipKeepClearAlgorithmTest extends ShellTestCase {
private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
private PhonePipKeepClearAlgorithm mPipKeepClearAlgorithm;
private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 1000, 1000);
@Before
public void setUp() throws Exception {
mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
mPipKeepClearAlgorithm = new PhonePipKeepClearAlgorithm(mContext);
}
@Test
public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
public void findUnoccludedPosition_withCollidingRestrictedKeepClearArea_movesBounds() {
final Rect inBounds = new Rect(0, 0, 100, 100);
final Rect keepClearRect = new Rect(50, 50, 150, 150);
final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
Set.of(), DISPLAY_BOUNDS);
final Rect outBounds = mPipKeepClearAlgorithm.findUnoccludedPosition(inBounds,
Set.of(keepClearRect), Set.of(), DISPLAY_BOUNDS);
assertFalse(outBounds.contains(keepClearRect));
}
@Test
public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
public void findUnoccludedPosition_withNonCollidingRestrictedKeepClearArea_boundsUnchanged() {
final Rect inBounds = new Rect(0, 0, 100, 100);
final Rect keepClearRect = new Rect(100, 100, 150, 150);
final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
Set.of(), DISPLAY_BOUNDS);
final Rect outBounds = mPipKeepClearAlgorithm.findUnoccludedPosition(inBounds,
Set.of(keepClearRect), Set.of(), DISPLAY_BOUNDS);
assertEquals(inBounds, outBounds);
}
@Test
public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
public void findUnoccludedPosition_withCollidingUnrestrictedKeepClearArea_moveBounds() {
// TODO(b/183746978): update this test to accommodate for the updated algorithm
final Rect inBounds = new Rect(0, 0, 100, 100);
final Rect keepClearRect = new Rect(50, 50, 150, 150);
final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
final Rect outBounds = mPipKeepClearAlgorithm.findUnoccludedPosition(inBounds, Set.of(),
Set.of(keepClearRect), DISPLAY_BOUNDS);
assertEquals(inBounds, outBounds);
assertFalse(outBounds.contains(keepClearRect));
}
@Test
public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
public void findUnoccludedPosition_withNonCollidingUnrestrictedKeepClearArea_boundsUnchanged() {
final Rect inBounds = new Rect(0, 0, 100, 100);
final Rect keepClearRect = new Rect(100, 100, 150, 150);
final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
final Rect outBounds = mPipKeepClearAlgorithm.findUnoccludedPosition(inBounds, Set.of(),
Set.of(keepClearRect), DISPLAY_BOUNDS);
assertEquals(inBounds, outBounds);

View File

@@ -64,6 +64,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -85,7 +86,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@Mock private PhonePipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
@@ -267,7 +268,20 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
public void onKeepClearAreasChanged_updatesPipBoundsState() {
public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() {
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
displayId, Set.of(keepClearArea), Set.of());
verify(mMockPipBoundsState, never()).setKeepClearAreas(Mockito.anySet(), Mockito.anySet());
}
@Test
public void onKeepClearAreasChanged_featureEnabled_updatesPipBoundsState() {
mPipController.setEnablePipKeepClearAlgorithm(true);
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);

View File

@@ -36,6 +36,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -87,8 +88,10 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mPipBoundsState = new PipBoundsState(mContext);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
final PipKeepClearAlgorithm pipKeepClearAlgorithm =
new PipKeepClearAlgorithm() {};
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm);
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm);
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);

View File

@@ -34,6 +34,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -104,7 +105,8 @@ public class PipTouchHandlerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mPipBoundsState = new PipBoundsState(mContext);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithm() {});
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);