summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java640
2 files changed, 650 insertions, 8 deletions
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 601c7f450d4f..9616c286f1b3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -39,6 +39,7 @@ import android.util.SparseArray;
import android.view.DisplayInfo;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.TimingsTraceAndSlog;
import libcore.io.IoUtils;
@@ -65,7 +66,7 @@ public class WallpaperCropper {
* Maximum acceptable parallax.
* A value of 1 means "the additional width for parallax is at most 100% of the screen width"
*/
- private static final float MAX_PARALLAX = 1f;
+ @VisibleForTesting static final float MAX_PARALLAX = 1f;
/**
* We define three ways to adjust a crop. These modes are used depending on the situation:
@@ -73,10 +74,9 @@ public class WallpaperCropper {
* - When going from folded to unfolded, we want to add content
* - For a screen rotation, we want to keep the same amount of content
*/
- private static final int ADD = 1;
- private static final int REMOVE = 2;
- private static final int BALANCE = 3;
-
+ @VisibleForTesting static final int ADD = 1;
+ @VisibleForTesting static final int REMOVE = 2;
+ @VisibleForTesting static final int BALANCE = 3;
private final WallpaperDisplayHelper mWallpaperDisplayHelper;
@@ -209,7 +209,8 @@ public class WallpaperCropper {
* Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
* crop. This removes any additional width used for parallax. No-op if displaySize == null.
*/
- private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+ @VisibleForTesting
+ static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
if (displaySize == null) return crop;
Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
// only keep the visible part (without parallax)
@@ -240,12 +241,13 @@ public class WallpaperCropper {
* </li>
* </ul>
*/
- private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+ @VisibleForTesting
+ static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
boolean parallax, boolean rtl, int mode) {
Rect adjustedCrop = new Rect(crop);
float cropRatio = ((float) crop.width()) / crop.height();
float screenRatio = ((float) screenSize.x) / screenSize.y;
- if (cropRatio >= screenRatio) {
+ if (cropRatio > screenRatio) {
if (!parallax) {
// rotate everything 90 degrees clockwise, compute the result, and rotate back
int newLeft = bitmapSize.y - crop.bottom;
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
new file mode 100644
index 000000000000..7ecc7fd1b94b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -0,0 +1,640 @@
+/*
+ * 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.server.wallpaper;
+
+import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.PORTRAIT;
+import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
+
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular
+ * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_MULTI_CROP)
+public class WallpaperCropperTest {
+
+ @Mock
+ private WallpaperDisplayHelper mWallpaperDisplayHelper;
+ private WallpaperCropper mWallpaperCropper;
+
+ private static final Point PORTRAIT_ONE = new Point(500, 800);
+ private static final Point PORTRAIT_TWO = new Point(400, 1000);
+ private static final Point PORTRAIT_THREE = new Point(2000, 800);
+ private static final Point PORTRAIT_FOUR = new Point(1600, 1000);
+
+ private static final Point SQUARE_PORTRAIT_ONE = new Point(1000, 800);
+ private static final Point SQUARE_LANDSCAPE_ONE = new Point(800, 1000);
+
+ /**
+ * Common device: a single screen of portrait/landscape orientation
+ */
+ private static final List<Point> STANDARD_DISPLAY = List.of(PORTRAIT_ONE);
+
+ /** 1: folded: portrait, unfolded: square with w < h */
+ private static final List<Point> FOLDABLE_ONE = List.of(PORTRAIT_ONE, SQUARE_PORTRAIT_ONE);
+
+ /** 2: folded: portrait, unfolded: square with w > h */
+ private static final List<Point> FOLDABLE_TWO = List.of(PORTRAIT_TWO, SQUARE_LANDSCAPE_ONE);
+
+ /** 3: folded: square with w < h, unfolded: portrait */
+ private static final List<Point> FOLDABLE_THREE = List.of(SQUARE_PORTRAIT_ONE, PORTRAIT_THREE);
+
+ /** 4: folded: square with w > h, unfolded: portrait */
+ private static final List<Point> FOLDABLE_FOUR = List.of(SQUARE_LANDSCAPE_ONE, PORTRAIT_FOUR);
+
+ /**
+ * List of different sets of displays for foldable devices. Foldable devices have two displays:
+ * a folded (smaller) unfolded (larger).
+ */
+ private static final List<List<Point>> ALL_FOLDABLE_DISPLAYS = List.of(
+ FOLDABLE_ONE, FOLDABLE_TWO, FOLDABLE_THREE, FOLDABLE_FOUR);
+
+ private SparseArray<Point> mDisplaySizes = new SparseArray<>();
+ private int mFolded = ORIENTATION_UNKNOWN;
+ private int mFoldedRotated = ORIENTATION_UNKNOWN;
+ private int mUnfolded = ORIENTATION_UNKNOWN;
+ private int mUnfoldedRotated = ORIENTATION_UNKNOWN;
+
+ private static final List<Integer> ALL_MODES = List.of(
+ WallpaperCropper.ADD, WallpaperCropper.REMOVE, WallpaperCropper.BALANCE);
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+ }
+
+ private void setUpWithDisplays(List<Point> displaySizes) {
+ mDisplaySizes = new SparseArray<>();
+ displaySizes.forEach(size -> {
+ mDisplaySizes.put(getOrientation(size), size);
+ Point rotated = new Point(size.y, size.x);
+ mDisplaySizes.put(getOrientation(rotated), rotated);
+ });
+ when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes);
+ if (displaySizes.size() == 2) {
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(p -> p.x * p.y)).get();
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(p -> p.x * p.y)).get();
+ mUnfolded = getOrientation(largestDisplay);
+ mFolded = getOrientation(smallestDisplay);
+ mUnfoldedRotated = getRotatedOrientation(mUnfolded);
+ mFoldedRotated = getRotatedOrientation(mFolded);
+ }
+ doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0)))
+ .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt());
+ doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0)))
+ .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt());
+ }
+
+ private int getFoldedOrientation(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+ if (orientation == mUnfolded) return mFolded;
+ if (orientation == mUnfoldedRotated) return mFoldedRotated;
+ return ORIENTATION_UNKNOWN;
+ }
+
+ private int getUnfoldedOrientation(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+ if (orientation == mFolded) return mUnfolded;
+ if (orientation == mFoldedRotated) return mUnfoldedRotated;
+ return ORIENTATION_UNKNOWN;
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} successfully removes the parallax in a simple
+ * case, removing the right or left part depending on the "rtl" argument.
+ */
+ @Test
+ public void testNoParallax_noScale() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1200, 1000);
+ Point expectedCropSize = new Point(1000, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} correctly takes zooming into account.
+ */
+ @Test
+ public void testNoParallax_withScale() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(600, 500);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ Point expectedCropSize = new Point(500, 500);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} correctly removes parallax when the image is
+ * cropped, i.e. when the crop rectangle is not the full bitmap.
+ */
+ @Test
+ public void testNoParallax_withScaleAndCrop() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect crop = new Rect(300, 1000, 900, 1500);
+ Point expectedCropSize = new Point(500, 500);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop} does nothing when the crop has the same
+ * width/height ratio than the screen.
+ */
+ @Test
+ public void testGetAdjustedCrop_noOp() {
+ Point displaySize = new Point(1000, 1000);
+
+ for (Point bitmapSize: List.of(
+ new Point(1000, 1000),
+ new Point(2000, 2000),
+ new Point(500, 500))) {
+ for (Rect crop: List.of(
+ new Rect(0, 0, bitmapSize.x, bitmapSize.y),
+ new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) {
+ for (int mode: ALL_MODES) {
+ for (boolean rtl: List.of(true, false)) {
+ for (boolean parallax: List.of(true, false)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, parallax, rtl, mode))
+ .isEqualTo(crop);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+ * does not keep more width than needed for {@link WallpaperCropper#MAX_PARALLAX}.
+ */
+ @Test
+ public void testGetAdjustedCrop_tooMuchParallax() {
+ Point displaySize = new Point(1000, 1000);
+ int tooLargeWidth = (int) (displaySize.x * (1 + 2 * WallpaperCropper.MAX_PARALLAX));
+ Point bitmapSize = new Point(tooLargeWidth, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
+ Point expectedCropSize = new Point(expectedWidth, 1000);
+ for (int mode: ALL_MODES) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, false, mode))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, true, mode))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+ * does not remove parallax if the parallax is below {@link WallpaperCropper#MAX_PARALLAX}.
+ */
+ @Test
+ public void testGetAdjustedCrop_acceptableParallax() {
+ Point displaySize = new Point(1000, 1000);
+ List<Integer> acceptableWidths = List.of(displaySize.x,
+ (int) (displaySize.x * (1 + 0.5 * WallpaperCropper.MAX_PARALLAX)),
+ (int) (displaySize.x * (1 + 0.9 * WallpaperCropper.MAX_PARALLAX)),
+ (int) (displaySize.x * (1 + 1.0 * WallpaperCropper.MAX_PARALLAX)));
+ for (int acceptableWidth: acceptableWidths) {
+ Point bitmapSize = new Point(acceptableWidth, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ for (int mode : ALL_MODES) {
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, rtl, mode))
+ .isEqualTo(crop);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#ADD}, correctly enlarges the crop to match the display dimensions,
+ * and adds content to the crop by an equal amount on both sides when possible.
+ */
+ @Test
+ public void testGetAdjustedCrop_add() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1000, 1000);
+
+ List<Rect> crops = List.of(
+ new Rect(0, 0, 900, 1000),
+ new Rect(0, 0, 1000, 900),
+ new Rect(0, 0, 400, 500),
+ new Rect(500, 600, 1000, 1000));
+
+ List<Rect> expectedAdjustedCrops = List.of(
+ new Rect(0, 0, 1000, 1000),
+ new Rect(0, 0, 1000, 1000),
+ new Rect(0, 0, 500, 500),
+ new Rect(500, 500, 1000, 1000));
+
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ Rect expectedCrop = expectedAdjustedCrops.get(i);
+ for (boolean rtl: List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD))
+ .isEqualTo(expectedCrop);
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#REMOVE}, correctly shrinks the crop to match the display dimensions,
+ * and removes content by an equal amount on both sides.
+ */
+ @Test
+ public void testGetAdjustedCrop_remove() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1500, 1500);
+
+ List<Rect> crops = List.of(
+ new Rect(50, 0, 1150, 1000),
+ new Rect(0, 50, 1000, 1150));
+
+ Point expectedCropSize = new Point(1000, 1000);
+
+ for (Rect crop: crops) {
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE))
+ .isEqualTo(centerOf(crop, expectedCropSize));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#BALANCE}, gives an adjusted crop with the same center and same number
+ * of pixels when possible.
+ */
+ @Test
+ public void testGetAdjustedCrop_balance() {
+ Point displaySize = new Point(500, 1000);
+ Point transposedDisplaySize = new Point(1000, 500);
+ Point bitmapSize = new Point(1000, 1000);
+
+ List<Rect> crops = List.of(
+ new Rect(0, 250, 1000, 750),
+ new Rect(100, 0, 300, 100));
+
+ List<Rect> expectedAdjustedCrops = List.of(
+ new Rect(250, 0, 750, 1000),
+ new Rect(150, 0, 250, 200));
+
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ Rect expected = expectedAdjustedCrops.get(i);
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE))
+ .isEqualTo(expected);
+
+ Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right);
+ Rect expectedTransposed = new Rect(
+ expected.top, expected.left, expected.bottom, expected.right);
+ assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize,
+ transposedDisplaySize, false, false, WallpaperCropper.BALANCE))
+ .isEqualTo(expectedTransposed);
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
+ * no suggested crops are provided.
+ */
+ @Test
+ public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(800, 1000);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+
+ List<Point> displaySizes = List.of(
+ new Point(500, 1000),
+ new Point(1000, 500));
+ List<Point> expectedCropSizes = List.of(
+ new Point(500, 1000),
+ new Point(800, 400));
+
+ for (int i = 0; i < displaySizes.size(); i++) {
+ Point displaySize = displaySizes.get(i);
+ Point expectedCropSize = expectedCropSizes.get(i);
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ displaySize, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, expectedCropSize));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop} reuses a suggested crop of the same orientation
+ * as the display if possible, and does not remove additional width for parallax,
+ * but adds width if necessary.
+ */
+ @Test
+ public void testGetCrop_hasSuggestedCrop() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(800, 1000);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
+ for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+ suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
+ }
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ new Point(300, 800), bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(suggestedCrops.get(PORTRAIT));
+ assertThat(mWallpaperCropper.getCrop(
+ new Point(500, 800), bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(new Rect(0, 0, 500, 800));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, if there is no suggested crop of the same
+ * orientation as the display, reuses a suggested crop of the rotated orientation if possible,
+ * and preserves the center and number of pixels of the crop if possible.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image. Also the image is large enough to preserver the number
+ * of pixels (no additional zoom required).
+ */
+ @Test
+ public void testGetCrop_hasRotatedSuggestedCrop() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(2000, 1800);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ Point portrait = PORTRAIT_ONE;
+ Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
+ Point squarePortrait = SQUARE_PORTRAIT_ONE;
+ Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
+ suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
+ suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ landscape, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, landscape));
+
+ assertThat(mWallpaperCropper.getCrop(
+ squarePortrait, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, squarePortrait));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
+ * crop only for the relative unfolded orientation, creates the folded crop at the center of the
+ * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image.
+ */
+ @Test
+ public void testGetCrop_hasUnfoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2400);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int unfoldedOne = getOrientation(largestDisplay);
+ int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+ Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
+ Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(unfoldedOne, unfoldedCropOne);
+ suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
+
+ int foldedOne = getFoldedOrientation(unfoldedOne);
+ int foldedTwo = getFoldedOrientation(unfoldedTwo);
+ Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+ Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
+
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+ * crop only for the relative folded orientation, creates the unfolded crop with the same center
+ * as the folded crop, by adding content on two sides to match the unfolded screen dimensions.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom) and are
+ * at the center of the image. Also the image is large enough to add content.
+ */
+ @Test
+ public void testGetCrop_hasFoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int foldedOne = getOrientation(smallestDisplay);
+ int foldedTwo = getRotatedOrientation(foldedOne);
+ Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+ Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+ Rect foldedCropOne = centerOf(bitmapRect, foldedDisplayOne);
+ Rect foldedCropTwo = centerOf(bitmapRect, foldedDisplayTwo);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(foldedOne, foldedCropOne);
+ suggestedCrops.put(foldedTwo, foldedCropTwo);
+
+ int unfoldedOne = getUnfoldedOrientation(foldedOne);
+ int unfoldedTwo = getUnfoldedOrientation(foldedTwo);
+ Point unfoldedDisplayOne = mDisplaySizes.get(unfoldedOne);
+ Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(centerOf(mWallpaperCropper.getCrop(
+ unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne))
+ .isEqualTo(foldedCropOne);
+
+ assertThat(centerOf(mWallpaperCropper.getCrop(
+ unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo))
+ .isEqualTo(foldedCropTwo);
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an folded crop with a suggested
+ * crop only for the rotated unfolded orientation, creates the folded crop from that crop by
+ * combining a rotate + fold operation. The folded crop should have less pixels than the
+ * unfolded crop due to the fold operation which removes content on both sides of the image.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image.
+ */
+ @Test
+ public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int unfoldedOne = getOrientation(largestDisplay);
+ int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+ for (int unfolded: List.of(unfoldedOne, unfoldedTwo)) {
+ Rect unfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(unfolded));
+ int rotatedUnfolded = getRotatedOrientation(unfolded);
+ Rect rotatedUnfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(rotatedUnfolded));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(unfolded, unfoldedCrop);
+ int rotatedFolded = getFoldedOrientation(rotatedUnfolded);
+ Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay));
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+ * crop only for the rotated folded orientation, creates the unfolded crop from that crop by
+ * combining a rotate + unfold operation. The unfolded crop should have more pixels than the
+ * folded crop due to the unfold operation which adds content on two sides of the image.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are centered inside the image. Also the image is large enough to add content.
+ */
+ @Test
+ public void testGetCrop_hasRotatedFoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int foldedOne = getOrientation(smallestDisplay);
+ int foldedTwo = getRotatedOrientation(foldedOne);
+ for (int folded: List.of(foldedOne, foldedTwo)) {
+ Rect foldedCrop = centerOf(bitmapRect, mDisplaySizes.get(folded));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(folded, foldedCrop);
+ int rotatedFolded = getRotatedOrientation(folded);
+ int rotatedUnfolded = getUnfoldedOrientation(rotatedFolded);
+ Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+ Rect rotatedFoldedCrop = centerOf(bitmapRect, rotatedFoldedDisplay);
+ Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded);
+
+ for (boolean rtl : List.of(false, true)) {
+ Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop(
+ rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl);
+ assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay))
+ .isEqualTo(rotatedFoldedCrop);
+ }
+ }
+ }
+ }
+
+ private static Rect centerOf(Rect container, Point point) {
+ checkSubset(container, point);
+ int diffWidth = container.width() - point.x;
+ int diffHeight = container.height() - point.y;
+ int startX = container.left + diffWidth / 2;
+ int startY = container.top + diffHeight / 2;
+ return new Rect(startX, startY, startX + point.x, startY + point.y);
+ }
+
+ private static Rect leftOf(Rect container, Point point) {
+ Rect result = centerOf(container, point);
+ result.offset(container.left - result.left, 0);
+ return result;
+ }
+
+ private static Rect rightOf(Rect container, Point point) {
+ checkSubset(container, point);
+ Rect result = centerOf(container, point);
+ result.offset(container.right - result.right, 0);
+ return result;
+ }
+
+ private static void checkSubset(Rect container, Point point) {
+ if (container.width() < point.x || container.height() < point.y) {
+ throw new IllegalArgumentException();
+ }
+ }
+}