Letterbox Reachability: Add a central stop for repositioning.
Also, remove a gap between a device edge and an app window.
Bug: 197549949
Test: atest WmTests:SizeCompatTests and manual
Change-Id: I5fb3e027ccb64e705d13b51ed66219d57a68c061
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ed80013..243d8db 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4903,16 +4903,15 @@
device orientation. -->
<bool name="config_letterboxIsReachabilityEnabled">false</bool>
- <!-- Default horizonal position of a center of the letterboxed app window when reachability is
- enabled and an app is fullscreen in landscape device orientation.
- 0 corresponds to the left side of the screen and 1 to the right side. If given value < 0.0
- or > 1, it is ignored and right positionis used (1.0). The position multiplier is changed
- to a symmetrical value computed as (1 - current multiplier) after each double tap in the
- letterbox area. -->
- <item name="config_letterboxDefaultPositionMultiplierForReachability"
- format="float" type="dimen">
- 0.9
- </item>
+ <!-- Default horizonal position of the letterboxed app window when reachability is
+ enabled and an app is fullscreen in landscape device orientation. When reachability is
+ enabled, the position can change between left, center and right. This config defines the
+ default one:
+ - Option 0 - Left.
+ - Option 1 - Center.
+ - Option 2 - Right.
+ If given value is outside of this range, the option 1 (center) is assummed. -->
+ <integer name="config_letterboxDefaultPositionForReachability">1</integer>
<!-- If true, hide the display cutout with display area -->
<bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0c0db2c..b917912 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4259,7 +4259,7 @@
<java-symbol type="color" name="config_letterboxBackgroundColor" />
<java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
<java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
- <java-symbol type="dimen" name="config_letterboxDefaultPositionMultiplierForReachability" />
+ <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 45411a9..4b98013 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -36,6 +36,7 @@
import com.android.server.UiThread;
+import java.util.function.IntConsumer;
import java.util.function.Supplier;
/**
@@ -70,7 +71,7 @@
private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
// Reachability gestures.
- private final Runnable mDoubleTapCallback;
+ private final IntConsumer mDoubleTapCallback;
/**
* Constructs a Letterbox.
@@ -84,7 +85,7 @@
Supplier<Boolean> hasWallpaperBackgroundSupplier,
Supplier<Integer> blurRadiusSupplier,
Supplier<Float> darkScrimAlphaSupplier,
- Runnable doubleTapCallback) {
+ IntConsumer doubleTapCallback) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
mAreCornersRounded = areCornersRounded;
@@ -262,7 +263,7 @@
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
- mDoubleTapCallback.run();
+ mDoubleTapCallback.accept((int) e.getX());
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 72fbfcc..cbb473c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -54,6 +54,27 @@
/** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */
static final int LETTERBOX_BACKGROUND_WALLPAPER = 3;
+ /**
+ * Enum for Letterbox reachability position types.
+ *
+ * <p>Order from left to right is important since it's used in {@link
+ * #movePositionForReachabilityToNextRightStop} and {@link
+ * #movePositionForReachabilityToNextLeftStop}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({LETTERBOX_REACHABILITY_POSITION_LEFT, LETTERBOX_REACHABILITY_POSITION_CENTER,
+ LETTERBOX_REACHABILITY_POSITION_RIGHT})
+ @interface LetterboxReachabilityPosition {};
+
+ /** Letterboxed app window is aligned to the left side. */
+ static final int LETTERBOX_REACHABILITY_POSITION_LEFT = 0;
+
+ /** Letterboxed app window is positioned in the horizontal center. */
+ static final int LETTERBOX_REACHABILITY_POSITION_CENTER = 1;
+
+ /** Letterboxed app window is aligned to the right side. */
+ static final int LETTERBOX_REACHABILITY_POSITION_RIGHT = 2;
+
final Context mContext;
// Aspect ratio of letterbox for fixed orientation, values <=
@@ -85,25 +106,25 @@
// side of the screen and 1.0 to the right side.
private float mLetterboxHorizontalPositionMultiplier;
- // Default horizontal position of a center of the letterboxed app window when reachability is
- // enabled and an app is fullscreen in landscape device orientatio. 0 corresponds to the left
- // side of the screen and 1.0 to the right side.
- // It is used as a starting point for mLetterboxHorizontalMultiplierForReachability.
- private float mDefaultPositionMultiplierForReachability;
+ // Default horizontal position the letterboxed app window when reachability is enabled and
+ // an app is fullscreen in landscape device orientatio.
+ // It is used as a starting point for mLetterboxPositionForReachability.
+ @LetterboxReachabilityPosition
+ private int mDefaultPositionForReachability;
// Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
// device orientation.
private boolean mIsReachabilityEnabled;
- // Horizontal position of a center of the letterboxed app window. 0 corresponds to
- // the left side of the screen and 1 to the right side. Keep it global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app
- // window in response to a double tap gesture (see LetterboxUiController#handleDoubleTap).
- // Used in LetterboxUiController#getHorizontalPositionMultiplier which is called from
+ // Horizontal position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getHorizontalPositionMultiplier which is called from
// ActivityRecord#updateResolvedBoundsHorizontalPosition.
// TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
// Overview after changing position in another app.
- private volatile float mLetterboxHorizontalMultiplierForReachability;
+ @LetterboxReachabilityPosition
+ private volatile int mLetterboxPositionForReachability;
LetterboxConfiguration(Context systemUiContext) {
mContext = systemUiContext;
@@ -120,9 +141,8 @@
R.dimen.config_letterboxHorizontalPositionMultiplier);
mIsReachabilityEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsReachabilityEnabled);
- mDefaultPositionMultiplierForReachability = mContext.getResources().getFloat(
- R.dimen.config_letterboxDefaultPositionMultiplierForReachability);
- mLetterboxHorizontalMultiplierForReachability = mDefaultPositionMultiplierForReachability;
+ mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
+ mLetterboxPositionForReachability = mDefaultPositionForReachability;
}
/**
@@ -395,58 +415,90 @@
}
/*
- * Gets default horizontal position of a center of the letterboxed app window when reachability
- * is enabled specified in {@link
- * R.dimen.config_letterboxDefaultPositionMultiplierForReachability} or via an ADB command.
- * 0 corresponds to the left side of the screen and 1 to the right side. The returned value is
- * >= 0.0 and <= 1.0.
+ * Gets default horizontal position of the letterboxed app window when reachability is enabled.
+ * Specified in {@link R.integer.config_letterboxDefaultPositionForReachability} or via an ADB
+ * command.
*/
- float getDefaultPositionMultiplierForReachability() {
- return (mDefaultPositionMultiplierForReachability < 0.0f
- || mDefaultPositionMultiplierForReachability > 1.0f)
- // Default to a right position if invalid value is provided.
- ? 1.0f : mDefaultPositionMultiplierForReachability;
+ @LetterboxReachabilityPosition
+ int getDefaultPositionForReachability() {
+ return mDefaultPositionForReachability;
}
/**
- * Overrides default horizontal position of a center of the letterboxed app window when
- * reachability is enabled. If given value < 0.0 or > 1.0, then it and a value of {@link
- * R.dimen.config_letterboxDefaultPositionMultiplierForReachability} are ignored and the right
- * position (1.0) is used.
+ * Overrides default horizonal position of the letterboxed app window when reachability
+ * is enabled.
*/
- void setDefaultPositionMultiplierForReachability(float multiplier) {
- mDefaultPositionMultiplierForReachability = multiplier;
+ void setDefaultPositionForReachability(@LetterboxReachabilityPosition int position) {
+ mDefaultPositionForReachability = position;
}
/**
- * Resets default horizontal position of a center of the letterboxed app window when
- * reachability is enabled to {@link
- * R.dimen.config_letterboxDefaultPositionMultiplierForReachability}.
+ * Resets default horizontal position of the letterboxed app window when reachability is
+ * enabled to {@link R.integer.config_letterboxDefaultPositionForReachability}.
*/
- void resetDefaultPositionMultiplierForReachability() {
- mDefaultPositionMultiplierForReachability = mContext.getResources().getFloat(
- R.dimen.config_letterboxDefaultPositionMultiplierForReachability);
+ void resetDefaultPositionForReachability() {
+ mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
+ }
+
+ @LetterboxReachabilityPosition
+ private static int readLetterboxReachabilityPositionFromConfig(Context context) {
+ int position = context.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForReachability);
+ return position == LETTERBOX_REACHABILITY_POSITION_LEFT
+ || position == LETTERBOX_REACHABILITY_POSITION_CENTER
+ || position == LETTERBOX_REACHABILITY_POSITION_RIGHT
+ ? position : LETTERBOX_REACHABILITY_POSITION_CENTER;
}
/*
* Gets horizontal position of a center of the letterboxed app window when reachability
* is enabled specified. 0 corresponds to the left side of the screen and 1 to the right side.
*
- * <p>The position multiplier is changed to a symmetrical value computed as (1 - current
- * multiplier) after each double tap in the letterbox area.
+ * <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getHorizontalMultiplierForReachability() {
- return mLetterboxHorizontalMultiplierForReachability;
+ switch (mLetterboxPositionForReachability) {
+ case LETTERBOX_REACHABILITY_POSITION_LEFT:
+ return 0.0f;
+ case LETTERBOX_REACHABILITY_POSITION_CENTER:
+ return 0.5f;
+ case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+ return 1.0f;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox position type: " + mLetterboxPositionForReachability);
+ }
+ }
+
+ /** Returns a string representing the given {@link LetterboxReachabilityPosition}. */
+ static String letterboxReachabilityPositionToString(
+ @LetterboxReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_REACHABILITY_POSITION_LEFT:
+ return "LETTERBOX_REACHABILITY_POSITION_LEFT";
+ case LETTERBOX_REACHABILITY_POSITION_CENTER:
+ return "LETTERBOX_REACHABILITY_POSITION_CENTER";
+ case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+ return "LETTERBOX_REACHABILITY_POSITION_RIGHT";
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox position type: " + position);
+ }
}
/**
- * Changes horizontal position of a center of the letterboxed app window to the opposite
- * (1 - current multiplier) when reachability is enabled specified. 0 corresponds to the left
- * side of the screen and 1 to the right side.
+ * Changes letterbox position for reachability to the next available one on the right side.
*/
- void flipHorizontalMultiplierForReachability() {
- mLetterboxHorizontalMultiplierForReachability =
- 1.0f - mLetterboxHorizontalMultiplierForReachability;
+ void movePositionForReachabilityToNextRightStop() {
+ mLetterboxPositionForReachability = Math.min(
+ mLetterboxPositionForReachability + 1, LETTERBOX_REACHABILITY_POSITION_RIGHT);
+ }
+
+ /**
+ * Changes letterbox position for reachability to the next available one on the left side.
+ */
+ void movePositionForReachabilityToNextLeftStop() {
+ mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index cf2afc9..2549407 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -204,12 +204,23 @@
return mActivityRecord.mWmService.mContext.getResources();
}
- private void handleDoubleTap() {
+ private void handleDoubleTap(int x) {
if (!isReachabilityEnabled() || mActivityRecord.isInTransition()) {
return;
}
- mLetterboxConfiguration.flipHorizontalMultiplierForReachability();
+ if (mLetterbox.getInnerFrame().left <= x && mLetterbox.getInnerFrame().right >= x) {
+ // Only react to clicks at the sides of the letterboxed app window.
+ return;
+ }
+
+ if (mLetterbox.getInnerFrame().left > x) {
+ // Moving to the next stop on the left side of the app window: right > center > left.
+ mLetterboxConfiguration.movePositionForReachabilityToNextLeftStop();
+ } else if (mLetterbox.getInnerFrame().right < x) {
+ // Moving to the next stop on the right side of the app window: left > center > right.
+ mLetterboxConfiguration.movePositionForReachabilityToNextRightStop();
+ }
// TODO(197549949): Add animation for transition.
mActivityRecord.recomputeConfiguration();
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 47d7f03..0f8587c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -23,6 +23,9 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_RIGHT;
import android.content.res.Resources.NotFoundException;
import android.graphics.Color;
@@ -44,6 +47,7 @@
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
+import com.android.server.wm.LetterboxConfiguration.LetterboxReachabilityPosition;
import java.io.IOException;
import java.io.PrintWriter;
@@ -787,22 +791,33 @@
return 0;
}
- private int runSetLetterboxDefaultPositionMultiplierForReachability(PrintWriter pw)
+ private int runSetLetterboxDefaultPositionForReachability(PrintWriter pw)
throws RemoteException {
- final float multiplier;
+ @LetterboxReachabilityPosition final int position;
try {
String arg = getNextArgRequired();
- multiplier = Float.parseFloat(arg);
- } catch (NumberFormatException e) {
- getErrPrintWriter().println("Error: bad multiplier format " + e);
- return -1;
+ switch (arg) {
+ case "left":
+ position = LETTERBOX_REACHABILITY_POSITION_LEFT;
+ break;
+ case "center":
+ position = LETTERBOX_REACHABILITY_POSITION_CENTER;
+ break;
+ case "right":
+ position = LETTERBOX_REACHABILITY_POSITION_RIGHT;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'left', 'center' or 'right' are expected as an argument");
+ return -1;
+ }
} catch (IllegalArgumentException e) {
getErrPrintWriter().println(
- "Error: multiplier should be provided as an argument " + e);
+ "Error: 'left', 'center' or 'right' are expected as an argument" + e);
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setDefaultPositionMultiplierForReachability(multiplier);
+ mLetterboxConfiguration.setDefaultPositionForReachability(position);
}
return 0;
}
@@ -841,8 +856,8 @@
case "--isReachabilityEnabled":
runSetLetterboxIsReachabilityEnabled(pw);
break;
- case "--defaultPositionMultiplierReachability":
- runSetLetterboxDefaultPositionMultiplierForReachability(pw);
+ case "--defaultPositionForReachability":
+ runSetLetterboxDefaultPositionForReachability(pw);
break;
default:
getErrPrintWriter().println(
@@ -885,8 +900,8 @@
case "isReachabilityEnabled":
mLetterboxConfiguration.getIsReachabilityEnabled();
break;
- case "defaultPositionMultiplierForReachability":
- mLetterboxConfiguration.getDefaultPositionMultiplierForReachability();
+ case "defaultPositionForReachability":
+ mLetterboxConfiguration.getDefaultPositionForReachability();
break;
default:
getErrPrintWriter().println(
@@ -982,7 +997,7 @@
mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
mLetterboxConfiguration.resetIsReachabilityEnabled();
- mLetterboxConfiguration.resetDefaultPositionMultiplierForReachability();
+ mLetterboxConfiguration.resetDefaultPositionForReachability();
}
}
@@ -996,8 +1011,9 @@
+ mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
pw.println("Is reachability enabled: "
+ mLetterboxConfiguration.getIsReachabilityEnabled());
- pw.println("Default position multiplier for reachability: "
- + mLetterboxConfiguration.getDefaultPositionMultiplierForReachability());
+ pw.println("Default position for reachability: "
+ + LetterboxConfiguration.letterboxReachabilityPositionToString(
+ mLetterboxConfiguration.getDefaultPositionForReachability()));
pw.println("Background type: "
+ LetterboxConfiguration.letterboxBackgroundTypeToString(
@@ -1135,11 +1151,9 @@
pw.println(" --isReachabilityEnabled [true|1|false|0]");
pw.println(" Whether reachability repositioning is allowed for letterboxed");
pw.println(" fullscreen apps in landscape device orientation.");
- pw.println(" --defaultPositionMultiplierReachability multiplier");
- pw.println(" Default horizontal position of app window center when reachability is");
- pw.println(" enabled. If multiplier < 0.0 or > 1, both it and ");
- pw.println(" R.dimen.config_letterboxDefaultPositionMultiplierForReachability");
- pw.println(" are ignored and right position (1.0) is used.");
+ pw.println(" --defaultPositionForReachability [left|center|right]");
+ pw.println(" Default horizontal position of app window when reachability is.");
+ pw.println(" enabled.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|isReachabilityEnabled");
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 78946fc..1e86522 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -63,7 +63,7 @@
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
() -> mAreCornersRounded, () -> Color.valueOf(mColor),
() -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
- /* doubleTapCallback= */ () -> {});
+ /* doubleTapCallback= */ x -> {});
mTransaction = spy(StubTransaction.class);
}