Allow status and navigation bars to be semi-transparent in letterbox mode.

Before this change, opaque bars were drawn over letterboxed activities to prevent showing a transparent bar over them because that can make notification icons or navigation buttons unreadable due to contrast between letterbox background and an activity. For instance, this happens when letterbox background is solid black while activity is white. To imporve UI in this case, semi-transparent bars are now allowed to be drawn over letterboxed activities.

Test: atest SystemUITests + manual letterbox testing
Fix: 175482966
Fix: 170216100
Change-Id: I35e35d01df7d25a5d77eb64af4e6b800f5020487
This commit is contained in:
Mariia Sandrikova
2021-01-28 15:46:34 +00:00
parent db10b7cb56
commit 3b9da58ab5
9 changed files with 88 additions and 40 deletions

View File

@@ -21,6 +21,8 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -54,7 +56,15 @@ public class InsetsFlags {
@ViewDebug.FlagToString(
mask = APPEARANCE_LIGHT_NAVIGATION_BARS,
equals = APPEARANCE_LIGHT_NAVIGATION_BARS,
name = "LIGHT_NAVIGATION_BARS")
name = "LIGHT_NAVIGATION_BARS"),
@ViewDebug.FlagToString(
mask = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
equals = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
name = "SEMI_TRANSPARENT_STATUS_BARS"),
@ViewDebug.FlagToString(
mask = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
equals = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
name = "SEMI_TRANSPARENT_NAVIGATION_BARS")
})
public @Appearance int appearance;

View File

@@ -66,6 +66,18 @@ public interface WindowInsetsController {
*/
int APPEARANCE_LIGHT_NAVIGATION_BARS = 1 << 4;
/**
* Makes status bars semi-transparent with dark background and light foreground.
* @hide
*/
int APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS = 1 << 5;
/**
* Makes navigation bars semi-transparent with dark background and light foreground.
* @hide
*/
int APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS = 1 << 6;
/**
* Determines the appearance of system bars.
* @hide
@@ -73,7 +85,8 @@ public interface WindowInsetsController {
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
APPEARANCE_LIGHT_NAVIGATION_BARS})
APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS})
@interface Appearance {
}

View File

@@ -28,6 +28,7 @@ import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
@@ -994,6 +995,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
return MODE_LIGHTS_OUT_TRANSPARENT;
} else if ((appearance & APPEARANCE_OPAQUE_NAVIGATION_BARS) != 0) {
return MODE_OPAQUE;
} else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS) != 0) {
return MODE_SEMI_TRANSPARENT;
} else {
return MODE_TRANSPARENT;
}

View File

@@ -26,6 +26,7 @@ import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
import static androidx.lifecycle.Lifecycle.State.RESUMED;
@@ -2428,6 +2429,8 @@ public class StatusBar extends SystemUI implements DemoMode,
return MODE_LIGHTS_OUT_TRANSPARENT;
} else if ((appearance & APPEARANCE_OPAQUE_STATUS_BARS) != 0) {
return MODE_OPAQUE;
} else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS) != 0) {
return MODE_SEMI_TRANSPARENT;
} else {
return MODE_TRANSPARENT;
}

View File

@@ -1431,14 +1431,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
* @return {@code true} if bar shown within a given rectangle is allowed to be transparent
* @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
* when the current activity is displayed.
*/
boolean isTransparentBarAllowed(Rect rect) {
// TODO(b/175482966): Allow status and navigation bars to be semi-transparent black
// in letterbox mode.
return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect)
|| mWmService.isLetterboxActivityCornersRounded();
boolean isFullyTransparentBarAllowed(Rect rect) {
return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
}
/**

View File

@@ -52,10 +52,19 @@ public class BarController {
return !win.isLetterboxedOverlappingWith(getContentFrame(win));
}
boolean isTransparentAllowed(WindowState win) {
/**
* @return {@code true} if bar is allowed to be fully transparent when given window is show.
*
* <p>Prevents showing a transparent bar over a letterboxed activity which can make
* notification icons or navigation buttons unreadable due to contrast between letterbox
* background and an activity. For instance, this happens when letterbox background is solid
* black while activity is white. To resolve this, only semi-transparent bars are allowed to
* be drawn over letterboxed activity.
*/
boolean isFullyTransparentAllowed(WindowState win) {
if (win == null) {
return true;
}
return win.isTransparentBarAllowed(getContentFrame(win));
return win.isFullyTransparentBarAllowed(getContentFrame(win));
}
}

View File

@@ -42,6 +42,8 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
@@ -2693,34 +2695,17 @@ public class DisplayPolicy {
private int updateSystemBarsLw(WindowState win, int disableFlags) {
final boolean dockedRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
.isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
final boolean resizing = mDisplayContent.getDockedDividerController().isResizing();
// We need to force system bars when the docked root task is visible, when the freeform
// root task is focused but also when we are resizing for the transitions when docked
// root task visibility changes.
mForceShowSystemBars = dockedRootTaskVisible || win.inFreeformWindowingMode() || resizing;
final boolean forceOpaqueStatusBar = mForceShowSystemBars && !isKeyguardShowing();
final boolean fullscreenDrawsStatusBarBackground =
drawsStatusBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsStatusBarBackground =
drawsStatusBarBackground(mTopDockedOpaqueWindowState);
final boolean fullscreenDrawsNavBarBackground =
drawsNavigationBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsNavigationBarBackground =
drawsNavigationBarBackground(mTopDockedOpaqueWindowState);
int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
if (fullscreenDrawsStatusBarBackground && dockedDrawsStatusBarBackground) {
appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
}
appearance = configureNavBarOpacity(appearance, dockedRootTaskVisible,
freeformRootTaskVisible, resizing, fullscreenDrawsNavBarBackground,
dockedDrawsNavigationBarBackground);
appearance = configureStatusBarOpacity(appearance);
appearance = configureNavBarOpacity(appearance, dockedRootTaskVisible, resizing);
final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
final long now = SystemClock.uptimeMillis();
@@ -2755,9 +2740,6 @@ public class DisplayPolicy {
}
private boolean drawsBarBackground(WindowState win, BarController controller) {
if (!controller.isTransparentAllowed(win)) {
return false;
}
if (win == null) {
return true;
}
@@ -2778,15 +2760,40 @@ public class DisplayPolicy {
return drawsBarBackground(win, mNavigationBarController);
}
/** @return the current visibility flags with the status bar opacity related flags toggled. */
private int configureStatusBarOpacity(int appearance) {
final boolean fullscreenDrawsBackground =
drawsStatusBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsBackground =
drawsStatusBarBackground(mTopDockedOpaqueWindowState);
if (fullscreenDrawsBackground && dockedDrawsBackground) {
appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
}
if (!mStatusBarController.isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState)
|| !mStatusBarController.isFullyTransparentAllowed(mTopDockedOpaqueWindowState)) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
}
return appearance;
}
/**
* @return the current visibility flags with the nav-bar opacity related flags toggled based
* on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
*/
private int configureNavBarOpacity(int appearance, boolean dockedRootTaskVisible,
boolean freeformRootTaskVisible, boolean isDockedDividerResizing,
boolean fullscreenDrawsBackground, boolean dockedDrawsNavigationBarBackground) {
boolean isDockedDividerResizing) {
final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
final boolean fullscreenDrawsBackground =
drawsNavigationBarBackground(mTopFullscreenOpaqueWindowState);
final boolean dockedDrawsBackground =
drawsNavigationBarBackground(mTopDockedOpaqueWindowState);
if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
if (fullscreenDrawsBackground && dockedDrawsNavigationBarBackground) {
if (fullscreenDrawsBackground && dockedDrawsBackground) {
appearance = clearNavBarOpaqueFlag(appearance);
} else if (dockedRootTaskVisible) {
appearance = setNavBarOpaqueFlag(appearance);
@@ -2811,6 +2818,12 @@ public class DisplayPolicy {
}
}
if (!mNavigationBarController.isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState)
|| !mNavigationBarController.isFullyTransparentAllowed(
mTopDockedOpaqueWindowState)) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
}
return appearance;
}

View File

@@ -3846,11 +3846,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
* @return {@code true} if bar shown within a given frame is allowed to be transparent
* @return {@code true} if bar shown within a given frame is allowed to be fully transparent
* when the current window is displayed.
*/
boolean isTransparentBarAllowed(Rect frame) {
return mActivityRecord == null || mActivityRecord.isTransparentBarAllowed(frame);
boolean isFullyTransparentBarAllowed(Rect frame) {
return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
}
public boolean isLetterboxedOverlappingWith(Rect rect) {

View File

@@ -607,7 +607,7 @@ public class SizeCompatTests extends WindowTestsBase {
// The activity doesn't fill the display, so the letterbox of the rotated activity is
// overlapped with the rotated content frame of status bar. Hence the status bar shouldn't
// be transparent.
assertFalse(statusBarController.isTransparentAllowed(w));
assertFalse(statusBarController.isFullyTransparentAllowed(w));
// Make the activity fill the display.
prepareUnresizable(mActivity, 10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
@@ -617,7 +617,7 @@ public class SizeCompatTests extends WindowTestsBase {
// The letterbox should only cover the notch area, so status bar can be transparent.
assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets());
assertTrue(statusBarController.isTransparentAllowed(w));
assertTrue(statusBarController.isFullyTransparentAllowed(w));
}
@Test