diff options
author | 2022-06-21 20:00:45 +0000 | |
---|---|---|
committer | 2022-09-02 00:23:34 +0000 | |
commit | 835dd73120fa3a385e9931b4f2534f78e90c890f (patch) | |
tree | 2bd5ab3d4c45317d255b3e8b31b4aa637fecb825 | |
parent | c08941510012e3675aafc3e2e84be90f94b5fc0e (diff) |
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
17 files changed, 321 insertions, 136 deletions
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 1dac9caba01e..e24934ba2b0a 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -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> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 31596f304cb9..1564f8f4073f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -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 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl index e03421dd58ac..4def15db2f52 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl @@ -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 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index 7397e5273753..cd61dbb5b7d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -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. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java new file mode 100644 index 000000000000..e3495e100c62 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithm.java @@ -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; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java index 1a4be3b41911..c6b5ce93fd35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java @@ -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); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java new file mode 100644 index 000000000000..6dd02e46d657 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java @@ -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; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index ac3407dd1ca1..6c9a6b64c864 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -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]; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java deleted file mode 100644 index 78084fafe197..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java +++ /dev/null @@ -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; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index a2eadcdf6210..ce34d2f9547d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -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); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 9ba51661aa5f..61ac49835185 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -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) } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java index 0059846c6055..262e4290ef44 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java @@ -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)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 579638d28311..90880772b25d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -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, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java index e0f7e35f8d02..4d7e9e450ceb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java @@ -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); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index eb5726bebb74..1b5091f58f26 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -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); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index dd10aa7752f5..dba037db72eb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -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); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index ecefd89d8778..474d6aaf4623 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -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); |