From cd5b7a4b1e47637ba9a71e0e8e44a276f859695d Mon Sep 17 00:00:00 2001 From: Vali Calinescu Date: Thu, 21 Jul 2022 14:09:20 +0000 Subject: [PATCH] Pass LetterboxDetails to SysUI Create LetterboxDetails component to be passed to SysUI so that the status bar scrim can only be shown when needed on large screens when apps are letterboxed. Fix: 238611867 Test: atest WmTests:SizeCompatTests Change-Id: I41517a15b3b57452b55b927e18eb2e5acc2fffdd --- .../com/android/server/wm/DisplayPolicy.java | 32 +++++- .../java/com/android/server/wm/Letterbox.java | 5 + .../server/wm/LetterboxUiController.java | 32 ++++++ .../android/server/wm/SizeCompatTests.java | 104 ++++++++++++++++++ 4 files changed, 171 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 98a51a97110db..87a70c7ddcd39 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -316,12 +316,19 @@ public class DisplayPolicy { */ private final ArrayList mStatusBarBackgroundWindows = new ArrayList<>(); + /** + * A collection of {@link LetterboxDetails} of all visible activities to be sent to SysUI in + * order to determine status bar appearance + */ + private final ArrayList mLetterboxDetails = new ArrayList<>(); + private String mFocusedApp; private int mLastDisableFlags; private int mLastAppearance; private int mLastBehavior; private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); private AppearanceRegion[] mLastStatusBarAppearanceRegions; + private LetterboxDetails[] mLastLetterboxDetails; /** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */ private final Rect mStatusBarColorCheckedBounds = new Rect(); @@ -1629,6 +1636,7 @@ public class DisplayPolicy { mNavBarColorWindowCandidate = null; mNavBarBackgroundWindow = null; mStatusBarAppearanceRegionList.clear(); + mLetterboxDetails.clear(); mStatusBarBackgroundWindows.clear(); mStatusBarColorCheckedBounds.setEmpty(); mStatusBarBackgroundCheckedBounds.setEmpty(); @@ -1708,6 +1716,16 @@ public class DisplayPolicy { win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS, new Rect(win.getFrame()))); mStatusBarColorCheckedBounds.union(sTmpRect); + // Check if current activity is letterboxed in order create a LetterboxDetails + // component to be passed to SysUI for status bar treatment + final ActivityRecord currentActivity = win.getActivityRecord(); + if (currentActivity != null) { + final LetterboxDetails currentLetterboxDetails = currentActivity + .mLetterboxUiController.getLetterboxDetails(); + if (currentLetterboxDetails != null) { + mLetterboxDetails.add(currentLetterboxDetails); + } + } } } @@ -2384,12 +2402,15 @@ public class DisplayPolicy { callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags, cause)); } + final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()]; + mLetterboxDetails.toArray(letterboxDetails); if (mLastAppearance == appearance && mLastBehavior == behavior && mRequestedVisibilities.equals(win.getRequestedVisibilities()) && Objects.equals(mFocusedApp, focusedApp) && mLastFocusIsFullscreen == isFullscreen - && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) { + && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions) + && Arrays.equals(mLastLetterboxDetails, letterboxDetails)) { return; } if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen @@ -2405,9 +2426,10 @@ public class DisplayPolicy { mFocusedApp = focusedApp; mLastFocusIsFullscreen = isFullscreen; mLastStatusBarAppearanceRegions = statusBarAppearanceRegions; + mLastLetterboxDetails = letterboxDetails; callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId, appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior, - requestedVisibilities, focusedApp, new LetterboxDetails[]{})); + requestedVisibilities, focusedApp, letterboxDetails)); } private void callStatusBarSafely(Consumer consumer) { @@ -2823,6 +2845,12 @@ public class DisplayPolicy { pw.print(prefixInner); pw.println(mLastStatusBarAppearanceRegions[i]); } } + if (mLastLetterboxDetails != null) { + pw.print(prefix); pw.println("mLastLetterboxDetails="); + for (int i = mLastLetterboxDetails.length - 1; i >= 0; i--) { + pw.print(prefixInner); pw.println(mLastLetterboxDetails[i]); + } + } if (!mStatusBarBackgroundWindows.isEmpty()) { pw.print(prefix); pw.println("mStatusBarBackgroundWindows="); for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index b5eff41d4f620..df3109ad33c73 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -138,6 +138,11 @@ public class Letterbox { return mInner; } + /** @return The frame that contains the inner frame and the insets. */ + Rect getOuterFrame() { + return mOuter; + } + /** * Returns {@code true} if the letterbox does not overlap with the bar, or the letterbox can * fully cover the window frame. diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index d652767937006..ec9ee29679a46 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -68,6 +68,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.LetterboxDetails; import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import java.io.PrintWriter; @@ -141,6 +142,15 @@ final class LetterboxUiController { } } + /** Gets the outer bounds of letterbox. The bounds will be empty if there is no letterbox. */ + private void getLetterboxOuterBounds(Rect outBounds) { + if (mLetterbox != null) { + outBounds.set(mLetterbox.getOuterFrame()); + } else { + outBounds.setEmpty(); + } + } + /** * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent * when the current activity is displayed. @@ -683,4 +693,26 @@ final class LetterboxUiController { mActivityRecord.mTaskSupervisor.getActivityMetricsLogger() .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange); } + + @Nullable + LetterboxDetails getLetterboxDetails() { + final WindowState w = mActivityRecord.findMainWindow(); + if (mLetterbox == null || w == null || w.isLetterboxedForDisplayCutout()) { + return null; + } + Rect letterboxInnerBounds = new Rect(); + Rect letterboxOuterBounds = new Rect(); + getLetterboxInnerBounds(letterboxInnerBounds); + getLetterboxOuterBounds(letterboxOuterBounds); + + if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { + return null; + } + + return new LetterboxDetails( + letterboxInnerBounds, + letterboxOuterBounds, + w.mAttrs.insetsFlags.appearance + ); + } } 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 f2640d2dc4049..ff79b46129320 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -61,8 +61,10 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; @@ -82,11 +84,14 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; +import android.view.InsetsVisibilities; import android.view.WindowManager; import androidx.test.filters.MediumTest; import com.android.internal.policy.SystemBarUtils; +import com.android.internal.statusbar.LetterboxDetails; +import com.android.server.statusbar.StatusBarManagerInternal; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @@ -97,6 +102,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; +import org.mockito.Mockito; /** * Tests for Size Compatibility mode. @@ -2186,6 +2192,104 @@ public class SizeCompatTests extends WindowTestsBase { assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(organizer.mPrimary.getBounds()); } + @Test + public void testLetterboxDetailsForStatusBar_noLetterbox() { + setUpDisplaySizeWithApp(2800, 1000); + addStatusBar(mActivity.mDisplayContent); + addWindowToActivity(mActivity); // Add a window to the activity so that we can get an + // appearance inside letterboxDetails + + DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy(); + StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal(); + // We should get a null LetterboxDetails object as there is no letterboxed activity, so + // nothing will get passed to SysUI + verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(), + any(), anyBoolean(), anyInt(), + any(InsetsVisibilities.class), isNull(), isNull()); + + } + + @Test + public void testLetterboxDetailsForStatusBar_letterboxedForMaxAspectRatio() { + setUpDisplaySizeWithApp(2800, 1000); + addStatusBar(mActivity.mDisplayContent); + addWindowToActivity(mActivity); // Add a window to the activity so that we can get an + // appearance inside letterboxDetails + // Prepare unresizable activity with max aspect ratio + prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + // Refresh the letterbox + mActivity.mRootWindowContainer.performSurfacePlacement(); + + Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + assertEquals(mBounds, new Rect(850, 0, 1950, 1000)); + + DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy(); + LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails( + mBounds, + mActivity.getDisplayContent().getBounds(), + mActivity.findMainWindow().mAttrs.insetsFlags.appearance + )}; + + // Check that letterboxDetails actually gets passed to SysUI + StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal(); + verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(), + any(), anyBoolean(), anyInt(), + any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails)); + } + + @Test + public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() { + mAtm.mDevEnableNonResizableMultiWindow = true; + setUpDisplaySizeWithApp(2800, 1000); + addStatusBar(mActivity.mDisplayContent); + // Create another task for the second activity + final Task newTask = new TaskBuilder(mSupervisor).setDisplay(mActivity.getDisplayContent()) + .setCreateActivity(true).build(); + ActivityRecord newActivity = newTask.getTopNonFinishingActivity(); + + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); + + // Move first activity to split screen which takes half of the screen. + organizer.mPrimary.setBounds(0, 0, 1400, 1000); + organizer.putTaskToPrimary(mTask, true); + // Move second activity to split screen which takes half of the screen. + organizer.mSecondary.setBounds(1400, 0, 2800, 1000); + organizer.putTaskToSecondary(newTask, true); + + addWindowToActivity(mActivity); // Add a window to the activity so that we can get an + // appearance inside letterboxDetails + // Prepare unresizable activity with max aspect ratio + prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + addWindowToActivity(newActivity); + prepareUnresizable(newActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + + // Refresh the letterboxes + newActivity.mRootWindowContainer.performSurfacePlacement(); + + Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + assertEquals(mBounds, new Rect(150, 0, 1250, 1000)); + final Rect newBounds = new Rect(newActivity.getWindowConfiguration().getBounds()); + assertEquals(newBounds, new Rect(1550, 0, 2650, 1000)); + + DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy(); + LetterboxDetails[] expectedLetterboxDetails = { new LetterboxDetails( + mBounds, + organizer.mPrimary.getBounds(), + mActivity.findMainWindow().mAttrs.insetsFlags.appearance + ), new LetterboxDetails( + newBounds, + organizer.mSecondary.getBounds(), + newActivity.findMainWindow().mAttrs.insetsFlags.appearance + )}; + + // Check that letterboxDetails actually gets passed to SysUI + StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal(); + verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(), + any(), anyBoolean(), anyInt(), + any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails)); + } + private void recomputeNaturalConfigurationOfUnresizableActivity() { // Recompute the natural configuration of the non-resizable activity and the split screen. mActivity.clearSizeCompatMode();