| /* | |
| * Copyright (C) 2009 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 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; | |
| import android.annotation.Nullable; | |
| import android.annotation.RawRes; | |
| import android.annotation.RequiresPermission; | |
| import android.annotation.SdkConstant; | |
| import android.annotation.SdkConstant.SdkConstantType; | |
| import android.annotation.SystemApi; | |
| import android.annotation.SystemService; | |
| import android.annotation.TestApi; | |
| import android.annotation.UiContext; | |
| import android.app.compat.CompatChanges; | |
| import android.compat.annotation.ChangeId; | |
| import android.compat.annotation.EnabledSince; | |
| import android.compat.annotation.UnsupportedAppUsage; | |
| import android.content.ComponentName; | |
| import android.content.ContentResolver; | |
| import android.content.Context; | |
| import android.content.Intent; | |
| import android.content.pm.PackageManager; | |
| import android.content.pm.ResolveInfo; | |
| import android.content.res.Configuration; | |
| import android.content.res.Resources; | |
| import android.content.res.Resources.NotFoundException; | |
| import android.graphics.Bitmap; | |
| import android.graphics.BitmapFactory; | |
| import android.graphics.BitmapRegionDecoder; | |
| import android.graphics.Canvas; | |
| import android.graphics.ColorFilter; | |
| import android.graphics.ColorSpace; | |
| 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; | |
| import android.graphics.RectF; | |
| import android.graphics.drawable.BitmapDrawable; | |
| import android.graphics.drawable.Drawable; | |
| import android.net.Uri; | |
| import android.os.Build; | |
| import android.os.Bundle; | |
| import android.os.DeadSystemException; | |
| import android.os.Environment; | |
| import android.os.FileUtils; | |
| import android.os.Handler; | |
| import android.os.IBinder; | |
| import android.os.Looper; | |
| import android.os.ParcelFileDescriptor; | |
| import android.os.RemoteException; | |
| import android.os.StrictMode; | |
| import android.os.SystemProperties; | |
| import android.os.Trace; | |
| import android.text.TextUtils; | |
| import android.util.ArrayMap; | |
| 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; | |
| import com.android.internal.R; | |
| import com.android.internal.annotations.Keep; | |
| import libcore.io.IoUtils; | |
| import java.io.BufferedInputStream; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.FileNotFoundException; | |
| import java.io.FileOutputStream; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.lang.annotation.Retention; | |
| import java.lang.annotation.RetentionPolicy; | |
| 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; | |
| /** | |
| * Provides access to the system wallpaper. With WallpaperManager, you can | |
| * get the current wallpaper, get the desired dimensions for the wallpaper, set | |
| * the wallpaper, and more. | |
| * | |
| * <p> An app can check whether wallpapers are supported for the current user, by calling | |
| * {@link #isWallpaperSupported()}, and whether setting of wallpapers is allowed, by calling | |
| * {@link #isSetWallpaperAllowed()}. | |
| */ | |
| @SystemService(Context.WALLPAPER_SERVICE) | |
| public class WallpaperManager { | |
| private static String TAG = "WallpaperManager"; | |
| private static final boolean DEBUG = false; | |
| /** | |
| * Trying to read the wallpaper file or bitmap in T will return | |
| * the default wallpaper bitmap/file instead of throwing a SecurityException. | |
| */ | |
| @ChangeId | |
| @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) | |
| static final long RETURN_DEFAULT_ON_SECURITY_EXCEPTION = 239784307L; | |
| /** | |
| * In U and later, attempting to read the wallpaper file or bitmap will throw an exception, | |
| * (except with the READ_WALLPAPER_INTERNAL permission). | |
| */ | |
| @ChangeId | |
| @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) | |
| static final long THROW_ON_SECURITY_EXCEPTION = 237508058L; | |
| private float mWallpaperXStep = -1; | |
| private float mWallpaperYStep = -1; | |
| private static final @NonNull RectF LOCAL_COLOR_BOUNDS = | |
| new RectF(0, 0, 1, 1); | |
| /** {@hide} */ | |
| private static final String PROP_WALLPAPER = "ro.config.wallpaper"; | |
| /** {@hide} */ | |
| private static final String PROP_LOCK_WALLPAPER = "ro.config.lock_wallpaper"; | |
| /** {@hide} */ | |
| private static final String PROP_WALLPAPER_COMPONENT = "ro.config.wallpaper_component"; | |
| /** {@hide} */ | |
| private static final String VALUE_CMF_COLOR = | |
| android.os.SystemProperties.get("ro.boot.hardware.color"); | |
| /** {@hide} */ | |
| private static final String WALLPAPER_CMF_PATH = "/wallpaper/image/"; | |
| /** | |
| * Activity Action: Show settings for choosing wallpaper. Do not use directly to construct | |
| * an intent; instead, use {@link #getCropAndSetWallpaperIntent}. | |
| * <p>Input: {@link Intent#getData} is the URI of the image to crop and set as wallpaper. | |
| * <p>Output: RESULT_OK if user decided to crop/set the wallpaper, RESULT_CANCEL otherwise | |
| * Activities that support this intent should specify a MIME filter of "image/*" | |
| */ | |
| @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) | |
| public static final String ACTION_CROP_AND_SET_WALLPAPER = | |
| "android.service.wallpaper.CROP_AND_SET_WALLPAPER"; | |
| /** | |
| * Launch an activity for the user to pick the current global live | |
| * wallpaper. | |
| */ | |
| public static final String ACTION_LIVE_WALLPAPER_CHOOSER | |
| = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; | |
| /** | |
| * Directly launch live wallpaper preview, allowing the user to immediately | |
| * confirm to switch to a specific live wallpaper. You must specify | |
| * {@link #EXTRA_LIVE_WALLPAPER_COMPONENT} with the ComponentName of | |
| * a live wallpaper component that is to be shown. | |
| */ | |
| public static final String ACTION_CHANGE_LIVE_WALLPAPER | |
| = "android.service.wallpaper.CHANGE_LIVE_WALLPAPER"; | |
| /** | |
| * Extra in {@link #ACTION_CHANGE_LIVE_WALLPAPER} that specifies the | |
| * ComponentName of a live wallpaper that should be shown as a preview, | |
| * for the user to confirm. | |
| */ | |
| public static final String EXTRA_LIVE_WALLPAPER_COMPONENT | |
| = "android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT"; | |
| /** | |
| * Manifest entry for activities that respond to {@link Intent#ACTION_SET_WALLPAPER} | |
| * which allows them to provide a custom large icon associated with this action. | |
| */ | |
| public static final String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported by the wallpaper | |
| * host when the user taps on an empty area (not performing an action | |
| * in the host). The x and y arguments are the location of the tap in | |
| * screen coordinates. | |
| */ | |
| public static final String COMMAND_TAP = "android.wallpaper.tap"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported by the wallpaper | |
| * host when the user releases a secondary pointer on an empty area | |
| * (not performing an action in the host). The x and y arguments are | |
| * the location of the secondary tap in screen coordinates. | |
| */ | |
| public static final String COMMAND_SECONDARY_TAP = "android.wallpaper.secondaryTap"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported by the wallpaper | |
| * host when the user drops an object into an area of the host. The x | |
| * and y arguments are the location of the drop. | |
| */ | |
| public static final String COMMAND_DROP = "android.home.drop"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is waking | |
| * up. The x and y arguments are a location (possibly very roughly) corresponding to the action | |
| * that caused the device to wake up. For example, if the power button was pressed, this will be | |
| * the location on the screen nearest the power button. | |
| * | |
| * If the location is unknown or not applicable, x and y will be -1. | |
| * | |
| * @hide | |
| */ | |
| public static final String COMMAND_WAKING_UP = "android.wallpaper.wakingup"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported by System UI when the device keyguard | |
| * starts going away. | |
| * This command is triggered by {@link android.app.IActivityTaskManager#keyguardGoingAway(int)}. | |
| * | |
| * @hide | |
| */ | |
| public static final String COMMAND_KEYGUARD_GOING_AWAY = | |
| "android.wallpaper.keyguardgoingaway"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported by System UI when the device is going to | |
| * sleep. The x and y arguments are a location (possibly very roughly) corresponding to the | |
| * action that caused the device to go to sleep. For example, if the power button was pressed, | |
| * this will be the location on the screen nearest the power button. | |
| * | |
| * If the location is unknown or not applicable, x and y will be -1. | |
| * | |
| * @hide | |
| */ | |
| public static final String COMMAND_GOING_TO_SLEEP = "android.wallpaper.goingtosleep"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported when a physical display switch event | |
| * happens, e.g. fold and unfold. | |
| * @hide | |
| */ | |
| public static final String COMMAND_DISPLAY_SWITCH = "android.wallpaper.displayswitch"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported when the wallpaper that was already | |
| * set is re-applied by the user. | |
| * @hide | |
| */ | |
| public static final String COMMAND_REAPPLY = "android.wallpaper.reapply"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported when the live wallpaper needs to be | |
| * frozen. | |
| * @hide | |
| */ | |
| public static final String COMMAND_FREEZE = "android.wallpaper.freeze"; | |
| /** | |
| * Command for {@link #sendWallpaperCommand}: reported when the live wallapper doesn't need | |
| * to be frozen anymore. | |
| * @hide | |
| */ | |
| public static final String COMMAND_UNFREEZE = "android.wallpaper.unfreeze"; | |
| /** | |
| * Extra passed back from setWallpaper() giving the new wallpaper's assigned ID. | |
| * @hide | |
| */ | |
| public static final String EXTRA_NEW_WALLPAPER_ID = "android.service.wallpaper.extra.ID"; | |
| /** | |
| * Extra passed on {@link Intent.ACTION_WALLPAPER_CHANGED} indicating if wallpaper was set from | |
| * a foreground app. | |
| * @hide | |
| */ | |
| 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 */ | |
| @IntDef(flag = true, prefix = { "FLAG_" }, value = { | |
| FLAG_SYSTEM, | |
| FLAG_LOCK | |
| }) | |
| @Retention(RetentionPolicy.SOURCE) | |
| public @interface SetWallpaperFlags {} | |
| /** | |
| * Flag: set or retrieve the general system wallpaper. | |
| */ | |
| public static final int FLAG_SYSTEM = 1 << 0; | |
| /** | |
| * Flag: set or retrieve the lock-screen-specific wallpaper. | |
| */ | |
| public static final int FLAG_LOCK = 1 << 1; | |
| private static final Object sSync = new Object[0]; | |
| @UnsupportedAppUsage | |
| private static Globals sGlobals; | |
| private final Context mContext; | |
| private final boolean mWcgEnabled; | |
| private final ColorManagementProxy mCmProxy; | |
| private static Boolean sIsMultiCropEnabled = null; | |
| /** | |
| * Special drawable that draws a wallpaper as fast as possible. Assumes | |
| * no scaling or placement off (0,0) of the wallpaper (this should be done | |
| * at the time the bitmap is loaded). | |
| */ | |
| static class FastBitmapDrawable extends Drawable { | |
| private final Bitmap mBitmap; | |
| private final int mWidth; | |
| private final int mHeight; | |
| private int mDrawLeft; | |
| private int mDrawTop; | |
| private final Paint mPaint; | |
| private FastBitmapDrawable(Bitmap bitmap) { | |
| mBitmap = bitmap; | |
| mWidth = bitmap.getWidth(); | |
| mHeight = bitmap.getHeight(); | |
| setBounds(0, 0, mWidth, mHeight); | |
| mPaint = new Paint(); | |
| mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); | |
| } | |
| @Override | |
| public void draw(Canvas canvas) { | |
| canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, mPaint); | |
| } | |
| @Override | |
| public int getOpacity() { | |
| return PixelFormat.OPAQUE; | |
| } | |
| @Override | |
| public void setBounds(int left, int top, int right, int bottom) { | |
| mDrawLeft = left + (right-left - mWidth) / 2; | |
| mDrawTop = top + (bottom-top - mHeight) / 2; | |
| } | |
| @Override | |
| public void setAlpha(int alpha) { | |
| throw new UnsupportedOperationException("Not supported with this drawable"); | |
| } | |
| @Override | |
| public void setColorFilter(ColorFilter colorFilter) { | |
| throw new UnsupportedOperationException("Not supported with this drawable"); | |
| } | |
| @Override | |
| public void setDither(boolean dither) { | |
| throw new UnsupportedOperationException("Not supported with this drawable"); | |
| } | |
| @Override | |
| public void setFilterBitmap(boolean filter) { | |
| throw new UnsupportedOperationException("Not supported with this drawable"); | |
| } | |
| @Override | |
| public int getIntrinsicWidth() { | |
| return mWidth; | |
| } | |
| @Override | |
| public int getIntrinsicHeight() { | |
| return mHeight; | |
| } | |
| @Override | |
| public int getMinimumWidth() { | |
| return mWidth; | |
| } | |
| @Override | |
| public int getMinimumHeight() { | |
| return mHeight; | |
| } | |
| } | |
| /** | |
| * Convenience class representing a cached wallpaper bitmap and associated data. | |
| */ | |
| private static class CachedWallpaper { | |
| final Bitmap mCachedWallpaper; | |
| final int mCachedWallpaperUserId; | |
| @SetWallpaperFlags final int mWhich; | |
| CachedWallpaper(Bitmap cachedWallpaper, int cachedWallpaperUserId, | |
| @SetWallpaperFlags int which) { | |
| mCachedWallpaper = cachedWallpaper; | |
| mCachedWallpaperUserId = cachedWallpaperUserId; | |
| mWhich = which; | |
| } | |
| /** | |
| * Returns true if this object represents a valid cached bitmap for the given parameters, | |
| * otherwise false. | |
| */ | |
| boolean isValid(int userId, @SetWallpaperFlags int which) { | |
| return userId == mCachedWallpaperUserId && which == mWhich | |
| && !mCachedWallpaper.isRecycled(); | |
| } | |
| } | |
| private static class Globals extends IWallpaperManagerCallback.Stub { | |
| private final IWallpaperManager mService; | |
| private boolean mColorCallbackRegistered; | |
| private final ArrayList<Pair<OnColorsChangedListener, Handler>> mColorListeners = | |
| new ArrayList<>(); | |
| private CachedWallpaper mCachedWallpaper; | |
| private Bitmap mDefaultWallpaper; | |
| private Handler mMainLooperHandler; | |
| private ArrayMap<LocalWallpaperColorConsumer, ArraySet<RectF>> mLocalColorCallbackAreas = | |
| new ArrayMap<>(); | |
| private ILocalWallpaperColorConsumer mLocalColorCallback = | |
| new ILocalWallpaperColorConsumer.Stub() { | |
| @Override | |
| public void onColorsChanged(RectF area, WallpaperColors colors) { | |
| for (LocalWallpaperColorConsumer callback : | |
| mLocalColorCallbackAreas.keySet()) { | |
| ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback); | |
| if (areas != null && areas.contains(area)) { | |
| callback.onColorsChanged(area, colors); | |
| } | |
| } | |
| } | |
| }; | |
| Globals(IWallpaperManager service, Looper looper) { | |
| mService = service; | |
| mMainLooperHandler = new Handler(looper); | |
| forgetLoadedWallpaper(); | |
| } | |
| public void onWallpaperChanged() { | |
| /* The wallpaper has changed but we shouldn't eagerly load the | |
| * wallpaper as that would be inefficient. Reset the cached wallpaper | |
| * to null so if the user requests the wallpaper again then we'll | |
| * fetch it. | |
| */ | |
| forgetLoadedWallpaper(); | |
| } | |
| /** | |
| * Start listening to wallpaper color events. | |
| * Will be called whenever someone changes their wallpaper or if a live wallpaper | |
| * changes its colors. | |
| * @param callback Listener | |
| * @param handler Thread to call it from. Main thread if null. | |
| * @param userId Owner of the wallpaper or UserHandle.USER_ALL | |
| * @param displayId Caller comes from which display | |
| */ | |
| public void addOnColorsChangedListener(@NonNull OnColorsChangedListener callback, | |
| @Nullable Handler handler, int userId, int displayId) { | |
| synchronized (this) { | |
| if (!mColorCallbackRegistered) { | |
| try { | |
| mService.registerWallpaperColorsCallback(this, userId, displayId); | |
| mColorCallbackRegistered = true; | |
| } catch (RemoteException e) { | |
| // Failed, service is gone | |
| Log.w(TAG, "Can't register for color updates", e); | |
| } | |
| } | |
| mColorListeners.add(new Pair<>(callback, handler)); | |
| } | |
| } | |
| public void addOnColorsChangedListener( | |
| @NonNull LocalWallpaperColorConsumer callback, | |
| @NonNull List<RectF> regions, int which, int userId, int displayId) { | |
| synchronized (this) { | |
| for (RectF area : regions) { | |
| ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback); | |
| if (areas == null) { | |
| areas = new ArraySet<>(); | |
| mLocalColorCallbackAreas.put(callback, areas); | |
| } | |
| areas.add(area); | |
| } | |
| try { | |
| // one way returns immediately | |
| mService.addOnLocalColorsChangedListener(mLocalColorCallback, regions, which, | |
| userId, displayId); | |
| } catch (RemoteException e) { | |
| // Can't get colors, connection lost. | |
| Log.e(TAG, "Can't register for local color updates", e); | |
| } | |
| } | |
| } | |
| public void removeOnColorsChangedListener( | |
| @NonNull LocalWallpaperColorConsumer callback, int which, int userId, | |
| int displayId) { | |
| synchronized (this) { | |
| final ArraySet<RectF> removeAreas = mLocalColorCallbackAreas.remove(callback); | |
| if (removeAreas == null || removeAreas.size() == 0) { | |
| return; | |
| } | |
| for (LocalWallpaperColorConsumer cb : mLocalColorCallbackAreas.keySet()) { | |
| ArraySet<RectF> areas = mLocalColorCallbackAreas.get(cb); | |
| if (areas != null && cb != callback) removeAreas.removeAll(areas); | |
| } | |
| try { | |
| if (removeAreas.size() > 0) { | |
| // one way returns immediately | |
| mService.removeOnLocalColorsChangedListener( | |
| mLocalColorCallback, new ArrayList(removeAreas), which, userId, | |
| displayId); | |
| } | |
| } catch (RemoteException e) { | |
| // Can't get colors, connection lost. | |
| Log.e(TAG, "Can't unregister for local color updates", e); | |
| } | |
| } | |
| } | |
| /** | |
| * Stop listening to wallpaper color events. | |
| * | |
| * @param callback listener | |
| * @param userId Owner of the wallpaper or UserHandle.USER_ALL | |
| * @param displayId Which display is interested | |
| */ | |
| public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback, | |
| int userId, int displayId) { | |
| synchronized (this) { | |
| mColorListeners.removeIf(pair -> pair.first == callback); | |
| if (mColorListeners.size() == 0 && mColorCallbackRegistered) { | |
| mColorCallbackRegistered = false; | |
| try { | |
| mService.unregisterWallpaperColorsCallback(this, userId, displayId); | |
| } catch (RemoteException e) { | |
| // Failed, service is gone | |
| Log.w(TAG, "Can't unregister color updates", e); | |
| } | |
| } | |
| } | |
| } | |
| @Override | |
| public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) { | |
| synchronized (this) { | |
| for (Pair<OnColorsChangedListener, Handler> listener : mColorListeners) { | |
| Handler handler = listener.second; | |
| if (listener.second == null) { | |
| handler = mMainLooperHandler; | |
| } | |
| handler.post(() -> { | |
| // Dealing with race conditions between posting a callback and | |
| // removeOnColorsChangedListener being called. | |
| boolean stillExists; | |
| synchronized (sGlobals) { | |
| stillExists = mColorListeners.contains(listener); | |
| } | |
| if (stillExists) { | |
| listener.first.onColorsChanged(colors, which, userId); | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| WallpaperColors getWallpaperColors(int which, int userId, int displayId) { | |
| checkExactlyOneWallpaperFlagSet(which); | |
| try { | |
| return mService.getWallpaperColors(which, userId, displayId); | |
| } catch (RemoteException e) { | |
| // Can't get colors, connection lost. | |
| } | |
| return null; | |
| } | |
| public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault, | |
| @SetWallpaperFlags int which, ColorManagementProxy cmProxy) { | |
| return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(), | |
| false /* hardware */, cmProxy); | |
| } | |
| /** | |
| * Retrieves the current wallpaper Bitmap, caching the result. If this fails and | |
| * `returnDefault` is set, returns the Bitmap for the default wallpaper; otherwise returns | |
| * null. | |
| * | |
| * More sophisticated caching might a) store and compare the wallpaper ID so that | |
| * consecutive calls for FLAG_SYSTEM and FLAG_LOCK could return the cached wallpaper if | |
| * no lock screen wallpaper is set, or b) separately cache home and lock screen wallpaper. | |
| */ | |
| public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault, | |
| @SetWallpaperFlags int which, int userId, boolean hardware, | |
| ColorManagementProxy cmProxy) { | |
| if (mService != null) { | |
| try { | |
| Trace.beginSection("WPMS.isWallpaperSupported"); | |
| if (!mService.isWallpaperSupported(context.getOpPackageName())) { | |
| return null; | |
| } | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } finally { | |
| Trace.endSection(); | |
| } | |
| } | |
| synchronized (this) { | |
| if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which) && context | |
| .checkSelfPermission(READ_WALLPAPER_INTERNAL) == PERMISSION_GRANTED) { | |
| return mCachedWallpaper.mCachedWallpaper; | |
| } | |
| mCachedWallpaper = null; | |
| Bitmap currentWallpaper = null; | |
| try { | |
| Trace.beginSection("WPMS.getCurrentWallpaperLocked"); | |
| currentWallpaper = getCurrentWallpaperLocked( | |
| context, which, userId, hardware, cmProxy); | |
| } catch (OutOfMemoryError e) { | |
| Log.w(TAG, "Out of memory loading the current wallpaper: " + e); | |
| } catch (SecurityException e) { | |
| /* | |
| * Apps with target SDK <= S can still access the wallpaper through | |
| * READ_EXTERNAL_STORAGE. In T however, app that previously had access to the | |
| * wallpaper via READ_EXTERNAL_STORAGE will get a SecurityException here. | |
| * Thus, in T specifically, return the default wallpaper instead of crashing. | |
| */ | |
| if (CompatChanges.isChangeEnabled(RETURN_DEFAULT_ON_SECURITY_EXCEPTION) | |
| && !CompatChanges.isChangeEnabled(THROW_ON_SECURITY_EXCEPTION)) { | |
| Log.w(TAG, "No permission to access wallpaper, returning default" | |
| + " wallpaper to avoid crashing legacy app."); | |
| return getDefaultWallpaper(context, FLAG_SYSTEM); | |
| } | |
| if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { | |
| Log.w(TAG, "No permission to access wallpaper, suppressing" | |
| + " exception to avoid crashing legacy app."); | |
| } else { | |
| // Post-O apps really most sincerely need the permission. | |
| throw e; | |
| } | |
| } finally { | |
| Trace.endSection(); | |
| } | |
| if (currentWallpaper != null) { | |
| mCachedWallpaper = new CachedWallpaper(currentWallpaper, userId, which); | |
| return currentWallpaper; | |
| } | |
| } | |
| if (returnDefault || (which == FLAG_LOCK && isStaticWallpaper(FLAG_LOCK))) { | |
| return getDefaultWallpaper(context, which); | |
| } | |
| return null; | |
| } | |
| @Nullable | |
| public Rect peekWallpaperDimensions(Context context, boolean returnDefault, | |
| @SetWallpaperFlags int which, int userId) { | |
| if (mService != null) { | |
| try { | |
| if (!mService.isWallpaperSupported(context.getOpPackageName())) { | |
| return new Rect(); | |
| } | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| Rect dimensions = null; | |
| synchronized (this) { | |
| Bundle params = new Bundle(); | |
| try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( | |
| context.getOpPackageName(), context.getAttributionTag(), this, which, | |
| params, userId, /* getCropped = */ true)) { | |
| // Let's peek user wallpaper first. | |
| if (pfd != null) { | |
| BitmapFactory.Options options = new BitmapFactory.Options(); | |
| options.inJustDecodeBounds = true; | |
| BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options); | |
| dimensions = new Rect(0, 0, options.outWidth, options.outHeight); | |
| } | |
| } catch (RemoteException ex) { | |
| Log.w(TAG, "peek wallpaper dimensions failed", ex); | |
| } catch (IOException ignored) { | |
| // This is only thrown on close and can be safely ignored. | |
| } | |
| } | |
| // If user wallpaper is unavailable, may be the default one instead. | |
| if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0) | |
| && (returnDefault || (which == FLAG_LOCK && isStaticWallpaper(FLAG_LOCK)))) { | |
| InputStream is = openDefaultWallpaper(context, which); | |
| if (is != null) { | |
| try { | |
| BitmapFactory.Options options = new BitmapFactory.Options(); | |
| options.inJustDecodeBounds = true; | |
| BitmapFactory.decodeStream(is, null, options); | |
| dimensions = new Rect(0, 0, options.outWidth, options.outHeight); | |
| } finally { | |
| IoUtils.closeQuietly(is); | |
| } | |
| } | |
| } | |
| return dimensions; | |
| } | |
| void forgetLoadedWallpaper() { | |
| synchronized (this) { | |
| mCachedWallpaper = null; | |
| mDefaultWallpaper = null; | |
| } | |
| } | |
| private Bitmap getCurrentWallpaperLocked(Context context, @SetWallpaperFlags int which, | |
| int userId, boolean hardware, ColorManagementProxy cmProxy) { | |
| if (mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| return null; | |
| } | |
| try { | |
| Bundle params = new Bundle(); | |
| Trace.beginSection("WPMS.getWallpaperWithFeature_" + which); | |
| ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( | |
| context.getOpPackageName(), context.getAttributionTag(), this, which, | |
| params, userId, /* getCropped = */ true); | |
| Trace.endSection(); | |
| if (pfd == null) { | |
| return null; | |
| } | |
| try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { | |
| ImageDecoder.Source src = ImageDecoder.createSource(context.getResources(), is); | |
| return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> { | |
| // Mutable and hardware config can't be set at the same time. | |
| decoder.setMutableRequired(!hardware); | |
| // Let's do color management | |
| if (cmProxy != null) { | |
| cmProxy.doColorManagement(decoder, info); | |
| } | |
| })); | |
| } catch (OutOfMemoryError | IOException e) { | |
| Log.w(TAG, "Can't decode file", e); | |
| } | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| return null; | |
| } | |
| private Bitmap getDefaultWallpaper(Context context, @SetWallpaperFlags int which) { | |
| Trace.beginSection("WPMS.getDefaultWallpaper_" + which); | |
| Bitmap defaultWallpaper = mDefaultWallpaper; | |
| if (defaultWallpaper == null || defaultWallpaper.isRecycled()) { | |
| defaultWallpaper = null; | |
| Trace.beginSection("WPMS.openDefaultWallpaper"); | |
| try (InputStream is = openDefaultWallpaper(context, which)) { | |
| Trace.endSection(); | |
| if (is != null) { | |
| BitmapFactory.Options options = new BitmapFactory.Options(); | |
| Trace.beginSection("WPMS.decodeStream"); | |
| defaultWallpaper = BitmapFactory.decodeStream(is, null, options); | |
| Trace.endSection(); | |
| } | |
| } catch (OutOfMemoryError | IOException e) { | |
| Log.w(TAG, "Can't decode stream", e); | |
| } | |
| } | |
| synchronized (this) { | |
| mDefaultWallpaper = defaultWallpaper; | |
| } | |
| Trace.endSection(); | |
| return defaultWallpaper; | |
| } | |
| /** | |
| * Return true if there is a static wallpaper on the specified screen. | |
| * With {@code which=}{@link #FLAG_LOCK}, always return false if the lockscreen doesn't run | |
| * its own wallpaper engine. | |
| */ | |
| private boolean isStaticWallpaper(@SetWallpaperFlags int which) { | |
| if (mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| return mService.isStaticWallpaper(which); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| } | |
| static void initGlobals(IWallpaperManager service, Looper looper) { | |
| synchronized (sSync) { | |
| if (sGlobals == null) { | |
| sGlobals = new Globals(service, looper); | |
| } | |
| } | |
| } | |
| /*package*/ WallpaperManager(IWallpaperManager service, @UiContext Context context, | |
| Handler handler) { | |
| mContext = context; | |
| if (service != null) { | |
| initGlobals(service, context.getMainLooper()); | |
| } | |
| // Check if supports mixed color spaces composition in hardware. | |
| mWcgEnabled = context.getResources().getConfiguration().isScreenWideColorGamut() | |
| && context.getResources().getBoolean(R.bool.config_enableWcgMode); | |
| mCmProxy = new ColorManagementProxy(context); | |
| } | |
| // no-op constructor called just by DisabledWallpaperManager | |
| /*package*/ WallpaperManager() { | |
| mContext = null; | |
| mCmProxy = null; | |
| mWcgEnabled = false; | |
| } | |
| /** | |
| * Retrieve a WallpaperManager associated with the given Context. | |
| */ | |
| public static WallpaperManager getInstance(Context context) { | |
| return (WallpaperManager)context.getSystemService( | |
| Context.WALLPAPER_SERVICE); | |
| } | |
| /** @hide */ | |
| @UnsupportedAppUsage | |
| public IWallpaperManager getIWallpaperManager() { | |
| return sGlobals.mService; | |
| } | |
| /** | |
| * TODO (b/305908217) remove | |
| * Temporary method for project b/197814683. | |
| * @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image | |
| * @hide | |
| */ | |
| @TestApi | |
| public boolean isLockscreenLiveWallpaperEnabled() { | |
| return true; | |
| } | |
| /** | |
| * Temporary method for project b/270726737 | |
| * @return true if the wallpaper supports different crops for different display dimensions | |
| * @hide | |
| */ | |
| public static boolean isMultiCropEnabled() { | |
| if (sIsMultiCropEnabled == null) { | |
| sIsMultiCropEnabled = multiCrop(); | |
| } | |
| return sIsMultiCropEnabled; | |
| } | |
| /** | |
| * Indicate whether wcg (Wide Color Gamut) should be enabled. | |
| * <p> | |
| * Some devices lack of capability of mixed color spaces composition, | |
| * enable wcg on such devices might cause memory or battery concern. | |
| * <p> | |
| * Therefore, in addition to {@link Configuration#isScreenWideColorGamut()}, | |
| * we also take mixed color spaces composition (config_enableWcgMode) into account. | |
| * | |
| * @see Configuration#isScreenWideColorGamut() | |
| * @return True if wcg should be enabled for this device. | |
| * @hide | |
| */ | |
| @TestApi | |
| public boolean shouldEnableWideColorGamut() { | |
| return mWcgEnabled; | |
| } | |
| /** | |
| * <strong> Important note: </strong> | |
| * <ul> | |
| * <li>Up to Android 12, this method requires the | |
| * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> | |
| * <li>Starting in Android 13, directly accessing the wallpaper is not possible anymore, | |
| * instead the default system wallpaper is returned | |
| * (some versions of Android 13 may throw a {@code SecurityException}).</li> | |
| * <li>From Android 14, this method should not be used | |
| * and will always throw a {@code SecurityException}.</li> | |
| * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} | |
| * can still access the real wallpaper on all versions. </li> | |
| * </ul> | |
| * | |
| * <p> | |
| * Equivalent to {@link #getDrawable(int)} with {@code which=}{@link #FLAG_SYSTEM}. | |
| * </p> | |
| * | |
| * @return A Drawable object for the requested wallpaper. | |
| * | |
| * @see #getDrawable(int) | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public Drawable getDrawable() { | |
| return getDrawable(FLAG_SYSTEM); | |
| } | |
| /** | |
| * <strong> Important note: </strong> only apps with | |
| * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} should use this method. | |
| * Otherwise, a {@code SecurityException} will be thrown. | |
| * | |
| * <p> | |
| * Retrieve the requested wallpaper for the specified wallpaper type if the wallpaper is not | |
| * a live wallpaper. This method should not be used to display the user wallpaper on an app: | |
| * {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WALLPAPER} should be used instead. | |
| * </p> | |
| * <p> | |
| * When called with {@code which=}{@link #FLAG_SYSTEM}, | |
| * if there is a live wallpaper on home screen, the built-in default wallpaper is returned. | |
| * </p> | |
| * <p> | |
| * When called with {@code which=}{@link #FLAG_LOCK}, if there is a live wallpaper | |
| * on lock screen, or if the lock screen and home screen share the same wallpaper engine, | |
| * {@code null} is returned. | |
| * </p> | |
| * <p> | |
| * {@link #getWallpaperInfo(int)} can be used to determine whether there is a live wallpaper | |
| * on a specified screen type. | |
| * </p> | |
| * | |
| * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws | |
| * IllegalArgumentException if an invalid wallpaper is requested. | |
| * @return A Drawable object for the requested wallpaper. | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public Drawable getDrawable(@SetWallpaperFlags int which) { | |
| final ColorManagementProxy cmProxy = getColorManagementProxy(); | |
| boolean returnDefault = which != FLAG_LOCK; | |
| Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, returnDefault, which, cmProxy); | |
| if (bm != null) { | |
| Drawable dr = new BitmapDrawable(mContext.getResources(), bm); | |
| dr.setDither(false); | |
| return dr; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Obtain a drawable for the built-in static system wallpaper. | |
| */ | |
| public Drawable getBuiltInDrawable() { | |
| return getBuiltInDrawable(0, 0, false, 0, 0, FLAG_SYSTEM); | |
| } | |
| /** | |
| * Obtain a drawable for the specified built-in static system wallpaper. | |
| * | |
| * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws | |
| * IllegalArgumentException if an invalid wallpaper is requested. | |
| * @return A Drawable presenting the specified wallpaper image, or {@code null} | |
| * if no built-in default image for that wallpaper type exists. | |
| */ | |
| public Drawable getBuiltInDrawable(@SetWallpaperFlags int which) { | |
| return getBuiltInDrawable(0, 0, false, 0, 0, which); | |
| } | |
| /** | |
| * Returns a drawable for the system built-in static wallpaper. Based on the parameters, the | |
| * drawable can be cropped and scaled | |
| * | |
| * @param outWidth The width of the returned drawable | |
| * @param outWidth The height of the returned drawable | |
| * @param scaleToFit If true, scale the wallpaper down rather than just cropping it | |
| * @param horizontalAlignment A float value between 0 and 1 specifying where to crop the image; | |
| * 0 for left-aligned, 0.5 for horizontal center-aligned, and 1 for right-aligned | |
| * @param verticalAlignment A float value between 0 and 1 specifying where to crop the image; | |
| * 0 for top-aligned, 0.5 for vertical center-aligned, and 1 for bottom-aligned | |
| * @return A Drawable presenting the built-in default system wallpaper image, | |
| * or {@code null} if no such default image is defined on this device. | |
| */ | |
| public Drawable getBuiltInDrawable(int outWidth, int outHeight, | |
| boolean scaleToFit, float horizontalAlignment, float verticalAlignment) { | |
| return getBuiltInDrawable(outWidth, outHeight, scaleToFit, | |
| horizontalAlignment, verticalAlignment, FLAG_SYSTEM); | |
| } | |
| /** | |
| * Returns a drawable for the built-in static wallpaper of the specified type. Based on the | |
| * parameters, the drawable can be cropped and scaled. | |
| * | |
| * @param outWidth The width of the returned drawable | |
| * @param outWidth The height of the returned drawable | |
| * @param scaleToFit If true, scale the wallpaper down rather than just cropping it | |
| * @param horizontalAlignment A float value between 0 and 1 specifying where to crop the image; | |
| * 0 for left-aligned, 0.5 for horizontal center-aligned, and 1 for right-aligned | |
| * @param verticalAlignment A float value between 0 and 1 specifying where to crop the image; | |
| * 0 for top-aligned, 0.5 for vertical center-aligned, and 1 for bottom-aligned | |
| * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws | |
| * IllegalArgumentException if an invalid wallpaper is requested. | |
| * @return A Drawable presenting the built-in default wallpaper image of the given type, | |
| * or {@code null} if no default image of that type is defined on this device. | |
| */ | |
| public Drawable getBuiltInDrawable(int outWidth, int outHeight, boolean scaleToFit, | |
| float horizontalAlignment, float verticalAlignment, @SetWallpaperFlags int which) { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| checkExactlyOneWallpaperFlagSet(which); | |
| Resources resources = mContext.getResources(); | |
| horizontalAlignment = Math.max(0, Math.min(1, horizontalAlignment)); | |
| verticalAlignment = Math.max(0, Math.min(1, verticalAlignment)); | |
| InputStream wpStream = openDefaultWallpaper(mContext, which); | |
| if (wpStream == null) { | |
| if (DEBUG) { | |
| Log.w(TAG, "default wallpaper stream " + which + " is null"); | |
| } | |
| return null; | |
| } else { | |
| InputStream is = new BufferedInputStream(wpStream); | |
| if (outWidth <= 0 || outHeight <= 0) { | |
| Bitmap fullSize = BitmapFactory.decodeStream(is, null, null); | |
| return new BitmapDrawable(resources, fullSize); | |
| } else { | |
| int inWidth; | |
| int inHeight; | |
| // Just measure this time through... | |
| { | |
| BitmapFactory.Options options = new BitmapFactory.Options(); | |
| options.inJustDecodeBounds = true; | |
| BitmapFactory.decodeStream(is, null, options); | |
| if (options.outWidth != 0 && options.outHeight != 0) { | |
| inWidth = options.outWidth; | |
| inHeight = options.outHeight; | |
| } else { | |
| Log.e(TAG, "default wallpaper dimensions are 0"); | |
| return null; | |
| } | |
| } | |
| // Reopen the stream to do the full decode. We know at this point | |
| // that openDefaultWallpaper() will return non-null. | |
| is = new BufferedInputStream(openDefaultWallpaper(mContext, which)); | |
| RectF cropRectF; | |
| outWidth = Math.min(inWidth, outWidth); | |
| outHeight = Math.min(inHeight, outHeight); | |
| if (scaleToFit) { | |
| cropRectF = getMaxCropRect(inWidth, inHeight, outWidth, outHeight, | |
| horizontalAlignment, verticalAlignment); | |
| } else { | |
| float left = (inWidth - outWidth) * horizontalAlignment; | |
| float right = left + outWidth; | |
| float top = (inHeight - outHeight) * verticalAlignment; | |
| float bottom = top + outHeight; | |
| cropRectF = new RectF(left, top, right, bottom); | |
| } | |
| Rect roundedTrueCrop = new Rect(); | |
| cropRectF.roundOut(roundedTrueCrop); | |
| if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { | |
| Log.w(TAG, "crop has bad values for full size image"); | |
| return null; | |
| } | |
| // See how much we're reducing the size of the image | |
| int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / outWidth, | |
| roundedTrueCrop.height() / outHeight); | |
| // Attempt to open a region decoder | |
| BitmapRegionDecoder decoder = null; | |
| try { | |
| decoder = BitmapRegionDecoder.newInstance(is, true); | |
| } catch (IOException e) { | |
| Log.w(TAG, "cannot open region decoder for default wallpaper"); | |
| } | |
| Bitmap crop = null; | |
| if (decoder != null) { | |
| // Do region decoding to get crop bitmap | |
| BitmapFactory.Options options = new BitmapFactory.Options(); | |
| if (scaleDownSampleSize > 1) { | |
| options.inSampleSize = scaleDownSampleSize; | |
| } | |
| crop = decoder.decodeRegion(roundedTrueCrop, options); | |
| decoder.recycle(); | |
| } | |
| if (crop == null) { | |
| // BitmapRegionDecoder has failed, try to crop in-memory. We know at | |
| // this point that openDefaultWallpaper() will return non-null. | |
| is = new BufferedInputStream(openDefaultWallpaper(mContext, which)); | |
| Bitmap fullSize = null; | |
| BitmapFactory.Options options = new BitmapFactory.Options(); | |
| if (scaleDownSampleSize > 1) { | |
| options.inSampleSize = scaleDownSampleSize; | |
| } | |
| fullSize = BitmapFactory.decodeStream(is, null, options); | |
| if (fullSize != null) { | |
| crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, | |
| roundedTrueCrop.top, roundedTrueCrop.width(), | |
| roundedTrueCrop.height()); | |
| } | |
| } | |
| if (crop == null) { | |
| Log.w(TAG, "cannot decode default wallpaper"); | |
| return null; | |
| } | |
| // Scale down if necessary | |
| if (outWidth > 0 && outHeight > 0 && | |
| (crop.getWidth() != outWidth || crop.getHeight() != outHeight)) { | |
| Matrix m = new Matrix(); | |
| RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight()); | |
| RectF returnRect = new RectF(0, 0, outWidth, outHeight); | |
| m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); | |
| Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), | |
| (int) returnRect.height(), Bitmap.Config.ARGB_8888); | |
| if (tmp != null) { | |
| Canvas c = new Canvas(tmp); | |
| Paint p = new Paint(); | |
| p.setFilterBitmap(true); | |
| c.drawBitmap(crop, m, p); | |
| crop = tmp; | |
| } | |
| } | |
| return new BitmapDrawable(resources, crop); | |
| } | |
| } | |
| } | |
| private static RectF getMaxCropRect(int inWidth, int inHeight, int outWidth, int outHeight, | |
| float horizontalAlignment, float verticalAlignment) { | |
| RectF cropRect = new RectF(); | |
| // Get a crop rect that will fit this | |
| if (inWidth / (float) inHeight > outWidth / (float) outHeight) { | |
| cropRect.top = 0; | |
| cropRect.bottom = inHeight; | |
| float cropWidth = outWidth * (inHeight / (float) outHeight); | |
| cropRect.left = (inWidth - cropWidth) * horizontalAlignment; | |
| cropRect.right = cropRect.left + cropWidth; | |
| } else { | |
| cropRect.left = 0; | |
| cropRect.right = inWidth; | |
| float cropHeight = outHeight * (inWidth / (float) outWidth); | |
| cropRect.top = (inHeight - cropHeight) * verticalAlignment; | |
| cropRect.bottom = cropRect.top + cropHeight; | |
| } | |
| return cropRect; | |
| } | |
| /** | |
| * <strong> Important note: </strong> | |
| * <ul> | |
| * <li>Up to Android 12, this method requires the | |
| * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> | |
| * <li>Starting in Android 13, directly accessing the wallpaper is not possible anymore, | |
| * instead the default system wallpaper is returned | |
| * (some versions of Android 13 may throw a {@code SecurityException}).</li> | |
| * <li>From Android 14, this method should not be used | |
| * and will always throw a {@code SecurityException}.</li> | |
| * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} | |
| * can still access the real wallpaper on all versions. </li> | |
| * </ul> | |
| * | |
| * <p> | |
| * Equivalent to {@link #getDrawable()}. | |
| * </p> | |
| * | |
| * @return A Drawable object for the requested wallpaper. | |
| * | |
| * @see #getDrawable() | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public Drawable peekDrawable() { | |
| return peekDrawable(FLAG_SYSTEM); | |
| } | |
| /** | |
| * <strong> Important note: </strong> only apps with | |
| * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} should use this method. | |
| * Otherwise, a {@code SecurityException} will be thrown. | |
| * | |
| * <p> | |
| * Equivalent to {@link #getDrawable(int)}. | |
| * </p> | |
| * | |
| * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws | |
| * IllegalArgumentException if an invalid wallpaper is requested. | |
| * @return A Drawable object for the requested wallpaper. | |
| * | |
| * @see #getDrawable(int) | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public Drawable peekDrawable(@SetWallpaperFlags int which) { | |
| return getDrawable(which); | |
| } | |
| /** | |
| * <strong> Important note: </strong> | |
| * <ul> | |
| * <li>Up to Android 12, this method requires the | |
| * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> | |
| * <li>Starting in Android 13, directly accessing the wallpaper is not possible anymore, | |
| * instead the default wallpaper is returned | |
| * (some versions of Android 13 may throw a {@code SecurityException}).</li> | |
| * <li>From Android 14, this method should not be used | |
| * and will always throw a {@code SecurityException}.</li> | |
| * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} | |
| * can still access the real wallpaper on all versions. </li> | |
| * </ul> | |
| * | |
| * <p> | |
| * Equivalent to {@link #getFastDrawable(int)} with {@code which=}{@link #FLAG_SYSTEM}. | |
| * </p> | |
| * | |
| * @return A Drawable object for the requested wallpaper. | |
| * | |
| * @see #getFastDrawable(int) | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public Drawable getFastDrawable() { | |
| return getFastDrawable(FLAG_SYSTEM); | |
| } | |
| /** | |
| * <strong> Important note: </strong> only apps with | |
| * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} should use this method. | |
| * Otherwise, a {@code SecurityException} will be thrown. | |
| * | |
| * Like {@link #getDrawable(int)}, but the returned Drawable has a number | |
| * of limitations to reduce its overhead as much as possible. It will | |
| * never scale the wallpaper (only centering it if the requested bounds | |
| * do match the bitmap bounds, which should not be typical), doesn't | |
| * allow setting an alpha, color filter, or other attributes, etc. The | |
| * bounds of the returned drawable will be initialized to the same bounds | |
| * as the wallpaper, so normally you will not need to touch it. The | |
| * drawable also assumes that it will be used in a context running in | |
| * the same density as the screen (not in density compatibility mode). | |
| * | |
| * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws | |
| * IllegalArgumentException if an invalid wallpaper is requested. | |
| * @return An optimized Drawable object for the requested wallpaper, or {@code null} | |
| * in some cases as specified in {@link #getDrawable(int)}. | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public Drawable getFastDrawable(@SetWallpaperFlags int which) { | |
| final ColorManagementProxy cmProxy = getColorManagementProxy(); | |
| boolean returnDefault = which != FLAG_LOCK; | |
| Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, returnDefault, which, cmProxy); | |
| if (bm != null) { | |
| return new FastBitmapDrawable(bm); | |
| } | |
| return null; | |
| } | |
| /** | |
| * <strong> Important note: </strong> only apps with | |
| * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} should use this method. | |
| * Otherwise, a {@code SecurityException} will be thrown. | |
| * | |
| * <p> | |
| * Equivalent to {@link #getFastDrawable()}. | |
| * </p> | |
| * | |
| * @return An optimized Drawable object for the requested wallpaper. | |
| * | |
| * @see #getFastDrawable() | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public Drawable peekFastDrawable() { | |
| return peekFastDrawable(FLAG_SYSTEM); | |
| } | |
| /** | |
| * <strong> Important note: </strong> only apps with | |
| * {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} | |
| * should use this method. Otherwise, a {@code SecurityException} will be thrown. | |
| * | |
| * <p> | |
| * Equivalent to {@link #getFastDrawable(int)}. | |
| * </p> | |
| * | |
| * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws | |
| * IllegalArgumentException if an invalid wallpaper is requested. | |
| * @return An optimized Drawable object for the requested wallpaper. | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public Drawable peekFastDrawable(@SetWallpaperFlags int which) { | |
| return getFastDrawable(which); | |
| } | |
| /** | |
| * Whether the wallpaper supports Wide Color Gamut or not. This is only meant to be used by | |
| * ImageWallpaper, and will always return false if the wallpaper for the specified screen | |
| * is not an ImageWallpaper. This will also return false when called with {@link #FLAG_LOCK} if | |
| * the lock and home screen share the same wallpaper engine. | |
| * | |
| * @param which The wallpaper whose image file is to be retrieved. Must be a single | |
| * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. | |
| * @return true when supported. | |
| * | |
| * @see #FLAG_LOCK | |
| * @see #FLAG_SYSTEM | |
| * @hide | |
| */ | |
| @TestApi | |
| public boolean wallpaperSupportsWcg(int which) { | |
| if (!shouldEnableWideColorGamut()) { | |
| return false; | |
| } | |
| final ColorManagementProxy cmProxy = getColorManagementProxy(); | |
| Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy); | |
| return bitmap != null && bitmap.getColorSpace() != null | |
| && bitmap.getColorSpace() != ColorSpace.get(ColorSpace.Named.SRGB) | |
| && cmProxy.isSupportedColorSpace(bitmap.getColorSpace()); | |
| } | |
| /** | |
| * Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}. | |
| * | |
| * @hide | |
| */ | |
| @TestApi | |
| @Nullable | |
| @UnsupportedAppUsage | |
| public Bitmap getBitmap() { | |
| return getBitmap(false); | |
| } | |
| /** | |
| * Like {@link #getDrawable()} but returns a Bitmap. | |
| * | |
| * @param hardware Asks for a hardware backed bitmap. | |
| * @see Bitmap.Config#HARDWARE | |
| * @hide | |
| */ | |
| @UnsupportedAppUsage | |
| public Bitmap getBitmap(boolean hardware) { | |
| return getBitmapAsUser(mContext.getUserId(), hardware); | |
| } | |
| /** | |
| * Like {@link #getDrawable(int)} but returns a Bitmap. | |
| * | |
| * @param hardware Asks for a hardware backed bitmap. | |
| * @param which Specifies home or lock screen | |
| * @see Bitmap.Config#HARDWARE | |
| * @hide | |
| */ | |
| @Nullable | |
| public Bitmap getBitmap(boolean hardware, @SetWallpaperFlags int which) { | |
| return getBitmapAsUser(mContext.getUserId(), hardware, which); | |
| } | |
| /** | |
| * Like {@link #getDrawable()} but returns a Bitmap for the provided user. | |
| * | |
| * @hide | |
| */ | |
| public Bitmap getBitmapAsUser(int userId, boolean hardware) { | |
| final ColorManagementProxy cmProxy = getColorManagementProxy(); | |
| return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware, cmProxy); | |
| } | |
| /** | |
| * Like {@link #getDrawable(int)} but returns a Bitmap for the provided user. | |
| * | |
| * @param which Specifies home or lock screen | |
| * @hide | |
| */ | |
| @TestApi | |
| @Nullable | |
| public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) { | |
| boolean returnDefault = which != FLAG_LOCK; | |
| return getBitmapAsUser(userId, hardware, which, returnDefault); | |
| } | |
| /** | |
| * Overload of {@link #getBitmapAsUser(int, boolean, int)} with a returnDefault argument. | |
| * | |
| * @param returnDefault If true, return the default static wallpaper if no custom static | |
| * wallpaper is set on the specified screen. | |
| * If false, return {@code null} in that case. | |
| * @hide | |
| */ | |
| @Nullable | |
| public Bitmap getBitmapAsUser(int userId, boolean hardware, | |
| @SetWallpaperFlags int which, boolean returnDefault) { | |
| final ColorManagementProxy cmProxy = getColorManagementProxy(); | |
| return sGlobals.peekWallpaperBitmap(mContext, returnDefault, | |
| which, userId, hardware, cmProxy); | |
| } | |
| /** | |
| * Peek the dimensions of system wallpaper of the user without decoding it. | |
| * Equivalent to {@link #peekBitmapDimensions(int)} with {@code which=}{@link #FLAG_SYSTEM}. | |
| * | |
| * @return the dimensions of system wallpaper | |
| * @hide | |
| */ | |
| @TestApi | |
| @Nullable | |
| public Rect peekBitmapDimensions() { | |
| return peekBitmapDimensions(FLAG_SYSTEM); | |
| } | |
| /** | |
| * Peek the dimensions of given wallpaper of the user without decoding it. | |
| * | |
| * <p> | |
| * When called with {@code which=}{@link #FLAG_SYSTEM}, if there is a live wallpaper on | |
| * home screen, the built-in default wallpaper dimensions are returned. | |
| * </p> | |
| * <p> | |
| * When called with {@code which=}{@link #FLAG_LOCK}, if there is a live wallpaper | |
| * on lock screen, or if the lock screen and home screen share the same wallpaper engine, | |
| * {@code null} is returned. | |
| * </p> | |
| * <p> | |
| * {@link #getWallpaperInfo(int)} can be used to determine whether there is a live wallpaper | |
| * on a specified screen type. | |
| * </p> | |
| * | |
| * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. | |
| * @return the dimensions of specified wallpaper | |
| * @hide | |
| */ | |
| @TestApi | |
| @Nullable | |
| public Rect peekBitmapDimensions(@SetWallpaperFlags int which) { | |
| boolean returnDefault = which != FLAG_LOCK; | |
| return peekBitmapDimensions(which, returnDefault); | |
| } | |
| /** | |
| * Overload of {@link #peekBitmapDimensions(int)} with a returnDefault argument. | |
| * | |
| * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. | |
| * @param returnDefault If true, always return the default static wallpaper dimensions | |
| * if no custom static wallpaper is set on the specified screen. | |
| * If false, always return {@code null} in that case. | |
| * @return the dimensions of specified wallpaper | |
| * @hide | |
| */ | |
| @Nullable | |
| public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) { | |
| checkExactlyOneWallpaperFlagSet(which); | |
| return sGlobals.peekWallpaperDimensions(mContext, returnDefault, which, | |
| 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 { | |
| List<Rect> result = sGlobals.mService.getBitmapCrops( | |
| displaySizes, which, originalBitmap, mContext.getUserId()); | |
| if (result != null) return result; | |
| // mService.getBitmapCrops returns null if the requested wallpaper is an ImageWallpaper, | |
| // but there are no crop hints and the bitmap size is unknown to the service (this | |
| // mostly happens for the default wallpaper). In that case, fetch the bitmap dimensions | |
| // and use the other getBitmapCrops API with no cropHints to figure out the crops. | |
| Rect bitmapDimensions = peekBitmapDimensions(which, true); | |
| if (bitmapDimensions == null) return List.of(); | |
| Point bitmapSize = new Point(bitmapDimensions.width(), bitmapDimensions.height()); | |
| return getBitmapCrops(bitmapSize, displaySizes, null); | |
| } 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> | |
| * <li>Up to Android 12, this method requires the | |
| * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> | |
| * <li>Starting in Android 13, directly accessing the wallpaper is not possible anymore, | |
| * instead the default system wallpaper is returned | |
| * (some versions of Android 13 may throw a {@code SecurityException}).</li> | |
| * <li>From Android 14, this method should not be used | |
| * and will always throw a {@code SecurityException}.</li> | |
| * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} | |
| * can still access the real wallpaper on all versions. </li> | |
| * </ul> | |
| * <br> | |
| * | |
| * Get an open, readable file descriptor to the given wallpaper image file. | |
| * The caller is responsible for closing the file descriptor when done ingesting the file. | |
| * | |
| * <p>If no lock-specific wallpaper has been configured for the given user, then | |
| * this method will return {@code null} when requesting {@link #FLAG_LOCK} rather than | |
| * returning the system wallpaper's image file. | |
| * | |
| * @param which The wallpaper whose image file is to be retrieved. Must be a single | |
| * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or | |
| * {@link #FLAG_LOCK}. | |
| * @return An open, readable file descriptor to the requested wallpaper image file; | |
| * or {@code null} if no such wallpaper is configured or if the calling app does | |
| * not have permission to read the current wallpaper. | |
| * | |
| * @see #FLAG_LOCK | |
| * @see #FLAG_SYSTEM | |
| * | |
| * @throws SecurityException as described in the note | |
| */ | |
| @Nullable | |
| @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) | |
| public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which) { | |
| return getWallpaperFile(which, mContext.getUserId()); | |
| } | |
| /** | |
| * Registers a listener to get notified when the wallpaper colors change. | |
| * @param listener A listener to register | |
| * @param handler Where to call it from. Will be called from the main thread | |
| * if null. | |
| */ | |
| public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener, | |
| @NonNull Handler handler) { | |
| addOnColorsChangedListener(listener, handler, mContext.getUserId()); | |
| } | |
| /** | |
| * Registers a listener to get notified when the wallpaper colors change | |
| * @param listener A listener to register | |
| * @param handler Where to call it from. Will be called from the main thread | |
| * if null. | |
| * @param userId Owner of the wallpaper or UserHandle.USER_ALL. | |
| * @hide | |
| */ | |
| @UnsupportedAppUsage | |
| public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener, | |
| @NonNull Handler handler, int userId) { | |
| sGlobals.addOnColorsChangedListener(listener, handler, userId, mContext.getDisplayId()); | |
| } | |
| /** | |
| * Stop listening to color updates. | |
| * @param callback A callback to unsubscribe. | |
| */ | |
| public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback) { | |
| removeOnColorsChangedListener(callback, mContext.getUserId()); | |
| } | |
| /** | |
| * Stop listening to color updates. | |
| * @param callback A callback to unsubscribe. | |
| * @param userId Owner of the wallpaper or UserHandle.USER_ALL. | |
| * @hide | |
| */ | |
| public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback, | |
| int userId) { | |
| sGlobals.removeOnColorsChangedListener(callback, userId, mContext.getDisplayId()); | |
| } | |
| /** | |
| * Get the primary colors of a wallpaper. | |
| * | |
| * <p>This method can return {@code null} when: | |
| * <ul> | |
| * <li>Colors are still being processed by the system.</li> | |
| * <li>The user has chosen to use a live wallpaper: live wallpapers might not | |
| * implement | |
| * {@link android.service.wallpaper.WallpaperService.Engine#onComputeColors() | |
| * WallpaperService.Engine#onComputeColors()}.</li> | |
| * </ul> | |
| * <p>Please note that this API will go through IPC and may take some time to | |
| * calculate the wallpaper color, which could block the caller thread, so it is | |
| * not recommended to call this in the UI thread.</p> | |
| * | |
| * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or | |
| * {@link #FLAG_LOCK}. | |
| * @return Current {@link WallpaperColors} or null if colors are unknown. | |
| * @see #addOnColorsChangedListener(OnColorsChangedListener, Handler) | |
| */ | |
| public @Nullable WallpaperColors getWallpaperColors(int which) { | |
| return getWallpaperColors(which, mContext.getUserId()); | |
| } | |
| // TODO(b/181083333): add multiple root display area support on this API. | |
| /** | |
| * Get the primary colors of the wallpaper configured in the given user. | |
| * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or | |
| * {@link #FLAG_LOCK} | |
| * @param userId Owner of the wallpaper. | |
| * @return {@link WallpaperColors} or null if colors are unknown. | |
| * @hide | |
| */ | |
| @UnsupportedAppUsage | |
| public @Nullable WallpaperColors getWallpaperColors(int which, int userId) { | |
| StrictMode.assertUiContext(mContext, "getWallpaperColors"); | |
| return sGlobals.getWallpaperColors(which, userId, mContext.getDisplayId()); | |
| } | |
| /** | |
| * @hide | |
| */ | |
| public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback, | |
| List<RectF> regions, int which) throws IllegalArgumentException { | |
| for (RectF region : regions) { | |
| if (!LOCAL_COLOR_BOUNDS.contains(region)) { | |
| throw new IllegalArgumentException("Regions must be within bounds " | |
| + LOCAL_COLOR_BOUNDS); | |
| } | |
| } | |
| sGlobals.addOnColorsChangedListener(callback, regions, which, | |
| mContext.getUserId(), mContext.getDisplayId()); | |
| } | |
| /** | |
| * @hide | |
| */ | |
| public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) { | |
| sGlobals.removeOnColorsChangedListener(callback, FLAG_SYSTEM, mContext.getUserId(), | |
| mContext.getDisplayId()); | |
| } | |
| /** | |
| * Version of {@link #getWallpaperFile(int)} that can access the wallpaper data | |
| * for a given user. The caller must hold the INTERACT_ACROSS_USERS_FULL | |
| * permission to access another user's wallpaper data. | |
| * | |
| * @param which The wallpaper whose image file is to be retrieved. Must be a single | |
| * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or | |
| * {@link #FLAG_LOCK}. | |
| * @param userId The user or profile whose imagery is to be retrieved | |
| * | |
| * @see #FLAG_LOCK | |
| * @see #FLAG_SYSTEM | |
| * | |
| * @hide | |
| */ | |
| @UnsupportedAppUsage | |
| public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId) { | |
| return getWallpaperFile(which, userId, /* getCropped = */ true); | |
| } | |
| /** | |
| * Version of {@link #getWallpaperFile(int)} that allows specifying whether to get the | |
| * cropped version of the wallpaper file or the original. | |
| * | |
| * @param which The wallpaper whose image file is to be retrieved. Must be a single | |
| * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}. | |
| * @param getCropped If true the cropped file will be retrieved, if false the original will | |
| * be retrieved. | |
| * | |
| * @hide | |
| */ | |
| @Nullable | |
| public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, boolean getCropped) { | |
| return getWallpaperFile(which, mContext.getUserId(), getCropped); | |
| } | |
| private ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId, | |
| boolean getCropped) { | |
| checkExactlyOneWallpaperFlagSet(which); | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } else { | |
| try { | |
| Bundle outParams = new Bundle(); | |
| return sGlobals.mService.getWallpaperWithFeature(mContext.getOpPackageName(), | |
| mContext.getAttributionTag(), null, which, outParams, | |
| userId, getCropped); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } catch (SecurityException e) { | |
| if (CompatChanges.isChangeEnabled(RETURN_DEFAULT_ON_SECURITY_EXCEPTION) | |
| && !CompatChanges.isChangeEnabled(THROW_ON_SECURITY_EXCEPTION)) { | |
| Log.w(TAG, "No permission to access wallpaper, returning default" | |
| + " wallpaper file to avoid crashing legacy app."); | |
| return getDefaultSystemWallpaperFile(); | |
| } | |
| if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { | |
| Log.w(TAG, "No permission to access wallpaper, suppressing" | |
| + " exception to avoid crashing legacy app."); | |
| return null; | |
| } | |
| throw e; | |
| } | |
| } | |
| } | |
| /** | |
| * Remove all internal references to the last loaded wallpaper. Useful | |
| * for apps that want to reduce memory usage when they only temporarily | |
| * need to have the wallpaper. After calling, the next request for the | |
| * wallpaper will require reloading it again from disk. | |
| */ | |
| public void forgetLoadedWallpaper() { | |
| sGlobals.forgetLoadedWallpaper(); | |
| } | |
| /** | |
| * Returns the information about the home screen wallpaper if its current wallpaper is a live | |
| * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the | |
| * caller doesn't have the appropriate permissions, this returns {@code null}. | |
| * | |
| * <p> | |
| * For devices running Android 13 or earlier, this method requires the | |
| * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission. | |
| * </p> | |
| * | |
| * <p> | |
| * For devices running Android 14 or later, in order to use this, apps should declare a | |
| * {@code <queries>} tag with the action {@code "android.service.wallpaper.WallpaperService"}. | |
| * Otherwise, this method will return {@code null} if the caller doesn't otherwise have | |
| * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package. | |
| * </p> | |
| */ | |
| @RequiresPermission(value = "QUERY_ALL_PACKAGES", conditional = true) | |
| public WallpaperInfo getWallpaperInfo() { | |
| return getWallpaperInfoForUser(mContext.getUserId()); | |
| } | |
| /** | |
| * Returns the information about the home screen wallpaper if its current wallpaper is a live | |
| * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null. | |
| * | |
| * @param userId Owner of the wallpaper. | |
| * @hide | |
| */ | |
| public WallpaperInfo getWallpaperInfoForUser(int userId) { | |
| return getWallpaperInfo(FLAG_SYSTEM, userId); | |
| } | |
| /** | |
| * Returns the information about the designated wallpaper if its current wallpaper is a live | |
| * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if | |
| * the caller doesn't have the appropriate permissions, this returns {@code null}. | |
| * | |
| * <p> | |
| * In order to use this, apps should declare a {@code <queries>} tag with the action | |
| * {@code "android.service.wallpaper.WallpaperService"}. Otherwise, this method will return | |
| * {@code null} if the caller doesn't otherwise have | |
| * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package. | |
| * </p> | |
| * | |
| * @param which Specifies wallpaper to request (home or lock). | |
| * @throws IllegalArgumentException if {@code which} is not exactly one of | |
| * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}. | |
| */ | |
| @Nullable | |
| public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) { | |
| return getWallpaperInfo(which, mContext.getUserId()); | |
| } | |
| /** | |
| * Returns the information about the designated wallpaper if its current wallpaper is a live | |
| * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the | |
| * caller doesn't have the appropriate permissions, this returns {@code null}. | |
| * | |
| * <p> | |
| * In order to use this, apps should declare a {@code <queries>} tag | |
| * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise, | |
| * this method will return {@code null} if the caller doesn't otherwise have | |
| * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package. | |
| * </p> | |
| * | |
| * @param which Specifies wallpaper to request (home or lock). | |
| * @param userId Owner of the wallpaper. | |
| * @throws IllegalArgumentException if {@code which} is not exactly one of | |
| * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}. | |
| * @hide | |
| */ | |
| public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) { | |
| checkExactlyOneWallpaperFlagSet(which); | |
| try { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } else { | |
| return sGlobals.mService.getWallpaperInfoWithFlags(which, userId); | |
| } | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Get an open, readable file descriptor for the file that contains metadata about the | |
| * context user's wallpaper. | |
| * | |
| * The caller is responsible for closing the file descriptor when done ingesting the file. | |
| * | |
| * @hide | |
| */ | |
| @Nullable | |
| public ParcelFileDescriptor getWallpaperInfoFile() { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } else { | |
| try { | |
| return sGlobals.mService.getWallpaperInfoFile(mContext.getUserId()); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| } | |
| /** | |
| * Get the ID of the current wallpaper of the given kind. If there is no | |
| * such wallpaper configured, returns a negative number. | |
| * | |
| * <p>Every time the wallpaper image is set, a new ID is assigned to it. | |
| * This method allows the caller to determine whether the wallpaper imagery | |
| * has changed, regardless of how that change happened. | |
| * | |
| * @param which The wallpaper whose ID is to be returned. Must be a single | |
| * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or | |
| * {@link #FLAG_LOCK}. | |
| * @return The positive numeric ID of the current wallpaper of the given kind, | |
| * or a negative value if no such wallpaper is configured. | |
| */ | |
| public int getWallpaperId(@SetWallpaperFlags int which) { | |
| return getWallpaperIdForUser(which, mContext.getUserId()); | |
| } | |
| /** | |
| * Get the ID of the given user's current wallpaper of the given kind. If there | |
| * is no such wallpaper configured, returns a negative number. | |
| * @hide | |
| */ | |
| public int getWallpaperIdForUser(@SetWallpaperFlags int which, int userId) { | |
| try { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } else { | |
| return sGlobals.mService.getWallpaperIdForUser(which, userId); | |
| } | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Gets an Intent that will launch an activity that crops the given | |
| * image and sets the device's wallpaper. If there is a default HOME activity | |
| * that supports cropping wallpapers, it will be preferred as the default. | |
| * Use this method instead of directly creating a {@link #ACTION_CROP_AND_SET_WALLPAPER} | |
| * intent. | |
| * | |
| * @param imageUri The image URI that will be set in the intent. The must be a content | |
| * URI and its provider must resolve its type to "image/*" | |
| * | |
| * @throws IllegalArgumentException if the URI is not a content URI or its MIME type is | |
| * not "image/*" | |
| */ | |
| public Intent getCropAndSetWallpaperIntent(Uri imageUri) { | |
| if (imageUri == null) { | |
| throw new IllegalArgumentException("Image URI must not be null"); | |
| } | |
| if (!ContentResolver.SCHEME_CONTENT.equals(imageUri.getScheme())) { | |
| throw new IllegalArgumentException("Image URI must be of the " | |
| + ContentResolver.SCHEME_CONTENT + " scheme type"); | |
| } | |
| final PackageManager packageManager = mContext.getPackageManager(); | |
| Intent cropAndSetWallpaperIntent = | |
| new Intent(ACTION_CROP_AND_SET_WALLPAPER, imageUri); | |
| cropAndSetWallpaperIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | |
| // Find out if the default HOME activity supports CROP_AND_SET_WALLPAPER | |
| Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME); | |
| ResolveInfo resolvedHome = packageManager.resolveActivity(homeIntent, | |
| PackageManager.MATCH_DEFAULT_ONLY); | |
| if (resolvedHome != null) { | |
| cropAndSetWallpaperIntent.setPackage(resolvedHome.activityInfo.packageName); | |
| List<ResolveInfo> cropAppList = packageManager.queryIntentActivities( | |
| cropAndSetWallpaperIntent, 0); | |
| if (cropAppList.size() > 0) { | |
| return cropAndSetWallpaperIntent; | |
| } | |
| } | |
| // fallback crop activity | |
| final String cropperPackage = mContext.getString( | |
| com.android.internal.R.string.config_wallpaperCropperPackage); | |
| cropAndSetWallpaperIntent.setPackage(cropperPackage); | |
| List<ResolveInfo> cropAppList = packageManager.queryIntentActivities( | |
| cropAndSetWallpaperIntent, 0); | |
| if (cropAppList.size() > 0) { | |
| return cropAndSetWallpaperIntent; | |
| } | |
| // If the URI is not of the right type, or for some reason the system wallpaper | |
| // cropper doesn't exist, return null | |
| throw new IllegalArgumentException("Cannot use passed URI to set wallpaper; " + | |
| "check that the type returned by ContentProvider matches image/*"); | |
| } | |
| /** | |
| * Change the current system wallpaper to the bitmap in the given resource. | |
| * The resource is opened as a raw data stream and copied into the | |
| * wallpaper; it must be a valid PNG or JPEG image. On success, the intent | |
| * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. | |
| * | |
| * <p>This method requires the caller to hold the permission | |
| * {@link android.Manifest.permission#SET_WALLPAPER}. | |
| * | |
| * @param resid The resource ID of the bitmap to be used as the wallpaper image | |
| * | |
| * @throws IOException If an error occurs reverting to the built-in | |
| * wallpaper. | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public void setResource(@RawRes int resid) throws IOException { | |
| setResource(resid, FLAG_SYSTEM | FLAG_LOCK); | |
| } | |
| /** | |
| * Version of {@link #setResource(int)} that allows the caller to specify which | |
| * of the supported wallpaper categories to set. | |
| * | |
| * @param resid The resource ID of the bitmap to be used as the wallpaper image | |
| * @param which Flags indicating which wallpaper(s) to configure with the new imagery | |
| * | |
| * @see #FLAG_LOCK | |
| * @see #FLAG_SYSTEM | |
| * | |
| * @return An integer ID assigned to the newly active wallpaper; or zero on failure. | |
| * | |
| * @throws IOException | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public int setResource(@RawRes int resid, @SetWallpaperFlags int which) | |
| throws IOException { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| final Bundle result = new Bundle(); | |
| final WallpaperSetCompletion completion = new WallpaperSetCompletion(); | |
| try { | |
| Resources resources = mContext.getResources(); | |
| /* Set the wallpaper to the default values */ | |
| ParcelFileDescriptor fd = sGlobals.mService.setWallpaper( | |
| "res:" + resources.getResourceName(resid), | |
| mContext.getOpPackageName(), null, null, false, result, which, completion, | |
| mContext.getUserId()); | |
| if (fd != null) { | |
| FileOutputStream fos = null; | |
| try { | |
| fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); | |
| copyStreamToWallpaperFile(resources.openRawResource(resid), fos); | |
| // The 'close()' is the trigger for any server-side image manipulation, | |
| // so we must do that before waiting for completion. | |
| fos.close(); | |
| completion.waitForCompletion(); | |
| } finally { | |
| // Might be redundant but completion shouldn't wait unless the write | |
| // succeeded; this is a fallback if it threw past the close+wait. | |
| IoUtils.closeQuietly(fos); | |
| } | |
| } | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0); | |
| } | |
| /** | |
| * Change the current system wallpaper to a bitmap. The given bitmap is | |
| * converted to a PNG and stored as the wallpaper. On success, the intent | |
| * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. | |
| * | |
| * <p>This method is equivalent to calling | |
| * {@link #setBitmap(Bitmap, Rect, boolean)} and passing {@code null} for the | |
| * {@code visibleCrop} rectangle and {@code true} for the {@code allowBackup} | |
| * parameter. | |
| * | |
| * <p>This method requires the caller to hold the permission | |
| * {@link android.Manifest.permission#SET_WALLPAPER}. | |
| * | |
| * @param bitmap The bitmap to be used as the new system wallpaper. | |
| * | |
| * @throws IOException If an error occurs when attempting to set the wallpaper | |
| * to the provided image. | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public void setBitmap(Bitmap bitmap) throws IOException { | |
| setBitmap(bitmap, null, true); | |
| } | |
| /** | |
| * Change the current system wallpaper to a bitmap, specifying a hint about | |
| * which subrectangle of the full image is to be visible. The OS will then | |
| * try to best present the given portion of the full image as the static system | |
| * wallpaper image. On success, the intent | |
| * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. | |
| * | |
| * <p>Passing {@code null} as the {@code visibleHint} parameter is equivalent to | |
| * passing (0, 0, {@code fullImage.getWidth()}, {@code fullImage.getHeight()}). | |
| * | |
| * <p>This method requires the caller to hold the permission | |
| * {@link android.Manifest.permission#SET_WALLPAPER}. | |
| * | |
| * @param fullImage A bitmap that will supply the wallpaper imagery. | |
| * @param visibleCropHint The rectangular subregion of {@code fullImage} that should be | |
| * displayed as wallpaper. Passing {@code null} for this parameter means that | |
| * the full image should be displayed if possible given the image's and device's | |
| * aspect ratios, etc. | |
| * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper | |
| * image for restore to a future device; {@code false} otherwise. | |
| * | |
| * @return An integer ID assigned to the newly active wallpaper; or zero on failure. | |
| * | |
| * @throws IOException If an error occurs when attempting to set the wallpaper | |
| * to the provided image. | |
| * @throws IllegalArgumentException If the {@code visibleCropHint} rectangle is | |
| * empty or invalid. | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup) | |
| throws IOException { | |
| return setBitmap(fullImage, visibleCropHint, allowBackup, FLAG_SYSTEM | FLAG_LOCK); | |
| } | |
| /** | |
| * Version of {@link #setBitmap(Bitmap, Rect, boolean)} that allows the caller | |
| * to specify which of the supported wallpaper categories to set. | |
| * | |
| * @param fullImage A bitmap that will supply the wallpaper imagery. | |
| * @param visibleCropHint The rectangular subregion of {@code fullImage} that should be | |
| * displayed as wallpaper. Passing {@code null} for this parameter means that | |
| * the full image should be displayed if possible given the image's and device's | |
| * aspect ratios, etc. | |
| * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper | |
| * image for restore to a future device; {@code false} otherwise. | |
| * @param which Flags indicating which wallpaper(s) to configure with the new imagery. | |
| * | |
| * @see #FLAG_LOCK | |
| * @see #FLAG_SYSTEM | |
| * | |
| * @return An integer ID assigned to the newly active wallpaper; or zero on failure. | |
| * | |
| * @throws IOException | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public int setBitmap(Bitmap fullImage, Rect visibleCropHint, | |
| boolean allowBackup, @SetWallpaperFlags int which) | |
| throws IOException { | |
| return setBitmap(fullImage, visibleCropHint, allowBackup, which, | |
| mContext.getUserId()); | |
| } | |
| /** | |
| * Like {@link #setBitmap(Bitmap, Rect, boolean, int)}, but allows to pass in an explicit user | |
| * id. If the user id doesn't match the user id the process is running under, calling this | |
| * requires permission {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. | |
| * @hide | |
| */ | |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) | |
| 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"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| 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(), screenOrientations, 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); | |
| } | |
| private final void validateRect(Rect rect) { | |
| if (rect != null && rect.isEmpty()) { | |
| throw new IllegalArgumentException("visibleCrop rectangle must be valid and non-empty"); | |
| } | |
| } | |
| /** | |
| * Change the current system wallpaper to a specific byte stream. The | |
| * give InputStream is copied into persistent storage and will now be | |
| * used as the wallpaper. Currently it must be either a JPEG or PNG | |
| * image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} | |
| * is broadcast. | |
| * | |
| * <p>This method is equivalent to calling | |
| * {@link #setStream(InputStream, Rect, boolean)} and passing {@code null} for the | |
| * {@code visibleCrop} rectangle and {@code true} for the {@code allowBackup} | |
| * parameter. | |
| * | |
| * <p>This method requires the caller to hold the permission | |
| * {@link android.Manifest.permission#SET_WALLPAPER}. | |
| * | |
| * @param bitmapData A stream containing the raw data to install as a wallpaper. This | |
| * data can be in any format handled by {@link BitmapRegionDecoder}. | |
| * | |
| * @throws IOException If an error occurs when attempting to set the wallpaper | |
| * based on the provided image data. | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public void setStream(InputStream bitmapData) throws IOException { | |
| setStream(bitmapData, null, true); | |
| } | |
| private void copyStreamToWallpaperFile(InputStream data, FileOutputStream fos) | |
| throws IOException { | |
| FileUtils.copy(data, fos); | |
| } | |
| /** | |
| * Change the current system wallpaper to a specific byte stream, specifying a | |
| * hint about which subrectangle of the full image is to be visible. The OS will | |
| * then try to best present the given portion of the full image as the static system | |
| * wallpaper image. The data from the given InputStream is copied into persistent | |
| * storage and will then be used as the system wallpaper. Currently the data must | |
| * be either a JPEG or PNG image. On success, the intent | |
| * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. | |
| * | |
| * <p>This method requires the caller to hold the permission | |
| * {@link android.Manifest.permission#SET_WALLPAPER}. | |
| * | |
| * @param bitmapData A stream containing the raw data to install as a wallpaper. This | |
| * data can be in any format handled by {@link BitmapRegionDecoder}. | |
| * @param visibleCropHint The rectangular subregion of the streamed image that should be | |
| * displayed as wallpaper. Passing {@code null} for this parameter means that | |
| * the full image should be displayed if possible given the image's and device's | |
| * aspect ratios, etc. | |
| * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper | |
| * image for restore to a future device; {@code false} otherwise. | |
| * @return An integer ID assigned to the newly active wallpaper; or zero on failure. | |
| * | |
| * @see #getWallpaperId(int) | |
| * | |
| * @throws IOException If an error occurs when attempting to set the wallpaper | |
| * based on the provided image data. | |
| * @throws IllegalArgumentException If the {@code visibleCropHint} rectangle is | |
| * empty or invalid. | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup) | |
| throws IOException { | |
| return setStream(bitmapData, visibleCropHint, allowBackup, FLAG_SYSTEM | FLAG_LOCK); | |
| } | |
| /** | |
| * Version of {@link #setStream(InputStream, Rect, boolean)} that allows the caller | |
| * to specify which of the supported wallpaper categories to set. | |
| * | |
| * @param bitmapData A stream containing the raw data to install as a wallpaper. This | |
| * data can be in any format handled by {@link BitmapRegionDecoder}. | |
| * @param visibleCropHint The rectangular subregion of the streamed image that should be | |
| * displayed as wallpaper. Passing {@code null} for this parameter means that | |
| * the full image should be displayed if possible given the image's and device's | |
| * aspect ratios, etc. | |
| * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper | |
| * image for restore to a future device; {@code false} otherwise. | |
| * @param which Flags indicating which wallpaper(s) to configure with the new imagery. | |
| * @return An integer ID assigned to the newly active wallpaper; or zero on failure. | |
| * | |
| * @see #getWallpaperId(int) | |
| * @see #FLAG_LOCK | |
| * @see #FLAG_SYSTEM | |
| * | |
| * @throws IOException | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| 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"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| 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, 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); | |
| } | |
| /** | |
| * 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 | |
| * set through {@link #setResource(int)} with the same resource id. | |
| */ | |
| public boolean hasResourceWallpaper(@RawRes int resid) { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| Resources resources = mContext.getResources(); | |
| String name = "res:" + resources.getResourceName(resid); | |
| return sGlobals.mService.hasNamedWallpaper(name); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| // TODO(b/181083333): add multiple root display area support on this API. | |
| /** | |
| * Returns the desired minimum width for the wallpaper. Callers of | |
| * {@link #setBitmap(android.graphics.Bitmap)} or | |
| * {@link #setStream(java.io.InputStream)} should check this value | |
| * beforehand to make sure the supplied wallpaper respects the desired | |
| * minimum width. | |
| * | |
| * If the returned value is <= 0, the caller should use the width of | |
| * the default display instead. | |
| * | |
| * @return The desired minimum width for the wallpaper. This value should | |
| * be honored by applications that set the wallpaper but it is not | |
| * mandatory. | |
| * | |
| * @see #getDesiredMinimumHeight() | |
| */ | |
| public int getDesiredMinimumWidth() { | |
| StrictMode.assertUiContext(mContext, "getDesiredMinimumWidth"); | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| return sGlobals.mService.getWidthHint(mContext.getDisplayId()); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| // TODO(b/181083333): add multiple root display area support on this API. | |
| /** | |
| * Returns the desired minimum height for the wallpaper. Callers of | |
| * {@link #setBitmap(android.graphics.Bitmap)} or | |
| * {@link #setStream(java.io.InputStream)} should check this value | |
| * beforehand to make sure the supplied wallpaper respects the desired | |
| * minimum height. | |
| * | |
| * If the returned value is <= 0, the caller should use the height of | |
| * the default display instead. | |
| * | |
| * @return The desired minimum height for the wallpaper. This value should | |
| * be honored by applications that set the wallpaper but it is not | |
| * mandatory. | |
| * | |
| * @see #getDesiredMinimumWidth() | |
| */ | |
| public int getDesiredMinimumHeight() { | |
| StrictMode.assertUiContext(mContext, "getDesiredMinimumHeight"); | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| return sGlobals.mService.getHeightHint(mContext.getDisplayId()); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| // TODO(b/181083333): add multiple root display area support on this API. | |
| /** | |
| * For use only by the current home application, to specify the size of | |
| * wallpaper it would like to use. This allows such applications to have | |
| * a virtual wallpaper that is larger than the physical screen, matching | |
| * the size of their workspace. | |
| * | |
| * <p class="note">Calling this method from apps other than the active | |
| * home app is not guaranteed to work properly. Other apps that supply | |
| * wallpaper imagery should use {@link #getDesiredMinimumWidth()} and | |
| * {@link #getDesiredMinimumHeight()} and construct a wallpaper that | |
| * matches those dimensions. | |
| * | |
| * <p>This method requires the caller to hold the permission | |
| * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}. | |
| * | |
| * @param minimumWidth Desired minimum width | |
| * @param minimumHeight Desired minimum height | |
| */ | |
| public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) { | |
| StrictMode.assertUiContext(mContext, "suggestDesiredDimensions"); | |
| try { | |
| /** | |
| * The framework makes no attempt to limit the window size | |
| * to the maximum texture size. Any window larger than this | |
| * cannot be composited. | |
| * | |
| * Read maximum texture size from system property and scale down | |
| * minimumWidth and minimumHeight accordingly. | |
| */ | |
| int maximumTextureSize; | |
| try { | |
| maximumTextureSize = SystemProperties.getInt("sys.max_texture_size", 0); | |
| } catch (Exception e) { | |
| maximumTextureSize = 0; | |
| } | |
| if (maximumTextureSize > 0) { | |
| if ((minimumWidth > maximumTextureSize) || | |
| (minimumHeight > maximumTextureSize)) { | |
| float aspect = (float)minimumHeight / (float)minimumWidth; | |
| if (minimumWidth > minimumHeight) { | |
| minimumWidth = maximumTextureSize; | |
| minimumHeight = (int)((minimumWidth * aspect) + 0.5); | |
| } else { | |
| minimumHeight = maximumTextureSize; | |
| minimumWidth = (int)((minimumHeight / aspect) + 0.5); | |
| } | |
| } | |
| } | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } else { | |
| sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight, | |
| mContext.getOpPackageName(), mContext.getDisplayId()); | |
| } | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| // TODO(b/181083333): add multiple root display area support on this API. | |
| /** | |
| * Specify extra padding that the wallpaper should have outside of the display. | |
| * That is, the given padding supplies additional pixels the wallpaper should extend | |
| * outside of the display itself. | |
| * | |
| * <p>This method requires the caller to hold the permission | |
| * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}. | |
| * | |
| * @param padding The number of pixels the wallpaper should extend beyond the display, | |
| * on its left, top, right, and bottom sides. | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_HINTS) | |
| public void setDisplayPadding(Rect padding) { | |
| StrictMode.assertUiContext(mContext, "setDisplayPadding"); | |
| try { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } else { | |
| sGlobals.mService.setDisplayPadding(padding, mContext.getOpPackageName(), | |
| mContext.getDisplayId()); | |
| } | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Apply a raw offset to the wallpaper window. Should only be used in | |
| * combination with {@link #setDisplayPadding(android.graphics.Rect)} when you | |
| * have ensured that the wallpaper will extend outside of the display area so that | |
| * it can be moved without leaving part of the display uncovered. | |
| * @param x The offset, in pixels, to apply to the left edge. | |
| * @param y The offset, in pixels, to apply to the top edge. | |
| * @hide | |
| */ | |
| @SystemApi | |
| public void setDisplayOffset(IBinder windowToken, int x, int y) { | |
| try { | |
| //Log.v(TAG, "Sending new wallpaper display offsets from app..."); | |
| WindowManagerGlobal.getWindowSession().setWallpaperDisplayOffset( | |
| windowToken, x, y); | |
| //Log.v(TAG, "...app returning after sending display offset!"); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Equivalent to {@link #clear()}. | |
| * @see #clear() | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public void clearWallpaper() { | |
| clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId()); | |
| } | |
| /** | |
| * Clear the wallpaper for a specific user. | |
| * <ul> | |
| * <li> When called with {@code which=}{@link #FLAG_LOCK}, clear the lockscreen wallpaper. | |
| * The home screen wallpaper will become visible on the lock screen. </li> | |
| * | |
| * <li> When called with {@code which=}{@link #FLAG_SYSTEM}, revert the home screen | |
| * wallpaper to default. The lockscreen wallpaper will be unchanged: if the previous | |
| * wallpaper was shared between home and lock screen, it will become lock screen only. </li> | |
| * | |
| * <li> When called with {@code which=}({@link #FLAG_LOCK} | {@link #FLAG_SYSTEM}), put the | |
| * default wallpaper on both home and lock screen, removing any user defined wallpaper.</li> | |
| * </ul> | |
| * </p> | |
| * | |
| * The caller must hold the | |
| * INTERACT_ACROSS_USERS_FULL permission to clear another user's | |
| * wallpaper, and must hold the SET_WALLPAPER permission in all | |
| * circumstances. | |
| * @hide | |
| */ | |
| @SystemApi | |
| @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) | |
| public void clearWallpaper(@SetWallpaperFlags int which, int userId) { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| sGlobals.mService.clearWallpaper(mContext.getOpPackageName(), which, userId); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render | |
| * wallpaper, usually in order to set a live wallpaper. | |
| * | |
| * @param name Name of the component to use. | |
| * | |
| * @hide | |
| */ | |
| @SystemApi | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) | |
| public boolean setWallpaperComponent(ComponentName name) { | |
| return setWallpaperComponent(name, mContext.getUserId()); | |
| } | |
| /** | |
| * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default | |
| * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. | |
| * | |
| * @hide | |
| */ | |
| @SystemApi | |
| @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"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| sGlobals.mService.setWallpaperDimAmount(MathUtils.saturate(dimAmount)); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Gets the current additional dim amount set on the wallpaper. 0f means no application has | |
| * added any dimming on top of the system default dim amount. | |
| * | |
| * @hide | |
| */ | |
| @SystemApi | |
| @RequiresPermission(SET_WALLPAPER_DIM_AMOUNT) | |
| public @FloatRange (from = 0f, to = 1f) float getWallpaperDimAmount() { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| return sGlobals.mService.getWallpaperDimAmount(); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Whether the lock screen wallpaper is different from the system wallpaper. | |
| * | |
| * @hide | |
| */ | |
| public boolean lockScreenWallpaperExists() { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| return sGlobals.mService.lockScreenWallpaperExists(); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render | |
| * wallpaper, usually in order to set a live wallpaper. | |
| * | |
| * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT | |
| * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change | |
| * another user's wallpaper. | |
| * | |
| * @param name Name of the component to use. | |
| * @param userId User for whom the component should be set. | |
| * | |
| * @hide | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) | |
| @UnsupportedAppUsage | |
| public boolean setWallpaperComponent(ComponentName name, int userId) { | |
| return setWallpaperComponentWithFlags(name, FLAG_SYSTEM | FLAG_LOCK, userId); | |
| } | |
| /** | |
| * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render | |
| * wallpaper, usually in order to set a live wallpaper, for a given wallpaper destination. | |
| * | |
| * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT | |
| * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change | |
| * another user's wallpaper. | |
| * | |
| * @param name Name of the component to use. | |
| * @param which Specifies wallpaper destination (home and/or lock). | |
| * | |
| * @hide | |
| */ | |
| @SystemApi | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) | |
| public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name, | |
| @SetWallpaperFlags int which) { | |
| return setWallpaperComponentWithFlags(name, which, mContext.getUserId()); | |
| } | |
| /** | |
| * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render | |
| * wallpaper, usually in order to set a live wallpaper, for a given wallpaper destination. | |
| * | |
| * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT | |
| * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change | |
| * another user's wallpaper. | |
| * | |
| * @param name Name of the component to use. | |
| * @param which Specifies wallpaper destination (home and/or lock). | |
| * @param userId User for whom the component should be set. | |
| * | |
| * @hide | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) | |
| public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name, | |
| @SetWallpaperFlags int which, int userId) { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperManagerService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(), | |
| which, userId); | |
| return true; | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Set the display position of the current wallpaper within any larger space, when | |
| * that wallpaper is visible behind the given window. The X and Y offsets | |
| * are floating point numbers ranging from 0 to 1, representing where the | |
| * wallpaper should be positioned within the screen space. These only | |
| * make sense when the wallpaper is larger than the display. | |
| * | |
| * @param windowToken The window who these offsets should be associated | |
| * with, as returned by {@link android.view.View#getWindowToken() | |
| * View.getWindowToken()}. | |
| * @param xOffset The offset along the X dimension, from 0 to 1. | |
| * @param yOffset The offset along the Y dimension, from 0 to 1. | |
| */ | |
| public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { | |
| try { | |
| //Log.v(TAG, "Sending new wallpaper offsets from app..."); | |
| WindowManagerGlobal.getWindowSession().setWallpaperPosition( | |
| windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep); | |
| //Log.v(TAG, "...app returning after sending offsets!"); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * For applications that use multiple virtual screens showing a wallpaper, | |
| * specify the step size between virtual screens. For example, if the | |
| * launcher has 3 virtual screens, it would specify an xStep of 0.5, | |
| * since the X offset for those screens are 0.0, 0.5 and 1.0 | |
| * @param xStep The X offset delta from one screen to the next one | |
| * @param yStep The Y offset delta from one screen to the next one | |
| */ | |
| public void setWallpaperOffsetSteps(float xStep, float yStep) { | |
| mWallpaperXStep = xStep; | |
| mWallpaperYStep = yStep; | |
| } | |
| /** | |
| * Send an arbitrary command to the current active wallpaper. | |
| * | |
| * @param windowToken The window who these offsets should be associated | |
| * with, as returned by {@link android.view.View#getWindowToken() | |
| * View.getWindowToken()}. | |
| * @param action Name of the command to perform. This must be a scoped | |
| * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT". | |
| * @param x Arbitrary integer argument based on command. | |
| * @param y Arbitrary integer argument based on command. | |
| * @param z Arbitrary integer argument based on command. | |
| * @param extras Optional additional information for the command, or null. | |
| */ | |
| @RequiresPermission(value = android.Manifest.permission.ALWAYS_UPDATE_WALLPAPER, | |
| conditional = true) | |
| public void sendWallpaperCommand(IBinder windowToken, String action, | |
| int x, int y, int z, Bundle extras) { | |
| try { | |
| //Log.v(TAG, "Sending new wallpaper offsets from app..."); | |
| WindowManagerGlobal.getWindowSession().sendWallpaperCommand( | |
| windowToken, action, x, y, z, extras, false); | |
| //Log.v(TAG, "...app returning after sending offsets!"); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Set the current zoom out level of the wallpaper. | |
| * | |
| * @param windowToken window requesting wallpaper zoom. Zoom level will only be applier while | |
| * such window is visible. | |
| * @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in | |
| * | |
| * @hide | |
| */ | |
| @Keep | |
| @TestApi | |
| public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) { | |
| if (zoom < 0 || zoom > 1f) { | |
| throw new IllegalArgumentException("zoom must be between 0 and 1: " + zoom); | |
| } | |
| if (windowToken == null) { | |
| throw new IllegalArgumentException("windowToken must not be null"); | |
| } | |
| try { | |
| WindowManagerGlobal.getWindowSession().setWallpaperZoomOut(windowToken, zoom); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Returns whether wallpapers are supported for the calling user. If this function returns | |
| * {@code false}, any attempts to changing the wallpaper will have no effect, | |
| * and any attempt to obtain of the wallpaper will return {@code null}. | |
| */ | |
| public boolean isWallpaperSupported() { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } else { | |
| try { | |
| return sGlobals.mService.isWallpaperSupported(mContext.getOpPackageName()); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| } | |
| /** | |
| * Returns whether the calling package is allowed to set the wallpaper for the calling user. | |
| * If this function returns {@code false}, any attempts to change the wallpaper will have | |
| * no effect. Always returns {@code true} for device owner and profile owner. | |
| * | |
| * @see android.os.UserManager#DISALLOW_SET_WALLPAPER | |
| */ | |
| public boolean isSetWallpaperAllowed() { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } else { | |
| try { | |
| return sGlobals.mService.isSetWallpaperAllowed(mContext.getOpPackageName()); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| } | |
| /** | |
| * Clear the offsets previously associated with this window through | |
| * {@link #setWallpaperOffsets(IBinder, float, float)}. This reverts | |
| * the window to its default state, where it does not cause the wallpaper | |
| * to scroll from whatever its last offsets were. | |
| * | |
| * @param windowToken The window who these offsets should be associated | |
| * with, as returned by {@link android.view.View#getWindowToken() | |
| * View.getWindowToken()}. | |
| */ | |
| public void clearWallpaperOffsets(IBinder windowToken) { | |
| try { | |
| WindowManagerGlobal.getWindowSession().setWallpaperPosition( | |
| windowToken, -1, -1, -1, -1); | |
| } catch (RemoteException e) { | |
| throw e.rethrowFromSystemServer(); | |
| } | |
| } | |
| /** | |
| * Remove any currently set system wallpaper, reverting to the system's built-in | |
| * wallpaper. | |
| * On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. | |
| * | |
| * <p>This method requires the caller to hold the permission | |
| * {@link android.Manifest.permission#SET_WALLPAPER}. | |
| * | |
| * @throws IOException If an error occurs reverting to the built-in | |
| * wallpaper. | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public void clear() throws IOException { | |
| clear(FLAG_SYSTEM | FLAG_LOCK); | |
| } | |
| /** | |
| * Remove one or more currently set wallpapers, reverting to the system default | |
| * display for each one. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} | |
| * is broadcast. | |
| * <ul> | |
| * <li> When called with {@code which=}{@link #FLAG_LOCK}, clear the lockscreen wallpaper. | |
| * The home screen wallpaper will become visible on the lock screen. </li> | |
| * | |
| * <li> When called with {@code which=}{@link #FLAG_SYSTEM}, revert the home screen | |
| * wallpaper to default. The lockscreen wallpaper will be unchanged: if the previous | |
| * wallpaper was shared between home and lock screen, it will become lock screen only. </li> | |
| * | |
| * <li> When called with {@code which=}({@link #FLAG_LOCK} | {@link #FLAG_SYSTEM}), put the | |
| * default wallpaper on both home and lock screen, removing any user defined wallpaper.</li> | |
| * </ul> | |
| * | |
| * @param which A bitwise combination of {@link #FLAG_SYSTEM} or | |
| * {@link #FLAG_LOCK} | |
| * @throws IOException If an error occurs reverting to the built-in wallpaper. | |
| */ | |
| @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) | |
| public void clear(@SetWallpaperFlags int which) throws IOException { | |
| clearWallpaper(which, mContext.getUserId()); | |
| } | |
| /** | |
| * Open stream representing the default static image wallpaper. | |
| * | |
| * If the device defines no default wallpaper of the requested kind, | |
| * {@code null} is returned. | |
| * | |
| * @hide | |
| */ | |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) | |
| public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) { | |
| final String whichProp; | |
| final int defaultResId; | |
| /* Factory-default lock wallpapers are not yet supported. | |
| whichProp = which == FLAG_LOCK ? PROP_LOCK_WALLPAPER : PROP_WALLPAPER; | |
| defaultResId = which == FLAG_LOCK ? R.drawable.default_lock_wallpaper : .... | |
| */ | |
| whichProp = PROP_WALLPAPER; | |
| defaultResId = R.drawable.default_wallpaper; | |
| final String path = SystemProperties.get(whichProp); | |
| final InputStream wallpaperInputStream = getWallpaperInputStream(path); | |
| if (wallpaperInputStream != null) { | |
| return wallpaperInputStream; | |
| } | |
| final String cmfPath = getCmfWallpaperPath(); | |
| final InputStream cmfWallpaperInputStream = getWallpaperInputStream(cmfPath); | |
| if (cmfWallpaperInputStream != null) { | |
| return cmfWallpaperInputStream; | |
| } | |
| try { | |
| return context.getResources().openRawResource(defaultResId); | |
| } catch (NotFoundException e) { | |
| // no default defined for this device; this is not a failure | |
| } | |
| return null; | |
| } | |
| /** | |
| * util used in T to return a default system wallpaper file | |
| * when third party apps attempt to read the wallpaper with {@link #getWallpaperFile} | |
| */ | |
| private static ParcelFileDescriptor getDefaultSystemWallpaperFile() { | |
| for (String path: getDefaultSystemWallpaperPaths()) { | |
| File file = new File(path); | |
| if (file.exists()) { | |
| try { | |
| return ParcelFileDescriptor.open(file, MODE_READ_ONLY); | |
| } catch (FileNotFoundException e) { | |
| // continue; default wallpaper file not found on this path | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| private static InputStream getWallpaperInputStream(String path) { | |
| if (!TextUtils.isEmpty(path)) { | |
| final File file = new File(path); | |
| if (file.exists()) { | |
| try { | |
| return new FileInputStream(file); | |
| } catch (IOException e) { | |
| // Ignored, fall back to platform default | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * @return a list of paths to the system default wallpapers, in order of priority: | |
| * if the file exists for the first path of this list, the first path should be used. | |
| */ | |
| private static List<String> getDefaultSystemWallpaperPaths() { | |
| return List.of(SystemProperties.get(PROP_WALLPAPER), getCmfWallpaperPath()); | |
| } | |
| private static String getCmfWallpaperPath() { | |
| return Environment.getProductDirectory() + WALLPAPER_CMF_PATH + "default_wallpaper_" | |
| + VALUE_CMF_COLOR; | |
| } | |
| /** | |
| * Return {@link ComponentName} of the default live wallpaper, or | |
| * {@code null} if none is defined. | |
| * | |
| * @hide | |
| */ | |
| public static ComponentName getDefaultWallpaperComponent(Context context) { | |
| ComponentName cn = null; | |
| String flat = SystemProperties.get(PROP_WALLPAPER_COMPONENT); | |
| if (!TextUtils.isEmpty(flat)) { | |
| cn = ComponentName.unflattenFromString(flat); | |
| } | |
| if (cn == null) { | |
| flat = context.getString(com.android.internal.R.string.default_wallpaper_component); | |
| if (!TextUtils.isEmpty(flat)) { | |
| cn = ComponentName.unflattenFromString(flat); | |
| } | |
| } | |
| if (!isComponentExist(context, cn)) { | |
| cn = null; | |
| } | |
| return cn; | |
| } | |
| /** | |
| * Return {@link ComponentName} of the CMF default wallpaper, or | |
| * {@link #getDefaultWallpaperComponent(Context)} if none is defined. | |
| * | |
| * @hide | |
| */ | |
| public static ComponentName getCmfDefaultWallpaperComponent(Context context) { | |
| ComponentName cn = null; | |
| String[] cmfWallpaperMap = context.getResources().getStringArray( | |
| com.android.internal.R.array.default_wallpaper_component_per_device_color); | |
| if (cmfWallpaperMap != null && cmfWallpaperMap.length > 0) { | |
| for (String entry : cmfWallpaperMap) { | |
| String[] cmfWallpaper; | |
| if (!TextUtils.isEmpty(entry)) { | |
| cmfWallpaper = entry.split(","); | |
| if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals( | |
| cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) { | |
| cn = ComponentName.unflattenFromString(cmfWallpaper[1]); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if (!isComponentExist(context, cn)) { | |
| cn = null; | |
| } | |
| return cn == null ? getDefaultWallpaperComponent(context) : cn; | |
| } | |
| private static boolean isComponentExist(Context context, ComponentName cn) { | |
| if (cn == null) { | |
| return false; | |
| } | |
| try { | |
| final PackageManager packageManager = context.getPackageManager(); | |
| packageManager.getPackageInfo(cn.getPackageName(), | |
| PackageManager.MATCH_DIRECT_BOOT_AWARE | |
| | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); | |
| } catch (PackageManager.NameNotFoundException e) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Is the current system wallpaper eligible for backup? | |
| * | |
| * Only the OS itself may use this method. | |
| * @hide | |
| */ | |
| public boolean isWallpaperBackupEligible(int which) { | |
| if (sGlobals.mService == null) { | |
| Log.w(TAG, "WallpaperService not running"); | |
| throw new RuntimeException(new DeadSystemException()); | |
| } | |
| try { | |
| return sGlobals.mService.isWallpaperBackupEligible(which, mContext.getUserId()); | |
| } catch (RemoteException e) { | |
| Log.e(TAG, "Exception querying wallpaper backup eligibility: " + e.getMessage()); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Get the instance of {@link ColorManagementProxy}. | |
| * | |
| * @return instance of {@link ColorManagementProxy}. | |
| * @hide | |
| */ | |
| public ColorManagementProxy getColorManagementProxy() { | |
| return mCmProxy; | |
| } | |
| private static void checkExactlyOneWallpaperFlagSet(@SetWallpaperFlags int which) { | |
| if (which == FLAG_SYSTEM || which == FLAG_LOCK) { | |
| return; | |
| } | |
| throw new IllegalArgumentException("Must specify exactly one kind of wallpaper"); | |
| } | |
| /** | |
| * A hidden class to help {@link Globals#getCurrentWallpaperLocked} handle color management. | |
| * @hide | |
| */ | |
| public static class ColorManagementProxy { | |
| private final Set<ColorSpace> mSupportedColorSpaces = new HashSet<>(); | |
| public ColorManagementProxy(@NonNull Context context) { | |
| // Get a list of supported wide gamut color spaces. | |
| Display display = context.getDisplayNoVerify(); | |
| if (display != null) { | |
| mSupportedColorSpaces.addAll(Arrays.asList(display.getSupportedWideColorGamut())); | |
| } | |
| } | |
| @NonNull | |
| public Set<ColorSpace> getSupportedColorSpaces() { | |
| return mSupportedColorSpaces; | |
| } | |
| boolean isSupportedColorSpace(ColorSpace colorSpace) { | |
| return colorSpace != null && (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB) | |
| || getSupportedColorSpaces().contains(colorSpace)); | |
| } | |
| void doColorManagement(ImageDecoder decoder, ImageDecoder.ImageInfo info) { | |
| if (!isSupportedColorSpace(info.getColorSpace())) { | |
| decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)); | |
| Log.w(TAG, "Not supported color space: " + info.getColorSpace()); | |
| } | |
| } | |
| } | |
| // Private completion callback for setWallpaper() synchronization | |
| private class WallpaperSetCompletion extends IWallpaperManagerCallback.Stub { | |
| final CountDownLatch mLatch; | |
| public WallpaperSetCompletion() { | |
| mLatch = new CountDownLatch(1); | |
| } | |
| public void waitForCompletion() { | |
| try { | |
| final boolean completed = mLatch.await(30, TimeUnit.SECONDS); | |
| if (completed) { | |
| Log.d(TAG, "Wallpaper set completion."); | |
| } else { | |
| Log.d(TAG, "Timeout waiting for wallpaper set completion!"); | |
| } | |
| } catch (InterruptedException e) { | |
| // This might be legit: the crop may take a very long time. Don't sweat | |
| // it in that case; we are okay with display lagging behind in order to | |
| // keep the caller from locking up indeterminately. | |
| } | |
| } | |
| @Override | |
| public void onWallpaperChanged() throws RemoteException { | |
| mLatch.countDown(); | |
| } | |
| @Override | |
| public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) | |
| throws RemoteException { | |
| sGlobals.onWallpaperColorsChanged(colors, which, userId); | |
| } | |
| } | |
| /** | |
| * Interface definition for a callback to be invoked when colors change on a wallpaper. | |
| */ | |
| public interface OnColorsChangedListener { | |
| /** | |
| * Called when colors change. | |
| * A {@link android.app.WallpaperColors} object containing a simplified | |
| * color histogram will be given. | |
| * | |
| * @param colors Wallpaper color info, {@code null} when not available. | |
| * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM} | |
| * @see android.service.wallpaper.WallpaperService.Engine#onComputeColors() | |
| */ | |
| void onColorsChanged(@Nullable WallpaperColors colors, int which); | |
| /** | |
| * Called when colors change. | |
| * A {@link android.app.WallpaperColors} object containing a simplified | |
| * color histogram will be given. | |
| * | |
| * @param colors Wallpaper color info, {@code null} when not available. | |
| * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM} | |
| * @param userId Owner of the wallpaper | |
| * @see android.service.wallpaper.WallpaperService.Engine#onComputeColors() | |
| * @hide | |
| */ | |
| default void onColorsChanged(@Nullable WallpaperColors colors, int which, int userId) { | |
| onColorsChanged(colors, which); | |
| } | |
| } | |
| /** | |
| * Callback to update a consumer with a local color change | |
| * @hide | |
| */ | |
| public interface LocalWallpaperColorConsumer { | |
| /** | |
| * Gets called when a color of an area gets updated | |
| * @param area | |
| * @param colors | |
| */ | |
| void onColorsChanged(RectF area, WallpaperColors colors); | |
| } | |
| } |