diff --git a/core/api/current.txt b/core/api/current.txt index e091b629d040a..604ac5815c64a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7575,23 +7575,23 @@ package android.app { method public android.content.Intent getCropAndSetWallpaperIntent(android.net.Uri); method public int getDesiredMinimumHeight(); method public int getDesiredMinimumWidth(); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable(); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable(int); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable(); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getDrawable(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getDrawable(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getFastDrawable(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getFastDrawable(int); method public static android.app.WallpaperManager getInstance(android.content.Context); method @Nullable public android.app.WallpaperColors getWallpaperColors(int); - method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.os.ParcelFileDescriptor getWallpaperFile(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int); method public int getWallpaperId(int); method public android.app.WallpaperInfo getWallpaperInfo(); method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int); method public boolean hasResourceWallpaper(@RawRes int); method public boolean isSetWallpaperAllowed(); method public boolean isWallpaperSupported(); - method @Nullable public android.graphics.drawable.Drawable peekDrawable(); - method @Nullable public android.graphics.drawable.Drawable peekDrawable(int); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable(); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekDrawable(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekDrawable(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable(int); method public void removeOnColorsChangedListener(@NonNull android.app.WallpaperManager.OnColorsChangedListener); method public void sendWallpaperCommand(android.os.IBinder, String, int, int, int, android.os.Bundle); method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void setBitmap(android.graphics.Bitmap) throws java.io.IOException; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 658a25dd1faac..5a84b9782ee63 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -476,7 +476,7 @@ package android.app { method @Nullable public android.graphics.Rect peekBitmapDimensions(int); method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float); method public boolean shouldEnableWideColorGamut(); - method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int); + method public boolean wallpaperSupportsWcg(int); } public class WindowConfiguration implements java.lang.Comparable android.os.Parcelable { diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index ff17824565967..540342b03f1ae 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -16,6 +16,11 @@ package android.app; +import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; +import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; + import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; @@ -28,6 +33,9 @@ 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; @@ -84,6 +92,7 @@ import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -108,8 +117,26 @@ import java.util.concurrent.TimeUnit; */ @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 = @@ -585,7 +612,8 @@ public class WallpaperManager { } } synchronized (this) { - if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which)) { + if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which) && context + .checkSelfPermission(READ_WALLPAPER_INTERNAL) == PERMISSION_GRANTED) { return mCachedWallpaper.mCachedWallpaper; } mCachedWallpaper = null; @@ -596,6 +624,19 @@ public class WallpaperManager { } 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."); @@ -808,6 +849,18 @@ public class WallpaperManager { } /** + * Important note: + * + *
+ * * Retrieve the current system wallpaper; if * no wallpaper is set, the system built-in static wallpaper is returned. * This is returned as an @@ -821,14 +874,28 @@ public class WallpaperManager { * @return Returns a Drawable object that will draw the system wallpaper, * or {@code null} if no system wallpaper exists or if the calling application * is not able to access the wallpaper. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getDrawable() { return getDrawable(FLAG_SYSTEM); } /** + * Important note: + * + *
+ * * Retrieve the requested wallpaper; if * no wallpaper is set, the requested built-in static wallpaper is returned. * This is returned as an @@ -844,9 +911,11 @@ public class WallpaperManager { * @return Returns a Drawable object that will draw the requested wallpaper, * or {@code null} if the requested wallpaper does not exist or if the calling application * is not able to access the wallpaper. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); @@ -1069,6 +1138,18 @@ public class WallpaperManager { } /** + * Important note: + * + *
+ * * Retrieve the current system wallpaper; if there is no wallpaper set, * a null pointer is returned. This is returned as an * abstract Drawable that you can install in a View to display whatever @@ -1076,13 +1157,28 @@ public class WallpaperManager { * * @return Returns a Drawable object that will draw the wallpaper or a * null pointer if wallpaper is unset. + * + * @throws SecurityException as described in the note */ @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekDrawable() { return peekDrawable(FLAG_SYSTEM); } /** + * Important note: + * + *
+ * * Retrieve the requested wallpaper; if there is no wallpaper set, * a null pointer is returned. This is returned as an * abstract Drawable that you can install in a View to display whatever @@ -1092,11 +1188,14 @@ public class WallpaperManager { * IllegalArgumentException if an invalid wallpaper is requested. * @return Returns a Drawable object that will draw the wallpaper or a null pointer if * wallpaper is unset. + * + * @throws SecurityException as described in the note */ @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); if (bm != null) { Drawable dr = new BitmapDrawable(mContext.getResources(), bm); dr.setDither(false); @@ -1106,6 +1205,18 @@ public class WallpaperManager { } /** + * Important note: + * + *
+ * * Like {@link #getDrawable()}, 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 @@ -1117,14 +1228,28 @@ public class WallpaperManager { * the same density as the screen (not in density compatibility mode). * * @return Returns a Drawable object that will draw the wallpaper. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getFastDrawable() { return getFastDrawable(FLAG_SYSTEM); } /** + * Important note: + * + *
+ * * 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 @@ -1138,9 +1263,11 @@ public class WallpaperManager { * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws * IllegalArgumentException if an invalid wallpaper is requested. * @return Returns a Drawable object that will draw the wallpaper. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getFastDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); @@ -1151,19 +1278,45 @@ public class WallpaperManager { } /** + * Important note: + * + *
+ * * Like {@link #getFastDrawable()}, but if there is no wallpaper set, * a null pointer is returned. * * @return Returns an optimized Drawable object that will draw the * wallpaper or a null pointer if these is none. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekFastDrawable() { return peekFastDrawable(FLAG_SYSTEM); } /** + * Important note: + * + *
+ * * Like {@link #getFastDrawable()}, but if there is no wallpaper set, * a null pointer is returned. * @@ -1171,12 +1324,14 @@ public class WallpaperManager { * IllegalArgumentException if an invalid wallpaper is requested. * @return Returns an optimized Drawable object that will draw the * wallpaper or a null pointer if these is none. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekFastDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); if (bm != null) { return new FastBitmapDrawable(bm); } @@ -1194,7 +1349,6 @@ public class WallpaperManager { * @hide */ @TestApi - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int which) { if (!shouldEnableWideColorGamut()) { return false; @@ -1295,6 +1449,18 @@ public class WallpaperManager { } /** + * Important note: + * + *
+ * * 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. * @@ -1305,14 +1471,17 @@ public class WallpaperManager { * @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 desriptor to the requested wallpaper image file; + * @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 */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) + @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which) { return getWallpaperFile(which, mContext.getUserId()); } @@ -1475,13 +1644,18 @@ public class WallpaperManager { } 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; - } else { - throw e; } + throw e; } } } @@ -2586,6 +2760,24 @@ public class WallpaperManager { 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); @@ -2600,6 +2792,14 @@ public class WallpaperManager { 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 getDefaultSystemWallpaperPaths() { + return List.of(SystemProperties.get(PROP_WALLPAPER), getCmfWallpaperPath()); + } + private static String getCmfWallpaperPath() { return Environment.getProductDirectory() + WALLPAPER_CMF_PATH + "default_wallpaper_" + VALUE_CMF_COLOR; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 06e91c3bebfa8..55edc245b7cee 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4038,8 +4038,7 @@ android:description="@string/permdesc_setWallpaperHints" android:protectionLevel="normal" /> - + diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index c9eef387eeb29..55060a677c8cf 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -90,7 +90,6 @@ import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.StorageManager; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.IWallpaperService; @@ -2210,12 +2209,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId, IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId, boolean getCropped) { - final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL); - if (!hasPrivilege) { - mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true, - Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId); - } - + checkPermission(READ_WALLPAPER_INTERNAL); wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null);