From 3b9da58ab559ca239fb6941e403b99f689e1fe2d Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Thu, 28 Jan 2021 15:46:34 +0000 Subject: [PATCH] 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 --- core/java/android/view/InsetsFlags.java | 12 +++- .../android/view/WindowInsetsController.java | 15 ++++- .../systemui/navigationbar/NavigationBar.java | 3 + .../systemui/statusbar/phone/StatusBar.java | 3 + .../com/android/server/wm/ActivityRecord.java | 9 +-- .../com/android/server/wm/BarController.java | 13 +++- .../com/android/server/wm/DisplayPolicy.java | 63 +++++++++++-------- .../com/android/server/wm/WindowState.java | 6 +- .../android/server/wm/SizeCompatTests.java | 4 +- 9 files changed, 88 insertions(+), 40 deletions(-) diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java index a334907c04bc1..3355252c43722 100644 --- a/core/java/android/view/InsetsFlags.java +++ b/core/java/android/view/InsetsFlags.java @@ -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; diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 991ed55180038..227b9f402bba9 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -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 { } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 34d1f6e1789cb..fcb5da3f8c525 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -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; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index d4a2b41573385..14a6d57560339 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -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; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3bb4c74b7dc06..7e870cf59315a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -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); } /** diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java index eee27c72e583c..3c8cf4edf7330 100644 --- a/services/core/java/com/android/server/wm/BarController.java +++ b/services/core/java/com/android/server/wm/BarController.java @@ -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. + * + *

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)); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index a7db9d624b7e9..f52cb09bc2016 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -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; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 9a7823e35a014..159760491ff1b 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3846,11 +3846,11 @@ class WindowState extends WindowContainer 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) { diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 942e1c91989cc..d0d612fc8dda1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -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