Override Launcher's window bottom inset to 0 on large screen (tablet + unfolded phone) if all display cutouts are at bottom left/right corners

Bug: 321917681
Flag: NONE
Test: manual
Change-Id: I3c41d42e11413c73146c7ef59fc712971e96db19
This commit is contained in:
Fengjiang Li
2024-01-30 10:52:37 -08:00
parent 9ac91ffa63
commit 1707575da3
3 changed files with 221 additions and 2 deletions
+3
View File
@@ -490,4 +490,7 @@
<dimen name="ps_button_width">36dp</dimen>
<dimen name="ps_lock_button_width">89dp</dimen>
<dimen name="ps_app_divider_padding">16dp</dimen>
<!-- WindowManagerProxy -->
<dimen name="max_width_and_height_of_small_display_cutout">136px</dimen>
</resources>
@@ -17,6 +17,7 @@ package com.android.launcher3.util.window;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.Utilities.dpToPx;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT;
@@ -49,6 +50,9 @@ import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.shared.ResourceUtils;
@@ -130,11 +134,11 @@ public class WindowManagerProxy implements ResourceBasedOverride {
Resources systemRes = context.getResources();
Configuration config = systemRes.getConfiguration();
boolean isTablet = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
boolean isLargeScreen = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
boolean isGesture = isGestureNav(context);
boolean isPortrait = config.screenHeightDp > config.screenWidthDp;
int bottomNav = isTablet
int bottomNav = isLargeScreen
? 0
: (isPortrait
? getDimenByName(systemRes, NAVBAR_HEIGHT)
@@ -165,6 +169,9 @@ public class WindowManagerProxy implements ResourceBasedOverride {
insetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
}
applyDisplayCutoutBottomInsetOverrideOnLargeScreen(
context, isLargeScreen, dpToPx(config.screenWidthDp), oldInsets, insetsBuilder);
WindowInsets result = insetsBuilder.build();
Insets systemWindowInsets = result.getInsetsIgnoringVisibility(
WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
@@ -173,6 +180,71 @@ public class WindowManagerProxy implements ResourceBasedOverride {
return result;
}
/**
* For large screen, when display cutout is at bottom left/right corner of screen, override
* display cutout's bottom inset to 0, because launcher allows drawing content over that area.
*/
private static void applyDisplayCutoutBottomInsetOverrideOnLargeScreen(
@NonNull Context context,
boolean isLargeScreen,
int screenWidthPx,
@NonNull WindowInsets windowInsets,
@NonNull WindowInsets.Builder insetsBuilder) {
if (!isLargeScreen || !Utilities.ATLEAST_S) {
return;
}
final DisplayCutout displayCutout = windowInsets.getDisplayCutout();
if (displayCutout == null) {
return;
}
if (!areBottomDisplayCutoutsSmallAndAtCorners(
displayCutout.getBoundingRectBottom(), screenWidthPx, context.getResources())) {
return;
}
Insets oldDisplayCutoutInset = windowInsets.getInsets(WindowInsets.Type.displayCutout());
Insets newDisplayCutoutInset = Insets.of(
oldDisplayCutoutInset.left,
oldDisplayCutoutInset.top,
oldDisplayCutoutInset.right,
0);
insetsBuilder.setInsetsIgnoringVisibility(
WindowInsets.Type.displayCutout(), newDisplayCutoutInset);
}
/**
* @see doc at {@link #areBottomDisplayCutoutsSmallAndAtCorners(Rect, int, int)}
*/
private static boolean areBottomDisplayCutoutsSmallAndAtCorners(
@NonNull Rect cutoutRectBottom, int screenWidthPx, @NonNull Resources res) {
return areBottomDisplayCutoutsSmallAndAtCorners(cutoutRectBottom, screenWidthPx,
res.getDimensionPixelSize(R.dimen.max_width_and_height_of_small_display_cutout));
}
/**
* Return true if bottom display cutouts are at bottom left/right corners, AND has width or
* height <= maxWidthAndHeightOfSmallCutoutPx. Note that display cutout rect and screenWidthPx
* passed to this method should be in the SAME screen rotation.
*
* @param cutoutRectBottom bottom display cutout rect, this is based on current screen rotation
* @param screenWidthPx screen width in px based on current screen rotation
* @param maxWidthAndHeightOfSmallCutoutPx maximum width and height pixels of cutout.
*/
@VisibleForTesting
static boolean areBottomDisplayCutoutsSmallAndAtCorners(
@NonNull Rect cutoutRectBottom, int screenWidthPx,
int maxWidthAndHeightOfSmallCutoutPx) {
// Empty cutoutRectBottom means there is no display cutout at the bottom. We should ignore
// it by returning false.
if (cutoutRectBottom.isEmpty()) {
return false;
}
return (cutoutRectBottom.right <= maxWidthAndHeightOfSmallCutoutPx)
|| cutoutRectBottom.left >= (screenWidthPx - maxWidthAndHeightOfSmallCutoutPx);
}
protected int getStatusBarHeight(Context context, boolean isPortrait, int statusBarInset) {
Resources systemRes = context.getResources();
int statusBarHeight = getDimenByName(systemRes,
@@ -249,6 +321,12 @@ public class WindowManagerProxy implements ResourceBasedOverride {
DisplayCutout rotatedCutout = rotateCutout(
displayInfo.cutout, displayInfo.size.x, displayInfo.size.y, rotation, i);
Rect insets = getSafeInsets(rotatedCutout);
if (areBottomDisplayCutoutsSmallAndAtCorners(
rotatedCutout.getBoundingRectBottom(),
bounds.width(),
context.getResources())) {
insets.bottom = 0;
}
insets.top = Math.max(insets.top, statusBarHeight);
insets.bottom = Math.max(insets.bottom, navBarHeight);
@@ -0,0 +1,138 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.util.window
import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.util.window.WindowManagerProxy.areBottomDisplayCutoutsSmallAndAtCorners
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
/** Unit test for [WindowManagerProxy] */
@SmallTest
@RunWith(AndroidJUnit4::class)
class WindowManagerProxyTest {
private val windowWidthPx = 2000
private val bottomLeftCutout = Rect(0, 2364, 136, 2500)
private val bottomRightCutout = Rect(1864, 2364, 2000, 2500)
private val bottomLeftCutoutWithOffset = Rect(10, 2364, 136, 2500)
private val bottomRightCutoutWithOffset = Rect(1864, 2364, 1990, 2500)
private val maxWidthAndHeightOfSmallCutoutPx = 136
@Test
fun cutout_at_bottom_right_corner() {
assertTrue(
areBottomDisplayCutoutsSmallAndAtCorners(
bottomRightCutout,
windowWidthPx,
maxWidthAndHeightOfSmallCutoutPx
)
)
}
@Test
fun cutout_at_bottom_left_corner_with_offset() {
assertTrue(
areBottomDisplayCutoutsSmallAndAtCorners(
bottomLeftCutoutWithOffset,
windowWidthPx,
maxWidthAndHeightOfSmallCutoutPx
)
)
}
@Test
fun cutout_at_bottom_right_corner_with_offset() {
assertTrue(
areBottomDisplayCutoutsSmallAndAtCorners(
bottomRightCutoutWithOffset,
windowWidthPx,
maxWidthAndHeightOfSmallCutoutPx
)
)
}
@Test
fun cutout_at_bottom_left_corner() {
assertTrue(
areBottomDisplayCutoutsSmallAndAtCorners(
bottomLeftCutout,
windowWidthPx,
maxWidthAndHeightOfSmallCutoutPx
)
)
}
@Test
fun cutout_at_bottom_edge_at_bottom_corners() {
assertTrue(
areBottomDisplayCutoutsSmallAndAtCorners(
bottomLeftCutout,
windowWidthPx,
maxWidthAndHeightOfSmallCutoutPx
)
)
}
@Test
fun cutout_too_big_not_at_bottom_corners() {
// Rect in size of 200px
val bigBottomLeftCutout = Rect(0, 2300, 200, 2500)
assertFalse(
areBottomDisplayCutoutsSmallAndAtCorners(
bigBottomLeftCutout,
windowWidthPx,
maxWidthAndHeightOfSmallCutoutPx
)
)
}
@Test
fun cutout_too_small_at_bottom_corners() {
// Rect in size of 100px
val smallBottomLeft = Rect(0, 2400, 100, 2500)
assertTrue(
areBottomDisplayCutoutsSmallAndAtCorners(
smallBottomLeft,
windowWidthPx,
maxWidthAndHeightOfSmallCutoutPx
)
)
}
@Test
fun cutout_empty_not_at_bottom_corners() {
val emptyRect = Rect(0, 0, 0, 0)
assertFalse(
areBottomDisplayCutoutsSmallAndAtCorners(
emptyRect,
windowWidthPx,
maxWidthAndHeightOfSmallCutoutPx
)
)
}
}