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
This commit is contained in:
Vali Calinescu
2022-07-21 14:09:20 +00:00
parent ad2153b3c8
commit cd5b7a4b1e
4 changed files with 171 additions and 2 deletions

View File

@@ -316,12 +316,19 @@ public class DisplayPolicy {
*/
private final ArrayList<WindowState> 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<LetterboxDetails> 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<StatusBarManagerInternal> 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--) {

View File

@@ -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.

View File

@@ -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
);
}
}

View File

@@ -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();