diff options
5 files changed, 160 insertions, 45 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index ccf95527efea..ba57b76020b4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -17,7 +17,6 @@ package androidx.window.extensions.layout; import static android.view.Display.DEFAULT_DISPLAY; - import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED; import static androidx.window.util.ExtensionHelper.isZero; @@ -319,13 +318,17 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return features; } + // We will transform the feature bounds to the Activity window, so using the rotation + // from the same source (WindowConfiguration) to make sure they are synchronized. + final int rotation = windowConfiguration.getDisplayRotation(); + for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { continue; } Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); + rotateRectToDisplayRotation(displayId, rotation, featureRect); transformToWindowSpaceRect(windowConfiguration, featureRect); if (isZero(featureRect)) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index 5bfb0ebdcaa8..a836e05b2d66 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -17,7 +17,6 @@ package androidx.window.sidecar; import static android.view.Display.DEFAULT_DISPLAY; - import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; @@ -120,10 +119,12 @@ class SampleSidecarImpl extends StubSidecar { } List<SidecarDisplayFeature> features = new ArrayList<>(); + final int rotation = activity.getResources().getConfiguration().windowConfiguration + .getDisplayRotation(); for (CommonFoldingFeature baseFeature : mStoredFeatures) { SidecarDisplayFeature feature = new SidecarDisplayFeature(); Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); + rotateRectToDisplayRotation(displayId, rotation, featureRect); transformToWindowSpaceRect(activity, featureRect); feature.setRect(featureRect); feature.setType(baseFeature.getType()); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java index 9e2611f392a3..a08db7939eca 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java @@ -16,21 +16,22 @@ package androidx.window.util; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.annotation.SuppressLint; import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; +import android.util.RotationUtils; import android.view.DisplayInfo; import android.view.Surface; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.UiContext; +import androidx.annotation.VisibleForTesting; /** * Util class for both Sidecar and Extensions. @@ -44,47 +45,39 @@ public final class ExtensionHelper { /** * Rotates the input rectangle specified in default display orientation to the current display * rotation. + * + * @param displayId the display id. + * @param rotation the target rotation relative to the default display orientation. + * @param inOutRect the input/output Rect as specified in the default display orientation. */ - public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) { - DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); - DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); - int rotation = displayInfo.rotation; + public static void rotateRectToDisplayRotation( + int displayId, @Surface.Rotation int rotation, @NonNull Rect inOutRect) { + final DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); + final DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); - boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270; - int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth; - int displayHeight = isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight; - - inOutRect.intersect(0, 0, displayWidth, displayHeight); - - rotateBounds(inOutRect, displayWidth, displayHeight, rotation); + rotateRectToDisplayRotation(displayInfo, rotation, inOutRect); } - /** - * Rotates the input rectangle within parent bounds for a given delta. - */ - private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight, - @Surface.Rotation int delta) { - int origLeft = inOutRect.left; - switch (delta) { - case ROTATION_0: - return; - case ROTATION_90: - inOutRect.left = inOutRect.top; - inOutRect.top = parentWidth - inOutRect.right; - inOutRect.right = inOutRect.bottom; - inOutRect.bottom = parentWidth - origLeft; - return; - case ROTATION_180: - inOutRect.left = parentWidth - inOutRect.right; - inOutRect.right = parentWidth - origLeft; - return; - case ROTATION_270: - inOutRect.left = parentHeight - inOutRect.bottom; - inOutRect.bottom = inOutRect.right; - inOutRect.right = parentHeight - inOutRect.top; - inOutRect.top = origLeft; - return; - } + // We suppress the Lint error CheckResult for Rect#intersect because in case the displayInfo and + // folding features are out of sync, e.g. when a foldable devices is unfolding, it is acceptable + // to provide the original folding feature Rect even if they don't intersect. + @SuppressLint("RectIntersectReturnValueIgnored") + @VisibleForTesting + static void rotateRectToDisplayRotation(@NonNull DisplayInfo displayInfo, + @Surface.Rotation int rotation, @NonNull Rect inOutRect) { + // The inOutRect is specified in the default display orientation, so here we need to get + // the display width and height in the default orientation to perform the intersection and + // rotation. + final boolean isSideRotation = + displayInfo.rotation == ROTATION_90 || displayInfo.rotation == ROTATION_270; + final int baseDisplayWidth = + isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth; + final int baseDisplayHeight = + isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight; + + inOutRect.intersect(0, 0, baseDisplayWidth, baseDisplayHeight); + + RotationUtils.rotateBounds(inOutRect, baseDisplayWidth, baseDisplayHeight, rotation); } /** Transforms rectangle from absolute coordinate space to the window coordinate space. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java index 45564cb46c67..9607b78bacf0 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java @@ -17,7 +17,6 @@ package androidx.window.extensions; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - import static com.google.common.truth.Truth.assertThat; import android.app.ActivityTaskManager; @@ -32,7 +31,7 @@ import org.junit.Test; import org.junit.runner.RunWith; /** - * Test class for {@link WindowExtensionsTest}. + * Test class for {@link WindowExtensions}. * * Build/Install/Run: * atest WMJetpackUnitTests:WindowExtensionsTest diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java new file mode 100644 index 000000000000..3278cdf1c337 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2023 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 androidx.window.util; + +import static org.junit.Assert.assertEquals; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.view.DisplayInfo; +import android.view.Surface; + +import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link ExtensionHelper}. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:ExtensionHelperTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ExtensionHelperTest { + + private static final int MOCK_DISPLAY_HEIGHT = 1000; + private static final int MOCK_DISPLAY_WIDTH = 2000; + private static final int MOCK_FEATURE_LEFT = 100; + private static final int MOCK_FEATURE_RIGHT = 200; + + private static final int[] ROTATIONS = { + Surface.ROTATION_0, + Surface.ROTATION_90, + Surface.ROTATION_180, + Surface.ROTATION_270 + }; + + private static final DisplayInfo[] MOCK_DISPLAY_INFOS = { + getMockDisplayInfo(Surface.ROTATION_0), + getMockDisplayInfo(Surface.ROTATION_90), + getMockDisplayInfo(Surface.ROTATION_180), + getMockDisplayInfo(Surface.ROTATION_270), + }; + + @Test + public void testRotateRectToDisplayRotation() { + for (int rotation : ROTATIONS) { + final Rect expectedResult = getExpectedFeatureRectAfterRotation(rotation); + // The method should return correctly rotated Rect even if the requested rotation value + // differs from the rotation in DisplayInfo. This is because the WindowConfiguration is + // not always synced with DisplayInfo. + for (DisplayInfo displayInfo : MOCK_DISPLAY_INFOS) { + final Rect rect = getMockFeatureRect(); + ExtensionHelper.rotateRectToDisplayRotation(displayInfo, rotation, rect); + assertEquals( + "Result Rect should equal to expected for rotation: " + rotation + + "; displayInfo: " + displayInfo, + expectedResult, rect); + } + } + } + + @NonNull + private static DisplayInfo getMockDisplayInfo(@Surface.Rotation int rotation) { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.rotation = rotation; + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { + displayInfo.logicalWidth = MOCK_DISPLAY_WIDTH; + displayInfo.logicalHeight = MOCK_DISPLAY_HEIGHT; + } else { + displayInfo.logicalWidth = MOCK_DISPLAY_HEIGHT; + displayInfo.logicalHeight = MOCK_DISPLAY_WIDTH; + } + return displayInfo; + } + + @NonNull + private static Rect getMockFeatureRect() { + return new Rect(MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT); + } + + @NonNull + private static Rect getExpectedFeatureRectAfterRotation(@Surface.Rotation int rotation) { + switch (rotation) { + case Surface.ROTATION_0: + return new Rect( + MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT); + case Surface.ROTATION_90: + return new Rect(0, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, + MOCK_DISPLAY_HEIGHT, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT); + case Surface.ROTATION_180: + return new Rect(MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, 0, + MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT); + case Surface.ROTATION_270: + return new Rect(0, MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT, + MOCK_FEATURE_RIGHT); + default: + throw new IllegalArgumentException("Unknown rotation value: " + rotation); + } + } +} |