Letterboxing: ensure bar contrast
Ensures that any bars that draw over a letterbox draw their own
background such that they always have contrast.
This fixes an issue, where if a light status / navigation bar app was
letterboxed, the bar would still draw dark icons over the now black
letterbox, making the icons unreadable.
To do so, splits the letterbox into a layout and an apply surface
changes phase.
Change-Id: Ia8afa3386d75d9a72434d701b867c3ebc35cc36f
Fixes: 72696928
Test: Open a letterboxed app with a white navigation bar, verify it is visible
Test: ApiDemos > App > Activity > Max Aspect Ratio > 1:1, verify navbar is visible
diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java
index c906705..cf88bd5 100644
--- a/services/core/java/com/android/server/policy/BarController.java
+++ b/services/core/java/com/android/server/policy/BarController.java
@@ -20,6 +20,7 @@
import static com.android.server.wm.proto.BarControllerProto.TRANSIENT_STATE;
import android.app.StatusBarManager;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
@@ -68,6 +69,7 @@
private boolean mShowTransparent;
private boolean mSetUnHideFlagWhenNextTransparent;
private boolean mNoAnimationOnNextShow;
+ private final Rect mContentFrame = new Rect();
private OnBarVisibilityChangedListener mVisibilityChangeListener;
@@ -87,6 +89,15 @@
mWin = win;
}
+ /**
+ * Sets the frame within which the bar will display its content.
+ *
+ * This is used to determine if letterboxes interfere with the display of such content.
+ */
+ public void setContentFrame(Rect frame) {
+ mContentFrame.set(frame);
+ }
+
public void setShowTransparent(boolean transparent) {
if (transparent != mShowTransparent) {
mShowTransparent = transparent;
@@ -135,7 +146,8 @@
} else {
vis &= ~mTranslucentFlag;
}
- if ((fl & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+ if ((fl & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+ && isTransparentAllowed(win)) {
vis |= mTransparentFlag;
} else {
vis &= ~mTransparentFlag;
@@ -148,6 +160,10 @@
return vis;
}
+ boolean isTransparentAllowed(WindowState win) {
+ return win == null || !win.isLetterboxedOverlappingWith(mContentFrame);
+ }
+
public boolean setBarShowingLw(final boolean show) {
if (mWin == null) return false;
if (show && mTransientBarState == TRANSIENT_BAR_HIDING) {
@@ -328,6 +344,7 @@
pw.println(StatusBarManager.windowStateToString(mState));
pw.print(prefix); pw.print(" "); pw.print("mTransientBar"); pw.print('=');
pw.println(transientBarStateToString(mTransientBarState));
+ pw.print(prefix); pw.print(" mContentFrame="); pw.println(mContentFrame);
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3cd79e1..c997d6e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4705,6 +4705,13 @@
displayFrames.mStable.top = displayFrames.mUnrestricted.top
+ mStatusBarHeightForRotation[displayFrames.mRotation];
+ // Tell the bar controller where the collapsed status bar content is
+ mTmpRect.set(mStatusBar.getContentFrameLw());
+ mTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
+ mTmpRect.top = mStatusBar.getContentFrameLw().top; // Ignore top display cutout inset
+ mTmpRect.bottom = displayFrames.mStable.top; // Use collapsed status bar size
+ mStatusBarController.setContentFrame(mTmpRect);
+
boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
boolean statusBarTranslucent = (sysui
& (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
@@ -4838,6 +4845,8 @@
mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe, mTmpNavigationFrame, dcf,
mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe,
displayFrames.mDisplayCutout);
+ mNavigationBarController.setContentFrame(mNavigationBar.getContentFrameLw());
+
if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
return mNavigationBarController.checkHiddenLw();
}
@@ -8097,15 +8106,6 @@
return vis;
}
- private boolean drawsSystemBarBackground(WindowState win) {
- return win == null || (win.getAttrs().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
- }
-
- private boolean forcesDrawStatusBarBackground(WindowState win) {
- return win == null || (win.getAttrs().privateFlags
- & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
- }
-
private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
final boolean dockedStackVisible =
mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -8129,13 +8129,9 @@
mTopDockedOpaqueWindowState, 0, 0);
final boolean fullscreenDrawsStatusBarBackground =
- (drawsSystemBarBackground(mTopFullscreenOpaqueWindowState)
- && (vis & View.STATUS_BAR_TRANSLUCENT) == 0)
- || forcesDrawStatusBarBackground(mTopFullscreenOpaqueWindowState);
+ drawsStatusBarBackground(vis, mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsStatusBarBackground =
- (drawsSystemBarBackground(mTopDockedOpaqueWindowState)
- && (dockedVis & View.STATUS_BAR_TRANSLUCENT) == 0)
- || forcesDrawStatusBarBackground(mTopDockedOpaqueWindowState);
+ drawsStatusBarBackground(dockedVis, mTopDockedOpaqueWindowState);
// prevent status bar interaction from clearing certain flags
int type = win.getAttrs().type;
@@ -8238,6 +8234,22 @@
return vis;
}
+ private boolean drawsStatusBarBackground(int vis, WindowState win) {
+ if (!mStatusBarController.isTransparentAllowed(win)) {
+ return false;
+ }
+ if (win == null) {
+ return true;
+ }
+
+ final boolean drawsSystemBars =
+ (win.getAttrs().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ final boolean forceDrawsSystemBars =
+ (win.getAttrs().privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
+
+ return forceDrawsSystemBars || drawsSystemBars && (vis & View.STATUS_BAR_TRANSLUCENT) == 0;
+ }
+
/**
* @return the current visibility flags with the nav-bar opacity related flags toggled based
* on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index b0d5e1a..9904216 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -447,6 +447,14 @@
return false;
}
+ /**
+ * Returns true if the window has a letterbox and any part of that letterbox overlaps with
+ * the given {@code rect}.
+ */
+ default boolean isLetterboxedOverlappingWith(Rect rect) {
+ return false;
+ }
+
/** @return the current windowing mode of this window. */
int getWindowingMode();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 1f71b8f..c15893b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -24,7 +24,6 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.SurfaceControl.HIDDEN;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -712,7 +711,7 @@
if (destroyedSomething) {
final DisplayContent dc = getDisplayContent();
dc.assignWindowLayers(true /*setLayoutNeeded*/);
- updateLetterbox(null);
+ updateLetterboxSurface(null);
}
}
@@ -979,7 +978,7 @@
void removeChild(WindowState child) {
super.removeChild(child);
checkKeyguardFlagsChanged();
- updateLetterbox(child);
+ updateLetterboxSurface(child);
}
private boolean waitingForReplacement() {
@@ -1470,7 +1469,7 @@
return isInterestingAndDrawn;
}
- void updateLetterbox(WindowState winHint) {
+ void layoutLetterbox(WindowState winHint) {
final WindowState w = findMainWindow();
if (w != winHint && winHint != null && w != null) {
return;
@@ -1481,19 +1480,20 @@
if (mLetterbox == null) {
mLetterbox = new Letterbox(() -> makeChildSurface(null));
}
- mLetterbox.setDimensions(mPendingTransaction, getParent().getBounds(), w.mFrame);
+ mLetterbox.layout(getParent().getBounds(), w.mFrame);
} else if (mLetterbox != null) {
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- // Make sure we have a transaction here, in case we're called outside of a transaction.
- // This does not use mPendingTransaction, because SurfaceAnimator uses a
- // global transaction in onAnimationEnd.
- SurfaceControl.openTransaction();
- try {
- mLetterbox.hide(t);
- } finally {
- SurfaceControl.mergeToGlobalTransaction(t);
- SurfaceControl.closeTransaction();
- }
+ mLetterbox.hide();
+ }
+ }
+
+ void updateLetterboxSurface(WindowState winHint) {
+ final WindowState w = findMainWindow();
+ if (w != winHint && winHint != null && w != null) {
+ return;
+ }
+ layoutLetterbox(winHint);
+ if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
+ mLetterbox.applySurfaceChanges(mPendingTransaction);
}
}
@@ -2156,4 +2156,12 @@
return new Rect();
}
}
+
+ /**
+ * @eturn true if there is a letterbox and any part of that letterbox overlaps with
+ * the given {@code rect}.
+ */
+ boolean isLetterboxOverlappingWith(Rect rect) {
+ return mLetterbox != null && mLetterbox.isOverlappingWith(rect);
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 59babcf..c35c05d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -80,7 +80,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -558,6 +557,10 @@
w.updateLastInsetValues();
}
+ if (w.mAppToken != null) {
+ w.mAppToken.layoutLetterbox(w);
+ }
+
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+ " mContainingFrame=" + w.mContainingFrame
+ " mDisplayFrame=" + w.mDisplayFrame);
@@ -697,7 +700,7 @@
final AppWindowToken atoken = w.mAppToken;
if (atoken != null) {
- atoken.updateLetterbox(w);
+ atoken.updateLetterboxSurface(w);
final boolean updateAllDrawn = atoken.updateDrawnWindowStates(w);
if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(atoken)) {
mTmpUpdateAllDrawn.add(atoken);
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 0f9735d..4eb021c 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -49,24 +49,26 @@
}
/**
- * Sets the dimensions of the the letterbox, such that the area between the outer and inner
+ * Lays out the letterbox, such that the area between the outer and inner
* frames will be covered by black color surfaces.
*
- * @param t a transaction in which to set the dimensions
+ * The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface.
+ *
* @param outer the outer frame of the letterbox (this frame will be black, except the area
* that intersects with the {code inner} frame).
* @param inner the inner frame of the letterbox (this frame will be clear)
*/
- public void setDimensions(SurfaceControl.Transaction t, Rect outer, Rect inner) {
+ public void layout(Rect outer, Rect inner) {
mOuter.set(outer);
mInner.set(inner);
- mTop.setRect(t, outer.left, outer.top, inner.right, inner.top);
- mLeft.setRect(t, outer.left, inner.top, inner.left, outer.bottom);
- mBottom.setRect(t, inner.left, inner.bottom, outer.right, outer.bottom);
- mRight.setRect(t, inner.right, outer.top, outer.right, inner.bottom);
+ mTop.layout(outer.left, outer.top, inner.right, inner.top);
+ mLeft.layout(outer.left, inner.top, inner.left, outer.bottom);
+ mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom);
+ mRight.layout(inner.right, outer.top, outer.right, inner.bottom);
}
+
/**
* Gets the insets between the outer and inner rects.
*/
@@ -79,12 +81,20 @@
}
/**
+ * Returns true if any part of the letterbox overlaps with the given {@code rect}.
+ */
+ public boolean isOverlappingWith(Rect rect) {
+ return mTop.isOverlappingWith(rect) || mLeft.isOverlappingWith(rect)
+ || mBottom.isOverlappingWith(rect) || mRight.isOverlappingWith(rect);
+ }
+
+ /**
* Hides the letterbox.
*
- * @param t a transaction in which to hide the letterbox
+ * The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface.
*/
- public void hide(SurfaceControl.Transaction t) {
- setDimensions(t, EMPTY_RECT, EMPTY_RECT);
+ public void hide() {
+ layout(EMPTY_RECT, EMPTY_RECT);
}
/**
@@ -100,43 +110,40 @@
mRight.destroy();
}
+ /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */
+ public boolean needsApplySurfaceChanges() {
+ return mTop.needsApplySurfaceChanges()
+ || mLeft.needsApplySurfaceChanges()
+ || mBottom.needsApplySurfaceChanges()
+ || mRight.needsApplySurfaceChanges();
+ }
+
+ public void applySurfaceChanges(SurfaceControl.Transaction t) {
+ mTop.applySurfaceChanges(t);
+ mLeft.applySurfaceChanges(t);
+ mBottom.applySurfaceChanges(t);
+ mRight.applySurfaceChanges(t);
+ }
+
private class LetterboxSurface {
private final String mType;
private SurfaceControl mSurface;
- private int mLastLeft = 0;
- private int mLastTop = 0;
- private int mLastRight = 0;
- private int mLastBottom = 0;
+ private final Rect mSurfaceFrame = new Rect();
+ private final Rect mLayoutFrame = new Rect();
public LetterboxSurface(String type) {
mType = type;
}
- public void setRect(SurfaceControl.Transaction t,
- int left, int top, int right, int bottom) {
- if (mLastLeft == left && mLastTop == top
- && mLastRight == right && mLastBottom == bottom) {
+ public void layout(int left, int top, int right, int bottom) {
+ if (mLayoutFrame.left == left && mLayoutFrame.top == top
+ && mLayoutFrame.right == right && mLayoutFrame.bottom == bottom) {
// Nothing changed.
return;
}
-
- if (left < right && top < bottom) {
- if (mSurface == null) {
- createSurface();
- }
- t.setPosition(mSurface, left, top);
- t.setSize(mSurface, right - left, bottom - top);
- t.show(mSurface);
- } else if (mSurface != null) {
- t.hide(mSurface);
- }
-
- mLastLeft = left;
- mLastTop = top;
- mLastRight = right;
- mLastBottom = bottom;
+ mLayoutFrame.set(left, top, right, bottom);
}
private void createSurface() {
@@ -154,11 +161,40 @@
}
public int getWidth() {
- return Math.max(0, mLastRight - mLastLeft);
+ return Math.max(0, mLayoutFrame.width());
}
public int getHeight() {
- return Math.max(0, mLastBottom - mLastTop);
+ return Math.max(0, mLayoutFrame.height());
+ }
+
+ public boolean isOverlappingWith(Rect rect) {
+ if (getWidth() <= 0 || getHeight() <= 0) {
+ return false;
+ }
+ return Rect.intersects(rect, mLayoutFrame);
+ }
+
+ public void applySurfaceChanges(SurfaceControl.Transaction t) {
+ if (mSurfaceFrame.equals(mLayoutFrame)) {
+ // Nothing changed.
+ return;
+ }
+ mSurfaceFrame.set(mLayoutFrame);
+ if (!mSurfaceFrame.isEmpty()) {
+ if (mSurface == null) {
+ createSurface();
+ }
+ t.setPosition(mSurface, mSurfaceFrame.left, mSurfaceFrame.top);
+ t.setSize(mSurface, mSurfaceFrame.width(), mSurfaceFrame.height());
+ t.show(mSurface);
+ } else if (mSurface != null) {
+ t.hide(mSurface);
+ }
+ }
+
+ public boolean needsApplySurfaceChanges() {
+ return !mSurfaceFrame.equals(mLayoutFrame);
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 91cd4bb..5912897 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3074,6 +3074,11 @@
return (fl & FLAG_FULLSCREEN) != 0 || (sysui & (SYSTEM_UI_FLAG_FULLSCREEN)) != 0;
}
+ @Override
+ public boolean isLetterboxedOverlappingWith(Rect rect) {
+ return mAppToken != null && mAppToken.isLetterboxOverlappingWith(rect);
+ }
+
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}