Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 831ee149 authored by Aurélien Pomini's avatar Aurélien Pomini
Browse files

Fix the computation of default crops to match legacy

Pre multi-crop, the wallpaper is always shown left to right (or right to
left if RTL) and use any additional width for parallax for all screens.

The only exception is for the folded screen of a foldable device: in
that case we always show the center of what's of the unfolded. Since the
logic to do that was in WM and has been deactivated, rewrite this logic
in WallpaperCropper.

Overall, the new system with no suggested crops should work exactly like
the old system. The only difference is that we now set a limit of
maximum parallax (1x the total width of the wallpape for parallax).
This limit shouldn't have real-life effect since the picker never sets a
parallax as high as this.

Flag: ACONFIG com.android.window.flags.multi_crop TEAMFOOD
Bug: 330518320
Test: atest WallpaperCropperTest
Change-Id: I51f77d9e785b9a06b001afd08d480556d4bbde28
parent f6d2d5ed
Loading
Loading
Loading
Loading
+111 −31
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.wallpaper;

import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.app.WallpaperManager.getOrientation;
import static android.app.WallpaperManager.getRotatedOrientation;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -86,7 +87,7 @@ public class WallpaperCropper {
    public interface WallpaperCropUtils {

        /**
         * Equivalent to {@link #getCrop(Point, Point, SparseArray, boolean)}
         * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}
         */
        Rect getCrop(Point displaySize, Point bitmapSize,
                SparseArray<Rect> suggestedCrops, boolean rtl);
@@ -120,16 +121,23 @@ public class WallpaperCropper {
    public Rect getCrop(Point displaySize, Point bitmapSize,
            SparseArray<Rect> suggestedCrops, boolean rtl) {

        // Case 1: if no crops are provided, center align the full image
        int orientation = getOrientation(displaySize);

        // Case 1: if no crops are provided, show the full image (from the left, or right if RTL).
        if (suggestedCrops == null || suggestedCrops.size() == 0) {
            Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
            float scale = Math.min(
                    ((float) bitmapSize.x) / displaySize.x,
                    ((float) bitmapSize.y) / displaySize.y);
            crop.scale(scale);
            crop.offset((bitmapSize.x - crop.width()) / 2,
                    (bitmapSize.y - crop.height()) / 2);
            return crop;
            Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);

            // The first exception is if the device is a foldable and we're on the folded screen.
            // In that case, show the center of what's on the unfolded screen.
            int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
            if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
                // Let the system know that we're showing the full image on the unfolded screen
                SparseArray<Rect> newSuggestedCrops = new SparseArray<>();
                newSuggestedCrops.put(unfoldedOrientation, crop);
                // This will fall into "Case 4" of this function and center the folded screen
                return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl);
            }
            return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
        }

        // If any suggested crop is invalid, fallback to case 1
@@ -142,8 +150,6 @@ public class WallpaperCropper {
            }
        }

        int orientation = getOrientation(displaySize);

        // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
        Rect suggestedCrop = suggestedCrops.get(orientation);
        if (suggestedCrop != null) {
@@ -168,10 +174,20 @@ public class WallpaperCropper {
        suggestedCrop = suggestedCrops.get(unfoldedOrientation);
        suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
        if (suggestedCrop != null) {
            // only keep the visible part (without parallax)
            // compute the visible part (without parallax) of the unfolded screen
            Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
            // compute the folded crop, at the center of the crop of the unfolded screen
            Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
            // if we removed some width, add it back to add a parallax effect
            if (res.width() < adjustedCrop.width()) {
                if (rtl) res.left = Math.min(res.left, adjustedCrop.left);
                else res.right = Math.max(res.right, adjustedCrop.right);
                // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX
                res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD);
            }
            return res;
        }


        // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
        // have the suggested crop of the relative folded orientation, reuse it by adding content.
@@ -274,11 +290,8 @@ public class WallpaperCropper {
            if (additionalWidthForParallax > MAX_PARALLAX) {
                int widthToRemove = (int) Math.ceil(
                        (additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height());
                if (rtl) {
                    adjustedCrop.left += widthToRemove;
                } else {
                    adjustedCrop.right -= widthToRemove;
                }
                adjustedCrop.left += widthToRemove / 2;
                adjustedCrop.right -= widthToRemove / 2 + widthToRemove % 2;
            }
        } else {
            // TODO (b/281648899) the third case is not always correct, fix that.
@@ -366,6 +379,24 @@ public class WallpaperCropper {
     */
    SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {

        // If the suggested crops is single-element map with (ORIENTATION_UNKNOWN, cropHint),
        // Crop the bitmap using the cropHint and compute the crops for cropped bitmap.
        Rect cropHint = suggestedCrops.get(ORIENTATION_UNKNOWN);
        if (cropHint != null) {
            Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
            if (suggestedCrops.size() != 1 || !bitmapRect.contains(cropHint)) {
                Slog.w(TAG, "Couldn't get default crops from suggested crops " + suggestedCrops
                        + " for bitmap of size " + bitmapSize + "; ignoring suggested crops");
                return getDefaultCrops(new SparseArray<>(), bitmapSize);
            }
            Point cropSize = new Point(cropHint.width(), cropHint.height());
            SparseArray<Rect> relativeDefaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
            for (int i = 0; i < relativeDefaultCrops.size(); i++) {
                relativeDefaultCrops.valueAt(i).offset(cropHint.left, cropHint.top);
            }
            return relativeDefaultCrops;
        }

        SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
        boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
                == View.LAYOUT_DIRECTION_RTL;
@@ -424,23 +455,72 @@ public class WallpaperCropper {
            boolean needScale;

            Point bitmapSize = new Point(options.outWidth, options.outHeight);
            Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);

            final Rect cropHint;
            if (multiCrop()) {
                SparseArray<Rect> defaultDisplayCrops =
                        getDefaultCrops(wallpaper.mCropHints, bitmapSize);
                // adapt the entries in wallpaper.mCropHints for the actual display
                // Check that the suggested crops per screen orientation are all within the bitmap.
                for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
                    int orientation = wallpaper.mCropHints.keyAt(i);
                    Rect crop = wallpaper.mCropHints.valueAt(i);
                    if (crop.isEmpty() || !bitmapRect.contains(crop)) {
                        Slog.w(TAG, "Invalid crop " + crop + " for orientation " + orientation
                                + " and bitmap size " + bitmapSize + "; clearing suggested crops.");
                        wallpaper.mCropHints.clear();
                        wallpaper.cropHint.set(bitmapRect);
                        break;
                    }
                }
            }
            final Rect cropHint;

            // A wallpaper with cropHints = Map.of(ORIENTATION_UNKNOWN, rect) is treated like
            // a wallpaper with cropHints = null and  cropHint = rect.
            Rect tempCropHint = wallpaper.mCropHints.get(ORIENTATION_UNKNOWN);
            if (multiCrop() && tempCropHint != null) {
                wallpaper.cropHint.set(tempCropHint);
                wallpaper.mCropHints.clear();
            }
            if (multiCrop() && wallpaper.mCropHints.size() > 0) {
                // Some suggested crops per screen orientation were provided,
                // use them to compute the default crops for this device
                SparseArray<Rect> defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize);
                // Adapt the provided crops to match the actual crops for the default display
                SparseArray<Rect> updatedCropHints = new SparseArray<>();
                for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
                    int orientation = wallpaper.mCropHints.keyAt(i);
                    Rect defaultCrop = defaultDisplayCrops.get(orientation);
                    Rect defaultCrop = defaultCrops.get(orientation);
                    if (defaultCrop != null) {
                        updatedCropHints.put(orientation, defaultCrop);
                    }
                }
                wallpaper.mCropHints = updatedCropHints;
                cropHint = getTotalCrop(defaultDisplayCrops);

                // Finally, compute the cropHint based on the default crops
                cropHint = getTotalCrop(defaultCrops);
                wallpaper.cropHint.set(cropHint);
                if (DEBUG) {
                    Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops
                            + " based on suggested crops: " + wallpaper.mCropHints);
                }
            } else if (multiCrop()) {
                // No crops per screen orientation were provided, but an overall cropHint may be
                // defined in wallpaper.cropHint. Compute the default crops for the sub-image
                // defined by the cropHint, then recompute the cropHint based on the default crops.
                // If the cropHint is empty or invalid, ignore it and use the full image.
                if (wallpaper.cropHint.isEmpty()) wallpaper.cropHint.set(bitmapRect);
                if (!bitmapRect.contains(wallpaper.cropHint)) {
                    Slog.w(TAG, "Ignoring wallpaper.cropHint = " + wallpaper.cropHint
                            + "; not within the bitmap of size " + bitmapSize);
                    wallpaper.cropHint.set(bitmapRect);
                }
                Point cropSize = new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
                SparseArray<Rect> defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
                cropHint = getTotalCrop(defaultCrops);
                cropHint.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
                wallpaper.cropHint.set(cropHint);
                if (DEBUG) {
                    Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops);
                }
            } else {
                cropHint = new Rect(wallpaper.cropHint);
            }
@@ -454,11 +534,11 @@ public class WallpaperCropper {
            }

            // Empty crop means use the full image
            if (cropHint.isEmpty()) {
            if (!multiCrop() && cropHint.isEmpty()) {
                cropHint.left = cropHint.top = 0;
                cropHint.right = options.outWidth;
                cropHint.bottom = options.outHeight;
            } else {
            } else if (!multiCrop()) {
                // force the crop rect to lie within the measured bounds
                int dx = cropHint.right > options.outWidth ? options.outWidth - cropHint.right : 0;
                int dy = cropHint.bottom > options.outHeight
@@ -472,11 +552,11 @@ public class WallpaperCropper {
                if (cropHint.top < 0) {
                    cropHint.top = 0;
                }
            }

            // Don't bother cropping if what we're left with is identity
            needCrop = (options.outHeight > cropHint.height()
                    || options.outWidth > cropHint.width());
            }

            // scale if the crop height winds up not matching the recommended metrics
            needScale = cropHint.height() > wpData.mHeight
+8 −7
Original line number Diff line number Diff line
@@ -339,10 +339,8 @@ public class WallpaperDataParser {
            }
            if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
                // migration case: the crops per screen orientation are not specified.
                int orientation = legacyCropHint.width() < legacyCropHint.height()
                        ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
                if (!legacyCropHint.isEmpty()) {
                    wallpaper.mCropHints.put(orientation, legacyCropHint);
                    wallpaper.cropHint.set(legacyCropHint);
                }
            } else {
                wallpaper.cropHint.set(totalCropHint);
@@ -469,6 +467,7 @@ public class WallpaperDataParser {
                Slog.e(TAG, "cropHints should not be null when saved");
                wallpaper.mCropHints = new SparseArray<>();
            }
            Rect rectToPutInLegacyCrop = new Rect(wallpaper.cropHint);
            for (Pair<Integer, String> pair : screenDimensionPairs()) {
                Rect cropHint = wallpaper.mCropHints.get(pair.first);
                if (cropHint == null) continue;
@@ -488,12 +487,14 @@ public class WallpaperDataParser {
                    }
                }
                if (pair.first == orientationToPutInLegacyCrop) {
                    out.attributeInt(null, "cropLeft", cropHint.left);
                    out.attributeInt(null, "cropTop", cropHint.top);
                    out.attributeInt(null, "cropRight", cropHint.right);
                    out.attributeInt(null, "cropBottom", cropHint.bottom);
                    rectToPutInLegacyCrop.set(cropHint);
                }
            }
            out.attributeInt(null, "cropLeft", rectToPutInLegacyCrop.left);
            out.attributeInt(null, "cropTop", rectToPutInLegacyCrop.top);
            out.attributeInt(null, "cropRight", rectToPutInLegacyCrop.right);
            out.attributeInt(null, "cropBottom", rectToPutInLegacyCrop.bottom);

            out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left);
            out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
            out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
+27 −30
Original line number Diff line number Diff line
@@ -1917,11 +1917,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
            final ComponentName component;
            final int finalWhich;

            if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) {
                clearWallpaperBitmaps(lockWallpaper);
            // Clear any previous ImageWallpaper related fields
            List<WallpaperData> toClear = new ArrayList<>();
            if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) toClear.add(lockWallpaper);
            if ((which & FLAG_SYSTEM) > 0) toClear.add(wallpaper);
            for (WallpaperData wallpaperToClear : toClear) {
                clearWallpaperBitmaps(wallpaperToClear);
                if (multiCrop()) {
                    wallpaperToClear.mCropHints.clear();
                    wallpaperToClear.cropHint.set(0, 0, 0, 0);
                    wallpaperToClear.mSampleSize = 1;
                }
            if ((which & FLAG_SYSTEM) > 0) {
                clearWallpaperBitmaps(wallpaper);
            }

            // lock only case: set the system wallpaper component to both screens
@@ -2257,7 +2263,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
    @Override
    public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
            int[] screenOrientations, List<Rect> crops) {
        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
        SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
        List<Rect> result = new ArrayList<>();
        boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
@@ -2274,7 +2280,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
            throw new UnsupportedOperationException(
                    "This method should only be called with the multi crop flag enabled");
        }
        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
        SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
        SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
        return WallpaperCropper.getTotalCrop(defaultCrops);
    }
@@ -2862,10 +2868,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
            return null;
        }

        int currentOrientation = mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
        SparseArray<Rect> cropMap = !multiCrop() ? null
                : getCropMap(screenOrientations, crops, currentOrientation);
        Rect cropHint = multiCrop() || crops == null ? null : crops.get(0);
        SparseArray<Rect> cropMap = !multiCrop() ? null : getCropMap(screenOrientations, crops);
        Rect cropHint = multiCrop() || crops == null || crops.isEmpty() ? new Rect() : crops.get(0);
        final boolean fromForegroundApp = !multiCrop() ? false
                : isFromForegroundApp(callingPackage);

@@ -2914,11 +2918,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                    wallpaper.setComplete = completion;
                    wallpaper.fromForegroundApp = multiCrop() ? fromForegroundApp
                            : isFromForegroundApp(callingPackage);
                    if (!multiCrop()) wallpaper.cropHint.set(cropHint);
                    if (multiCrop()) wallpaper.mCropHints = cropMap;
                    wallpaper.cropHint.set(cropHint);
                    if (multiCrop()) {
                        wallpaper.mCropHints = cropMap;
                        wallpaper.mSampleSize = 1f;
                        wallpaper.mOrientationWhenSet =
                                mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
                    }
                    wallpaper.allowBackup = allowBackup;
                    wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
                    wallpaper.mOrientationWhenSet = currentOrientation;
                }
                return pfd;
            } finally {
@@ -2927,16 +2935,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
        }
    }

    private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops,
            int currentOrientation) {
    private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops) {
        if ((crops == null ^ screenOrientations == null)
                || (crops != null && crops.size() != screenOrientations.length)) {
            throw new IllegalArgumentException(
                    "Illegal crops/orientations lists: must both be null, or both the same size");
        }
        SparseArray<Rect> cropMap = new SparseArray<>();
        boolean unknown = false;
        if (crops != null && crops.size() != 0) {
        if (crops != null && !crops.isEmpty()) {
            for (int i = 0; i < crops.size(); i++) {
                Rect crop = crops.get(i);
                int width = crop.width(), height = crop.height();
@@ -2944,22 +2950,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
                    throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
                }
                int orientation = screenOrientations[i];
                if (orientation == ORIENTATION_UNKNOWN) {
                    if (currentOrientation == ORIENTATION_UNKNOWN) {
                        throw new IllegalArgumentException(
                                "Invalid orientation: " + ORIENTATION_UNKNOWN);
                    }
                    unknown = true;
                    orientation = currentOrientation;
                if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) {
                    throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN"
                            + "screen orientation should only be used in a singleton map");
                }
                cropMap.put(orientation, crop);
            }
        }
        if (unknown && cropMap.size() > 1) {
            throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN screen "
                    + "orientation should only be used in a singleton map (in which case it"
                    + "represents the current orientation of the default display)");
        }
        return cropMap;
    }

+36 −18
Original line number Diff line number Diff line
@@ -235,12 +235,11 @@ public class WallpaperCropperTest {
        int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
        Point expectedCropSize = new Point(expectedWidth, 1000);
        for (int mode: ALL_MODES) {
            for (boolean rtl: List.of(false, true)) {
                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));
                        crop, bitmapSize, displaySize, true, rtl, mode))
                        .isEqualTo(centerOf(crop, expectedCropSize));
            }
        }
    }

@@ -362,11 +361,13 @@ public class WallpaperCropperTest {
    }

    /**
     * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
     * no suggested crops are provided.
     * Test that {@link WallpaperCropper#getCrop} uses the full image when no crops are provided.
     * If the image has more width/height ratio than the screen, keep that width for parallax up
     * to {@link WallpaperCropper#MAX_PARALLAX}. If the crop has less width/height ratio, remove the
     * surplus height, on both sides to keep the wallpaper centered.
     */
    @Test
    public void testGetCrop_noSuggestedCrops_centersWallpaper() {
    public void testGetCrop_noSuggestedCrops() {
        setUpWithDisplays(STANDARD_DISPLAY);
        Point bitmapSize = new Point(800, 1000);
        Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
@@ -374,9 +375,11 @@ public class WallpaperCropperTest {

        List<Point> displaySizes = List.of(
                new Point(500, 1000),
                new Point(200, 1000),
                new Point(1000, 500));
        List<Point> expectedCropSizes = List.of(
                new Point(500, 1000),
                new Point(Math.min(800, (int) (500 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000),
                new Point(Math.min(800, (int) (200 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000),
                new Point(800, 400));

        for (int i = 0; i < displaySizes.size(); i++) {
@@ -450,7 +453,8 @@ public class WallpaperCropperTest {
    /**
     * 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.
     * unfolded crop, by removing content on two sides to match the folded screen dimensions, and
     * then adds some width for parallax.
     * <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.
@@ -468,6 +472,7 @@ public class WallpaperCropperTest {
            int unfoldedTwo = getRotatedOrientation(unfoldedOne);
            Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
            Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
            List<Rect> unfoldedCrops = List.of(unfoldedCropOne, unfoldedCropTwo);
            SparseArray<Rect> suggestedCrops = new SparseArray<>();
            suggestedCrops.put(unfoldedOne, unfoldedCropOne);
            suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
@@ -476,15 +481,28 @@ public class WallpaperCropperTest {
            int foldedTwo = getFoldedOrientation(unfoldedTwo);
            Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
            Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
            List<Point> foldedDisplays = List.of(foldedDisplayOne, foldedDisplayTwo);

            for (boolean rtl : List.of(false, true)) {
                for (int i = 0; i < 2; i++) {
                    Rect unfoldedCrop = unfoldedCrops.get(i);
                    Point foldedDisplay = foldedDisplays.get(i);
                    Rect expectedCrop = centerOf(unfoldedCrop, foldedDisplay);
                    int maxParallax = (int) (WallpaperCropper.MAX_PARALLAX * unfoldedCrop.width());

                    // the expected behaviour is that we add width for parallax until we reach
                    // either MAX_PARALLAX or the edge of the crop for the unfolded screen.
                    if (rtl) {
                        expectedCrop.left = Math.max(
                                unfoldedCrop.left, expectedCrop.left - maxParallax);
                    } else {
                        expectedCrop.right = Math.min(
                                unfoldedCrop.right, unfoldedCrop.right + maxParallax);
                    }
                    assertThat(mWallpaperCropper.getCrop(
                        foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
                        .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));

                assertThat(mWallpaperCropper.getCrop(
                        foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
                        .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
                            foldedDisplay, bitmapSize, suggestedCrops, rtl))
                            .isEqualTo(expectedCrop);
                }
            }
        }
    }