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

Commit e8e32dee authored by Aurélien Pomini's avatar Aurélien Pomini Committed by Android (Google) Code Review
Browse files

Merge "New methods setBitmap, setStream, getBitmapCrop, getWallpaperColors" into main

parents d898ee3d d9915e4a
Loading
Loading
Loading
Loading
+37 −11
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app;

import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
@@ -26,6 +27,8 @@ import android.app.WallpaperInfo;
import android.content.ComponentName;
import android.app.WallpaperColors;

import java.util.List;

/** @hide */
interface IWallpaperManager {

@@ -39,15 +42,21 @@ interface IWallpaperManager {
     *   FLAG_SET_SYSTEM
     *   FLAG_SET_LOCK
     *
     * A 'null' cropHint rectangle is explicitly permitted as a sentinel for "whatever
     * the source image's bounding rect is."
     * 'screenOrientations' and 'crops' define how the wallpaper will be positioned for
     * different screen orientations. If some screen orientations are missing, crops for these
     * orientations will be added by the system.
     *
     * If 'screenOrientations' is null, 'crops' can be null or a singleton list. The system will
     * fit the provided crop (or the whole image, if 'crops' is 'null') for the current device
     * orientation, and add crops for the missing orientations.
     *
     * The completion callback's "onWallpaperChanged()" method is invoked when the
     * new wallpaper content is ready to display.
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_WALLPAPER)")
    ParcelFileDescriptor setWallpaper(String name, in String callingPackage,
            in Rect cropHint, boolean allowBackup, out Bundle extras, int which,
            IWallpaperManagerCallback completion, int userId);
            in int[] screenOrientations, in List<Rect> crops, boolean allowBackup,
            out Bundle extras, int which, IWallpaperManagerCallback completion, int userId);

    /**
     * Set the live wallpaper.
@@ -77,6 +86,30 @@ interface IWallpaperManager {
            IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId,
            boolean getCropped);

    /**
     * For a given user and a list of display sizes, get a list of Rect representing the
     * area of the current wallpaper that is displayed for each display size.
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)")
    @SuppressWarnings(value={"untyped-collection"})
    List getBitmapCrops(in List<Point> displaySizes, int which, boolean originalBitmap, int userId);

    /**
     * Return how a bitmap of a given size would be cropped for a given list of display sizes when
     * set with the given suggested crops.
     * @hide
     */
    @SuppressWarnings(value={"untyped-collection"})
    List getFutureBitmapCrops(in Point bitmapSize, in List<Point> displaySizes,
            in int[] screenOrientations, in List<Rect> crops);

    /**
     * Return how a bitmap of a given size would be cropped when set with the given suggested crops.
     * @hide
     */
    @SuppressWarnings(value={"untyped-collection"})
    Rect getBitmapCrop(in Point bitmapSize, in int[] screenOrientations, in List<Rect> crops);

    /**
     * Retrieve the given user's current wallpaper ID of the given kind.
     */
@@ -245,11 +278,4 @@ interface IWallpaperManager {
     * @hide
     */
    boolean isStaticWallpaper(int which);

    /**
     * Temporary method for project b/270726737.
     * Return true if the wallpaper supports different crops for different display dimensions.
     * @hide
     */
     boolean isMultiCropEnabled();
}
+319 −14
Original line number Diff line number Diff line
@@ -18,11 +18,14 @@ package android.app;

import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;

import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
import static com.android.window.flags.Flags.multiCrop;

import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -58,6 +61,7 @@ import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
@@ -84,6 +88,7 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
import android.util.SparseArray;
import android.view.Display;
import android.view.WindowManagerGlobal;

@@ -104,6 +109,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -289,6 +295,79 @@ public class WallpaperManager {
    public static final String EXTRA_FROM_FOREGROUND_APP =
            "android.service.wallpaper.extra.FROM_FOREGROUND_APP";

    /**
     * The different screen orientations. {@link #getOrientation} provides their exact definition.
     * This is only used internally by the framework and the WallpaperBackupAgent.
     * @hide
     */
    @IntDef(value = {
            ORIENTATION_UNKNOWN,
            PORTRAIT,
            LANDSCAPE,
            SQUARE_PORTRAIT,
            SQUARE_LANDSCAPE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ScreenOrientation {}

    /**
     * @hide
     */
    public static final int ORIENTATION_UNKNOWN = -1;

    /**
     * Portrait orientation of most screens
     * @hide
     */
    public static final int PORTRAIT = 0;

    /**
     * Landscape orientation of most screens
     * @hide
     */
    public static final int LANDSCAPE = 1;

    /**
     * Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
     * @hide
     */
    public static final int SQUARE_PORTRAIT = 2;

    /**
     * Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
     * @hide
     */
    public static final int SQUARE_LANDSCAPE = 3;

    /**
     * Converts a (width, height) screen size to a {@link ScreenOrientation}.
     * @param screenSize the dimensions of a screen
     * @return the corresponding {@link ScreenOrientation}.
     * @hide
     */
    public static @ScreenOrientation int getOrientation(Point screenSize) {
        float ratio = ((float) screenSize.x) / screenSize.y;
        // ratios between 3/4 and 4/3 are considered square
        return ratio >= 4 / 3f ? LANDSCAPE
                : ratio > 1f ? SQUARE_LANDSCAPE
                : ratio > 3 / 4f ? SQUARE_PORTRAIT
                : PORTRAIT;
    }

    /**
     * Get the 90° rotation of a given orientation
     * @hide
     */
    public static @ScreenOrientation int getRotatedOrientation(@ScreenOrientation int orientation) {
        switch (orientation) {
            case PORTRAIT: return LANDSCAPE;
            case LANDSCAPE: return PORTRAIT;
            case SQUARE_PORTRAIT: return SQUARE_LANDSCAPE;
            case SQUARE_LANDSCAPE: return SQUARE_PORTRAIT;
            default: return ORIENTATION_UNKNOWN;
        }
    }

    // flags for which kind of wallpaper to act on

    /** @hide */
@@ -867,15 +946,8 @@ public class WallpaperManager {
     * @hide
     */
    public static boolean isMultiCropEnabled() {
        if (sGlobals == null) {
            sIsMultiCropEnabled = multiCrop();
        }
        if (sIsMultiCropEnabled == null) {
            try {
                sIsMultiCropEnabled = sGlobals.mService.isMultiCropEnabled();
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
            sIsMultiCropEnabled = multiCrop();
        }
        return sIsMultiCropEnabled;
    }
@@ -1501,6 +1573,99 @@ public class WallpaperManager {
                mContext.getUserId());
    }

    /**
     * For the current user, given a list of display sizes, return a list of rectangles representing
     * the area of the current wallpaper that would be shown for each of these sizes.
     *
     * @param displaySizes the display sizes.
     * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
     * @param originalBitmap If true, return areas relative to the original bitmap.
     *                   If false, return areas relative to the cropped bitmap.
     * @return A List of Rect where the Rect is within the cropped/original bitmap, and corresponds
     *          to what is displayed. The Rect may have a larger width/height ratio than the screen
     *          due to parallax. Return {@code null} if the wallpaper is not an ImageWallpaper.
     *          Also return {@code null} when called with which={@link #FLAG_LOCK} if there is a
     *          shared home + lock wallpaper.
     * @hide
     */
    @FlaggedApi(FLAG_MULTI_CROP)
    @RequiresPermission(READ_WALLPAPER_INTERNAL)
    @Nullable
    public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
            @SetWallpaperFlags int which, boolean originalBitmap) {
        checkExactlyOneWallpaperFlagSet(which);
        try {
            return sGlobals.mService.getBitmapCrops(displaySizes, which, originalBitmap,
                    mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * For preview purposes.
     * Return how a bitmap of a given size would be cropped for a given list of display sizes, if
     * it was set as wallpaper via {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
     * {@link #setStreamWithCrops(InputStream, Map, boolean, int)}.
     *
     * @return A List of Rect where the Rect is within the bitmap, and corresponds to what is
     *          displayed for each display size. The Rect may have a larger width/height ratio than
     *          the display due to parallax.
     * @hide
     */
    @FlaggedApi(FLAG_MULTI_CROP)
    @Nullable
    public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
            @Nullable Map<Point, Rect> cropHints) {
        try {
            if (cropHints == null) cropHints = Map.of();
            Set<Map.Entry<Point, Rect>> entries = cropHints.entrySet();
            int[] screenOrientations = entries.stream().mapToInt(entry ->
                    getOrientation(entry.getKey())).toArray();
            List<Rect> crops = entries.stream().map(Map.Entry::getValue).toList();
            return sGlobals.mService.getFutureBitmapCrops(bitmapSize, displaySizes,
                    screenOrientations, crops);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * For preview purposes.
     * Compute the wallpaper colors of the given bitmap, if it was set as wallpaper via
     * {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
     * {@link #setStreamWithCrops(InputStream, Map, boolean, int)}.
     *  Return {@code null} if an error occurred and the colors could not be computed.
     *
     * @hide
     */
    @FlaggedApi(FLAG_MULTI_CROP)
    @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
    @Nullable
    public WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap,
            @Nullable Map<Point, Rect> cropHints) {
        if (sGlobals.mService == null) {
            Log.w(TAG, "WallpaperService not running");
            throw new RuntimeException(new DeadSystemException());
        }
        try {
            if (cropHints == null) cropHints = Map.of();
            Set<Map.Entry<Point, Rect>> entries = cropHints.entrySet();
            int[] screenOrientations = entries.stream().mapToInt(entry ->
                    getOrientation(entry.getKey())).toArray();
            List<Rect> crops = entries.stream().map(Map.Entry::getValue).toList();
            Point bitmapSize = new Point(bitmap.getWidth(), bitmap.getHeight());
            Rect crop = sGlobals.mService.getBitmapCrop(bitmapSize, screenOrientations, crops);
            float dimAmount = getWallpaperDimAmount();
            Bitmap croppedBitmap = Bitmap.createBitmap(
                    bitmap, crop.left, crop.top, crop.width(), crop.height());
            WallpaperColors result = WallpaperColors.fromBitmap(croppedBitmap, dimAmount);
            return result;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * <strong> Important note: </strong>
     * <ul>
@@ -1971,7 +2136,7 @@ public class WallpaperManager {
            /* Set the wallpaper to the default values */
            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
                    "res:" + resources.getResourceName(resid),
                    mContext.getOpPackageName(), null, false, result, which, completion,
                    mContext.getOpPackageName(), null, null, false, result, which, completion,
                    mContext.getUserId());
            if (fd != null) {
                FileOutputStream fos = null;
@@ -2089,6 +2254,11 @@ public class WallpaperManager {
    public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
            boolean allowBackup, @SetWallpaperFlags int which, int userId)
            throws IOException {
        if (multiCrop()) {
            SparseArray<Rect> cropMap = new SparseArray<>();
            if (visibleCropHint != null) cropMap.put(ORIENTATION_UNKNOWN, visibleCropHint);
            return setBitmapWithCrops(fullImage, cropMap, allowBackup, which, userId);
        }
        validateRect(visibleCropHint);
        if (sGlobals.mService == null) {
            Log.w(TAG, "WallpaperService not running");
@@ -2096,9 +2266,69 @@ public class WallpaperManager {
        }
        final Bundle result = new Bundle();
        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
        final List<Rect> crops = visibleCropHint == null ? null : List.of(visibleCropHint);
        try {
            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
                    mContext.getOpPackageName(), null, crops, allowBackup, result, which,
                    completion, userId);
            if (fd != null) {
                FileOutputStream fos = null;
                try {
                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                    fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);
                    fos.close();
                    completion.waitForCompletion();
                } finally {
                    IoUtils.closeQuietly(fos);
                }
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
    }

    /**
     * Version of setBitmap that defines how the wallpaper will be positioned for different
     * display sizes.
     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
     * @param cropHints map from screen dimensions to a sub-region of the image to display for those
     *                  dimensions. The {@code Rect} sub-region may have a larger width/height ratio
     *                  than the screen dimensions to apply a horizontal parallax effect. If the
     *                  map is empty or some entries are missing, the system will apply a default
     *                  strategy to position the wallpaper for any unspecified screen dimensions.
     * @hide
     */
    @FlaggedApi(FLAG_MULTI_CROP)
    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
    public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
        SparseArray<Rect> crops = new SparseArray<>();
        cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
        return setBitmapWithCrops(fullImage, crops, allowBackup, which, mContext.getUserId());
    }

    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
    private int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull SparseArray<Rect> cropHints,
            boolean allowBackup, @SetWallpaperFlags int which, int userId) throws IOException {
        if (sGlobals.mService == null) {
            Log.w(TAG, "WallpaperService not running");
            throw new RuntimeException(new DeadSystemException());
        }
        int size = cropHints.size();
        int[] screenOrientations = new int[size];
        List<Rect> crops = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            screenOrientations[i] = cropHints.keyAt(i);
            Rect cropHint = cropHints.valueAt(i);
            validateRect(cropHint);
            crops.add(cropHint);
        }
        final Bundle result = new Bundle();
        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
        try {
            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
                    mContext.getOpPackageName(), visibleCropHint, allowBackup,
                    mContext.getOpPackageName(), screenOrientations, crops, allowBackup,
                    result, which, completion, userId);
            if (fd != null) {
                FileOutputStream fos = null;
@@ -2214,6 +2444,11 @@ public class WallpaperManager {
    public int setStream(InputStream bitmapData, Rect visibleCropHint,
            boolean allowBackup, @SetWallpaperFlags int which)
                    throws IOException {
        if (multiCrop()) {
            SparseArray<Rect> cropMap = new SparseArray<>();
            if (visibleCropHint != null) cropMap.put(ORIENTATION_UNKNOWN, visibleCropHint);
            return setStreamWithCrops(bitmapData, cropMap, allowBackup, which);
        }
        validateRect(visibleCropHint);
        if (sGlobals.mService == null) {
            Log.w(TAG, "WallpaperService not running");
@@ -2221,10 +2456,11 @@ public class WallpaperManager {
        }
        final Bundle result = new Bundle();
        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
        final List<Rect> crops = visibleCropHint == null ? null : List.of(visibleCropHint);
        try {
            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
                    mContext.getOpPackageName(), visibleCropHint, allowBackup,
                    result, which, completion, mContext.getUserId());
                    mContext.getOpPackageName(), null, crops, allowBackup, result, which,
                    completion, mContext.getUserId());
            if (fd != null) {
                FileOutputStream fos = null;
                try {
@@ -2243,6 +2479,75 @@ public class WallpaperManager {
        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
    }

    /**
     * Version of setStream that defines how the wallpaper will be positioned for different
     * display sizes.
     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
     * @param cropHints map from screen dimensions to a sub-region of the image to display for those
     *                  dimensions. The {@code Rect} sub-region may have a larger width/height ratio
     *                  than the screen dimensions to apply a horizontal parallax effect. If the
     *                  map is empty or some entries are missing, the system will apply a default
     *                  strategy to position the wallpaper for any unspecified screen dimensions.
     * @hide
     */
    @FlaggedApi(FLAG_MULTI_CROP)
    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
    public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
        SparseArray<Rect> crops = new SparseArray<>();
        cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
        return setStreamWithCrops(bitmapData, crops, allowBackup, which);
    }

    /**
     * Similar to {@link #setStreamWithCrops(InputStream, Map, boolean, int)}, but using
     * {@link ScreenOrientation} as keys of the cropHints map. Used for backup & restore, since
     * WallpaperBackupAgent stores orientations rather than the exact display size.
     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
     * @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to display
     *                  for that screen orientation.
     * @hide
     */
    @FlaggedApi(FLAG_MULTI_CROP)
    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
    public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
        if (sGlobals.mService == null) {
            Log.w(TAG, "WallpaperService not running");
            throw new RuntimeException(new DeadSystemException());
        }
        int size = cropHints.size();
        int[] screenOrientations = new int[size];
        List<Rect> crops = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            screenOrientations[i] = cropHints.keyAt(i);
            Rect cropHint = cropHints.valueAt(i);
            validateRect(cropHint);
            crops.add(cropHint);
        }
        final Bundle result = new Bundle();
        final WallpaperSetCompletion completion = new WallpaperSetCompletion();
        try {
            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
                    mContext.getOpPackageName(), screenOrientations, crops, allowBackup,
                    result, which, completion, mContext.getUserId());
            if (fd != null) {
                FileOutputStream fos = null;
                try {
                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
                    copyStreamToWallpaperFile(bitmapData, fos);
                    fos.close();
                    completion.waitForCompletion();
                } finally {
                    IoUtils.closeQuietly(fos);
                }
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
    }

    /**
     * Return whether any users are currently set to use the wallpaper
     * with the given resource ID.  That is, their wallpaper has been
@@ -2499,7 +2804,7 @@ public class WallpaperManager {
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
    @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
    public void setWallpaperDimAmount(@FloatRange (from = 0f, to = 1f) float dimAmount) {
        if (sGlobals.mService == null) {
            Log.w(TAG, "WallpaperService not running");
@@ -2519,7 +2824,7 @@ public class WallpaperManager {
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
    @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT)
    public @FloatRange (from = 0f, to = 1f) float getWallpaperDimAmount() {
        if (sGlobals.mService == null) {
            Log.w(TAG, "WallpaperService not running");
+6 −0
Original line number Diff line number Diff line
@@ -4333,6 +4333,12 @@
      "group": "WM_DEBUG_ANIM",
      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
    },
    "1810872941": {
      "message": "setWallpaperCropHints: non-existent wallpaper token: %s",
      "level": "WARN",
      "group": "WM_ERROR",
      "at": "com\/android\/server\/wm\/WindowManagerService.java"
    },
    "1820873642": {
      "message": "SyncGroup %d:  Unfinished dependencies: %s",
      "level": "VERBOSE",
+67 −0

File changed.

Preview size limit exceeded, changes collapsed.

+9 −2
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOC
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM;
import static com.android.window.flags.Flags.multiCrop;

import static com.google.common.truth.Truth.assertThat;

@@ -60,6 +61,7 @@ import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.service.wallpaper.WallpaperService;
import android.util.SparseArray;
import android.util.Xml;

import androidx.test.InstrumentationRegistry;
@@ -711,8 +713,13 @@ public class WallpaperBackupAgentTest {

    @Test
    public void testOnRestore_throwsException_logsErrors() throws Exception {
        when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt())).thenThrow(
                new RuntimeException());
        if (!multiCrop()) {
            when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt()))
                    .thenThrow(new RuntimeException());
        } else {
            when(mWallpaperManager.setStreamWithCrops(any(), any(SparseArray.class), anyBoolean(),
                    anyInt())).thenThrow(new RuntimeException());
        }
        mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
        mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
        mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
Loading