Merge "Move PiP in response to keep clear areas changed events." into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
abcf028aa9
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user