blob: ed8dc7ded65483e072b4a6938c3a75dbfa7d981a [file] [log] [blame]
/*
* 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.os.SystemProperties;
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.PipKeepClearAlgorithmInterface;
import java.util.Set;
/**
* Calculates the adjusted position that does not occlude keep clear areas.
*/
public class PhonePipKeepClearAlgorithm implements PipKeepClearAlgorithmInterface {
private boolean mKeepClearAreaGravityEnabled =
SystemProperties.getBoolean(
"persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false);
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();
Rect insets = new Rect();
pipBoundsAlgorithm.getInsetBounds(insets);
if (pipBoundsState.isImeShowing()) {
insets.bottom -= pipBoundsState.getImeHeight();
}
Rect pipBounds = new Rect(startingBounds);
// move PiP towards corner if user hasn't moved it manually or the flag is on
if (mKeepClearAreaGravityEnabled
|| (!pipBoundsState.hasUserMovedPip() && !pipBoundsState.hasUserResizedPip())) {
float snapFraction = pipBoundsAlgorithm.getSnapFraction(startingBounds);
int verticalGravity = Gravity.BOTTOM;
int horizontalGravity;
if (snapFraction >= 0.5f && snapFraction < 2.5f) {
horizontalGravity = Gravity.RIGHT;
} else {
horizontalGravity = Gravity.LEFT;
}
if (verticalGravity == Gravity.BOTTOM) {
pipBounds.offsetTo(pipBounds.left,
insets.bottom - pipBounds.height());
}
if (horizontalGravity == Gravity.RIGHT) {
pipBounds.offsetTo(insets.right - pipBounds.width(), pipBounds.top);
} else {
pipBounds.offsetTo(insets.left, pipBounds.top);
}
}
return findUnoccludedPosition(pipBounds, 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;
}
}