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:
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user