diff options
| -rw-r--r-- | services/core/java/com/android/server/wallpaper/WallpaperCropper.java | 18 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java | 640 |
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(); + } + } +} |