Loading core/java/android/app/DisabledWallpaperManager.java +10 −0 Original line number Diff line number Diff line Loading @@ -177,6 +177,11 @@ final class DisabledWallpaperManager extends WallpaperManager { return unsupported(); } @Override public ParcelFileDescriptor getWallpaperFile(int which, boolean getCropped) { return unsupported(); } @Override public void forgetLoadedWallpaper() { unsupported(); Loading @@ -187,6 +192,11 @@ final class DisabledWallpaperManager extends WallpaperManager { return unsupported(); } @Override public ParcelFileDescriptor getWallpaperInfoFile() { return unsupported(); } @Override public WallpaperInfo getWallpaperInfoForUser(int userId) { return unsupported(); Loading core/java/android/app/IWallpaperManager.aidl +8 −1 Original line number Diff line number Diff line Loading @@ -74,7 +74,8 @@ interface IWallpaperManager { * Get the wallpaper for a given user. */ ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId, IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId); IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId, boolean getCropped); /** * Retrieve the given user's current wallpaper ID of the given kind. Loading @@ -95,6 +96,12 @@ interface IWallpaperManager { */ WallpaperInfo getWallpaperInfoWithFlags(int which, int userId); /** * Return a file descriptor for the file that contains metadata about the given user's * wallpaper. */ ParcelFileDescriptor getWallpaperInfoFile(int userId); /** * Clear the system wallpaper. */ Loading core/java/android/app/WallpaperManager.java +47 −3 Original line number Diff line number Diff line Loading @@ -640,7 +640,7 @@ public class WallpaperManager { Bundle params = new Bundle(); try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( context.getOpPackageName(), context.getAttributionTag(), this, which, params, userId)) { params, userId, /* getCropped = */ true)) { // Let's peek user wallpaper first. if (pfd != null) { BitmapFactory.Options options = new BitmapFactory.Options(); Loading Loading @@ -690,7 +690,7 @@ public class WallpaperManager { Bundle params = new Bundle(); ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( context.getOpPackageName(), context.getAttributionTag(), this, which, params, userId); params, userId, /* getCropped = */ true); if (pfd != null) { try (BufferedInputStream bis = new BufferedInputStream( Loading Loading @@ -1437,6 +1437,27 @@ public class WallpaperManager { */ @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) { Loading @@ -1446,7 +1467,8 @@ public class WallpaperManager { try { Bundle outParams = new Bundle(); return sGlobals.mService.getWallpaperWithFeature(mContext.getOpPackageName(), mContext.getAttributionTag(), null, which, outParams, userId); mContext.getAttributionTag(), null, which, outParams, userId, getCropped); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (SecurityException e) { Loading Loading @@ -1529,6 +1551,28 @@ public class WallpaperManager { } } /** * 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. Loading packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +172 −123 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ import android.content.SharedPreferences; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.graphics.Rect; import android.os.Environment; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; Loading @@ -43,8 +42,6 @@ import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import java.io.File; Loading @@ -52,39 +49,60 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * Backs up and restores wallpaper and metadata related to it. * * This agent has its own package because it does full backup as opposed to SystemBackupAgent * which does key/value backup. * * This class stages wallpaper files for backup by copying them into its own directory because of * the following reasons: * * <ul> * <li>Non-system users don't have permission to read the directory that the system stores * the wallpaper files in</li> * <li>{@link BackupAgent} enforces that backed up files must live inside the package's * {@link Context#getFilesDir()}</li> * </ul> * * There are 3 files to back up: * <ul> * <li>The "wallpaper info" file which contains metadata like the crop applied to the * wallpaper or the live wallpaper component name.</li> * <li>The "system" wallpaper file.</li> * <li>An optional "lock" wallpaper, which is shown on the lockscreen instead of the system * wallpaper if set.</li> * </ul> * * On restore, the metadata file is parsed and {@link WallpaperManager} APIs are used to set the * wallpaper. Note that if there's a live wallpaper, the live wallpaper package name will be * part of the metadata file and the wallpaper will be applied when the package it's installed. */ public class WallpaperBackupAgent extends BackupAgent { private static final String TAG = "WallpaperBackup"; private static final boolean DEBUG = false; // NB: must be kept in sync with WallpaperManagerService but has no // compile-time visibility. // Target filenames within the system's wallpaper directory static final String WALLPAPER = "wallpaper_orig"; static final String WALLPAPER_LOCK = "wallpaper_lock_orig"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; // Names of our local-data stage files @VisibleForTesting static final String SYSTEM_WALLPAPER_STAGE = "wallpaper-stage"; @VisibleForTesting static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage"; @VisibleForTesting static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage"; // Names of our local-data stage files/links static final String IMAGE_STAGE = "wallpaper-stage"; static final String LOCK_IMAGE_STAGE = "wallpaper-lock-stage"; static final String INFO_STAGE = "wallpaper-info-stage"; static final String EMPTY_SENTINEL = "empty"; static final String QUOTA_SENTINEL = "quota"; // Not-for-backup bookkeeping // Shared preferences constants. static final String PREFS_NAME = "wbprefs.xml"; static final String SYSTEM_GENERATION = "system_gen"; static final String LOCK_GENERATION = "lock_gen"; private File mWallpaperInfo; // wallpaper metadata file private File mWallpaperFile; // primary wallpaper image file private File mLockWallpaperFile; // lock wallpaper image file // If this file exists, it means we exceeded our quota last time private File mQuotaFile; private boolean mQuotaExceeded; private WallpaperManager mWm; private WallpaperManager mWallpaperManager; @Override public void onCreate() { Loading @@ -92,11 +110,7 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.v(TAG, "onCreate()"); } File wallpaperDir = getWallpaperDir(); mWallpaperInfo = new File(wallpaperDir, WALLPAPER_INFO); mWallpaperFile = new File(wallpaperDir, WALLPAPER); mLockWallpaperFile = new File(wallpaperDir, WALLPAPER_LOCK); mWm = (WallpaperManager) getSystemService(Context.WALLPAPER_SERVICE); mWallpaperManager = getSystemService(WallpaperManager.class); mQuotaFile = new File(getFilesDir(), QUOTA_SENTINEL); mQuotaExceeded = mQuotaFile.exists(); Loading @@ -105,120 +119,156 @@ public class WallpaperBackupAgent extends BackupAgent { } } @VisibleForTesting protected File getWallpaperDir() { return Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM); } @Override public void onFullBackup(FullBackupDataOutput data) throws IOException { // To avoid data duplication and disk churn, use links as the stage. final File filesDir = getFilesDir(); final File infoStage = new File(filesDir, INFO_STAGE); final File imageStage = new File (filesDir, IMAGE_STAGE); final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE); final File empty = new File (filesDir, EMPTY_SENTINEL); try { // We always back up this 'empty' file to ensure that the absence of // storable wallpaper imagery still produces a non-empty backup data // stream, otherwise it'd simply be ignored in preflight. final File empty = new File(getFilesDir(), EMPTY_SENTINEL); if (!empty.exists()) { FileOutputStream touch = new FileOutputStream(empty); touch.close(); } backupFile(empty, data); SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); final int lastSysGeneration = prefs.getInt(SYSTEM_GENERATION, -1); final int lastLockGeneration = prefs.getInt(LOCK_GENERATION, -1); SharedPreferences sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); final int sysGeneration = mWm.getWallpaperIdForUser(FLAG_SYSTEM, UserHandle.USER_SYSTEM); final int lockGeneration = mWm.getWallpaperIdForUser(FLAG_LOCK, UserHandle.USER_SYSTEM); // Check the IDs of the wallpapers that we backed up last time. If they haven't // changed, we won't re-stage them for backup and use the old staged versions to avoid // disk churn. final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1); final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1); final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM); final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK); final boolean sysChanged = (sysGeneration != lastSysGeneration); final boolean lockChanged = (lockGeneration != lastLockGeneration); final boolean sysEligible = mWm.isWallpaperBackupEligible(FLAG_SYSTEM); final boolean lockEligible = mWm.isWallpaperBackupEligible(FLAG_LOCK); // There might be a latent lock wallpaper file present but unused: don't // include it in the backup if that's the case. ParcelFileDescriptor lockFd = mWm.getWallpaperFile(FLAG_LOCK, UserHandle.USER_SYSTEM); final boolean hasLockWallpaper = (lockFd != null); IoUtils.closeQuietly(lockFd); if (DEBUG) { Slog.v(TAG, "sysGen=" + sysGeneration + " : sysChanged=" + sysChanged); Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged); Slog.v(TAG, "sysEligble=" + sysEligible); Slog.v(TAG, "lockEligible=" + lockEligible); Slog.v(TAG, "hasLockWallpaper=" + hasLockWallpaper); } // only back up the wallpapers if we've been told they're eligible if (mWallpaperInfo.exists()) { if (sysChanged || lockChanged || !infoStage.exists()) { backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data); backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data); backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data); } catch (Exception e) { Slog.e(TAG, "Unable to back up wallpaper", e); } finally { // Even if this time we had to back off on attempting to store the lock image // due to exceeding the data quota, try again next time. This will alternate // between "try both" and "only store the primary image" until either there // is no lock image to store, or the quota is raised, or both fit under the // quota. mQuotaFile.delete(); } } private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data) throws IOException { final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile(); if (wallpaperInfoFd == null) { Slog.w(TAG, "Wallpaper metadata file doesn't exist"); return; } final File infoStage = new File(getFilesDir(), WALLPAPER_INFO_STAGE); if (sysOrLockChanged || !infoStage.exists()) { if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying"); FileUtils.copyFileOrThrow(mWallpaperInfo, infoStage); copyFromPfdToFileAndClosePfd(wallpaperInfoFd, infoStage); } if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata"); backupFile(infoStage, data); } else { Slog.w(TAG, "Wallpaper metadata file doesn't exist: " + mWallpaperFile.getPath()); } if (sysEligible && mWallpaperFile.exists()) { private void backupSystemWallpaperFile(SharedPreferences sharedPrefs, boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException { if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) { Slog.d(TAG, "System wallpaper ineligible for backup"); return; } final ParcelFileDescriptor systemWallpaperImageFd = mWallpaperManager.getWallpaperFile( FLAG_SYSTEM, /* getCropped= */ false); if (systemWallpaperImageFd == null) { Slog.w(TAG, "System wallpaper doesn't exist"); return; } final File imageStage = new File(getFilesDir(), SYSTEM_WALLPAPER_STAGE); if (sysChanged || !imageStage.exists()) { if (DEBUG) Slog.v(TAG, "New system wallpaper; copying"); FileUtils.copyFileOrThrow(mWallpaperFile, imageStage); copyFromPfdToFileAndClosePfd(systemWallpaperImageFd, imageStage); } if (DEBUG) Slog.v(TAG, "Storing system wallpaper image"); backupFile(imageStage, data); prefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply(); } else { Slog.w(TAG, "Not backing up wallpaper as one of conditions is not " + "met: sysEligible = " + sysEligible + " wallpaperFileExists = " + mWallpaperFile.exists()); sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply(); } // If there's no lock wallpaper, then we have nothing to add to the backup. private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException { final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE); // This means there's no lock wallpaper set by the user. if (lockGeneration == -1) { if (lockChanged && lockImageStage.exists()) { if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting"); lockImageStage.delete(); } Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup"); prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); return; } if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) { Slog.d(TAG, "Lock screen wallpaper ineligible for backup"); return; } final ParcelFileDescriptor lockWallpaperFd = mWallpaperManager.getWallpaperFile( FLAG_LOCK, /* getCropped= */ false); // If we get to this point, that means lockGeneration != -1 so there's a lock wallpaper // set, but we can't find it. if (lockWallpaperFd == null) { Slog.w(TAG, "Lock wallpaper doesn't exist"); return; } if (mQuotaExceeded) { Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time"); return; } // Don't try to store the lock image if we overran our quota last time if (lockEligible && hasLockWallpaper && mLockWallpaperFile.exists() && !mQuotaExceeded) { if (lockChanged || !lockImageStage.exists()) { if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying"); FileUtils.copyFileOrThrow(mLockWallpaperFile, lockImageStage); copyFromPfdToFileAndClosePfd(lockWallpaperFd, lockImageStage); } if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image"); backupFile(lockImageStage, data); prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); } else { Slog.w(TAG, "Not backing up lockscreen wallpaper as one of conditions is not " + "met: lockEligible = " + lockEligible + " hasLockWallpaper = " + hasLockWallpaper + " lockWallpaperFileExists = " + mLockWallpaperFile.exists() + " quotaExceeded (last time) = " + mQuotaExceeded); sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); } } catch (Exception e) { Slog.e(TAG, "Unable to back up wallpaper", e); } finally { // Even if this time we had to back off on attempting to store the lock image // due to exceeding the data quota, try again next time. This will alternate // between "try both" and "only store the primary image" until either there // is no lock image to store, or the quota is raised, or both fit under the // quota. mQuotaFile.delete(); /** * Copies the contents of the given {@code pfd} to the given {@code file}. * * All resources used in the process including the {@code pfd} will be closed. */ private static void copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file) throws IOException { try (ParcelFileDescriptor.AutoCloseInputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); FileOutputStream outputStream = new FileOutputStream(file) ) { FileUtils.copy(inputStream, outputStream); } } Loading @@ -244,9 +294,9 @@ public class WallpaperBackupAgent extends BackupAgent { public void onRestoreFinished() { Slog.v(TAG, "onRestoreFinished()"); final File filesDir = getFilesDir(); final File infoStage = new File(filesDir, INFO_STAGE); final File imageStage = new File (filesDir, IMAGE_STAGE); final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE); final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE); final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE); final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE); // If we restored separate lock imagery, the system wallpaper should be // applied as system-only; but if there's no separate lock image, make Loading Loading @@ -283,11 +333,11 @@ public class WallpaperBackupAgent extends BackupAgent { void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException { if (servicePackageExists(wpService)) { Slog.i(TAG, "Using wallpaper service " + wpService); mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM); mWallpaperManager.setWallpaperComponent(wpService); if (applyToLock) { // We have a live wallpaper and no static lock image, // allow live wallpaper to show "through" on lock screen. mWm.clear(FLAG_LOCK); mWallpaperManager.clear(FLAG_LOCK); } } else { // If we've restored a live wallpaper, but the component doesn't exist, Loading @@ -311,8 +361,9 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.i(TAG, "Got restored wallpaper; applying which=" + which + "; cropHint = " + cropHint); try (FileInputStream in = new FileInputStream(stage)) { mWm.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); } finally {} // auto-closes 'in' mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); } } } else { Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath()); Loading Loading @@ -384,7 +435,7 @@ public class WallpaperBackupAgent extends BackupAgent { if (comp != null) { final IPackageManager pm = AppGlobals.getPackageManager(); final PackageInfo info = pm.getPackageInfo(comp.getPackageName(), 0, UserHandle.USER_SYSTEM); 0, getUserId()); return (info != null); } } catch (RemoteException e) { Loading @@ -393,16 +444,14 @@ public class WallpaperBackupAgent extends BackupAgent { return false; } // // Key/value API: abstract, therefore required; but not used // /** Unused Key/Value API. */ @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { // Intentionally blank } /** Unused Key/Value API. */ @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { Loading @@ -427,10 +476,10 @@ public class WallpaperBackupAgent extends BackupAgent { if (componentName.getPackageName().equals(packageName)) { Slog.d(TAG, "Applying component " + componentName); mWm.setWallpaperComponent(componentName); mWallpaperManager.setWallpaperComponent(componentName); if (applyToLock) { try { mWm.clear(FLAG_LOCK); mWallpaperManager.clear(FLAG_LOCK); } catch (IOException e) { Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e); } Loading packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +264 −74 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/app/DisabledWallpaperManager.java +10 −0 Original line number Diff line number Diff line Loading @@ -177,6 +177,11 @@ final class DisabledWallpaperManager extends WallpaperManager { return unsupported(); } @Override public ParcelFileDescriptor getWallpaperFile(int which, boolean getCropped) { return unsupported(); } @Override public void forgetLoadedWallpaper() { unsupported(); Loading @@ -187,6 +192,11 @@ final class DisabledWallpaperManager extends WallpaperManager { return unsupported(); } @Override public ParcelFileDescriptor getWallpaperInfoFile() { return unsupported(); } @Override public WallpaperInfo getWallpaperInfoForUser(int userId) { return unsupported(); Loading
core/java/android/app/IWallpaperManager.aidl +8 −1 Original line number Diff line number Diff line Loading @@ -74,7 +74,8 @@ interface IWallpaperManager { * Get the wallpaper for a given user. */ ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId, IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId); IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId, boolean getCropped); /** * Retrieve the given user's current wallpaper ID of the given kind. Loading @@ -95,6 +96,12 @@ interface IWallpaperManager { */ WallpaperInfo getWallpaperInfoWithFlags(int which, int userId); /** * Return a file descriptor for the file that contains metadata about the given user's * wallpaper. */ ParcelFileDescriptor getWallpaperInfoFile(int userId); /** * Clear the system wallpaper. */ Loading
core/java/android/app/WallpaperManager.java +47 −3 Original line number Diff line number Diff line Loading @@ -640,7 +640,7 @@ public class WallpaperManager { Bundle params = new Bundle(); try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( context.getOpPackageName(), context.getAttributionTag(), this, which, params, userId)) { params, userId, /* getCropped = */ true)) { // Let's peek user wallpaper first. if (pfd != null) { BitmapFactory.Options options = new BitmapFactory.Options(); Loading Loading @@ -690,7 +690,7 @@ public class WallpaperManager { Bundle params = new Bundle(); ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( context.getOpPackageName(), context.getAttributionTag(), this, which, params, userId); params, userId, /* getCropped = */ true); if (pfd != null) { try (BufferedInputStream bis = new BufferedInputStream( Loading Loading @@ -1437,6 +1437,27 @@ public class WallpaperManager { */ @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) { Loading @@ -1446,7 +1467,8 @@ public class WallpaperManager { try { Bundle outParams = new Bundle(); return sGlobals.mService.getWallpaperWithFeature(mContext.getOpPackageName(), mContext.getAttributionTag(), null, which, outParams, userId); mContext.getAttributionTag(), null, which, outParams, userId, getCropped); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (SecurityException e) { Loading Loading @@ -1529,6 +1551,28 @@ public class WallpaperManager { } } /** * 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. Loading
packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +172 −123 Original line number Diff line number Diff line Loading @@ -31,7 +31,6 @@ import android.content.SharedPreferences; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.graphics.Rect; import android.os.Environment; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; Loading @@ -43,8 +42,6 @@ import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import java.io.File; Loading @@ -52,39 +49,60 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * Backs up and restores wallpaper and metadata related to it. * * This agent has its own package because it does full backup as opposed to SystemBackupAgent * which does key/value backup. * * This class stages wallpaper files for backup by copying them into its own directory because of * the following reasons: * * <ul> * <li>Non-system users don't have permission to read the directory that the system stores * the wallpaper files in</li> * <li>{@link BackupAgent} enforces that backed up files must live inside the package's * {@link Context#getFilesDir()}</li> * </ul> * * There are 3 files to back up: * <ul> * <li>The "wallpaper info" file which contains metadata like the crop applied to the * wallpaper or the live wallpaper component name.</li> * <li>The "system" wallpaper file.</li> * <li>An optional "lock" wallpaper, which is shown on the lockscreen instead of the system * wallpaper if set.</li> * </ul> * * On restore, the metadata file is parsed and {@link WallpaperManager} APIs are used to set the * wallpaper. Note that if there's a live wallpaper, the live wallpaper package name will be * part of the metadata file and the wallpaper will be applied when the package it's installed. */ public class WallpaperBackupAgent extends BackupAgent { private static final String TAG = "WallpaperBackup"; private static final boolean DEBUG = false; // NB: must be kept in sync with WallpaperManagerService but has no // compile-time visibility. // Target filenames within the system's wallpaper directory static final String WALLPAPER = "wallpaper_orig"; static final String WALLPAPER_LOCK = "wallpaper_lock_orig"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; // Names of our local-data stage files @VisibleForTesting static final String SYSTEM_WALLPAPER_STAGE = "wallpaper-stage"; @VisibleForTesting static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage"; @VisibleForTesting static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage"; // Names of our local-data stage files/links static final String IMAGE_STAGE = "wallpaper-stage"; static final String LOCK_IMAGE_STAGE = "wallpaper-lock-stage"; static final String INFO_STAGE = "wallpaper-info-stage"; static final String EMPTY_SENTINEL = "empty"; static final String QUOTA_SENTINEL = "quota"; // Not-for-backup bookkeeping // Shared preferences constants. static final String PREFS_NAME = "wbprefs.xml"; static final String SYSTEM_GENERATION = "system_gen"; static final String LOCK_GENERATION = "lock_gen"; private File mWallpaperInfo; // wallpaper metadata file private File mWallpaperFile; // primary wallpaper image file private File mLockWallpaperFile; // lock wallpaper image file // If this file exists, it means we exceeded our quota last time private File mQuotaFile; private boolean mQuotaExceeded; private WallpaperManager mWm; private WallpaperManager mWallpaperManager; @Override public void onCreate() { Loading @@ -92,11 +110,7 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.v(TAG, "onCreate()"); } File wallpaperDir = getWallpaperDir(); mWallpaperInfo = new File(wallpaperDir, WALLPAPER_INFO); mWallpaperFile = new File(wallpaperDir, WALLPAPER); mLockWallpaperFile = new File(wallpaperDir, WALLPAPER_LOCK); mWm = (WallpaperManager) getSystemService(Context.WALLPAPER_SERVICE); mWallpaperManager = getSystemService(WallpaperManager.class); mQuotaFile = new File(getFilesDir(), QUOTA_SENTINEL); mQuotaExceeded = mQuotaFile.exists(); Loading @@ -105,120 +119,156 @@ public class WallpaperBackupAgent extends BackupAgent { } } @VisibleForTesting protected File getWallpaperDir() { return Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM); } @Override public void onFullBackup(FullBackupDataOutput data) throws IOException { // To avoid data duplication and disk churn, use links as the stage. final File filesDir = getFilesDir(); final File infoStage = new File(filesDir, INFO_STAGE); final File imageStage = new File (filesDir, IMAGE_STAGE); final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE); final File empty = new File (filesDir, EMPTY_SENTINEL); try { // We always back up this 'empty' file to ensure that the absence of // storable wallpaper imagery still produces a non-empty backup data // stream, otherwise it'd simply be ignored in preflight. final File empty = new File(getFilesDir(), EMPTY_SENTINEL); if (!empty.exists()) { FileOutputStream touch = new FileOutputStream(empty); touch.close(); } backupFile(empty, data); SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); final int lastSysGeneration = prefs.getInt(SYSTEM_GENERATION, -1); final int lastLockGeneration = prefs.getInt(LOCK_GENERATION, -1); SharedPreferences sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); final int sysGeneration = mWm.getWallpaperIdForUser(FLAG_SYSTEM, UserHandle.USER_SYSTEM); final int lockGeneration = mWm.getWallpaperIdForUser(FLAG_LOCK, UserHandle.USER_SYSTEM); // Check the IDs of the wallpapers that we backed up last time. If they haven't // changed, we won't re-stage them for backup and use the old staged versions to avoid // disk churn. final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1); final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1); final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM); final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK); final boolean sysChanged = (sysGeneration != lastSysGeneration); final boolean lockChanged = (lockGeneration != lastLockGeneration); final boolean sysEligible = mWm.isWallpaperBackupEligible(FLAG_SYSTEM); final boolean lockEligible = mWm.isWallpaperBackupEligible(FLAG_LOCK); // There might be a latent lock wallpaper file present but unused: don't // include it in the backup if that's the case. ParcelFileDescriptor lockFd = mWm.getWallpaperFile(FLAG_LOCK, UserHandle.USER_SYSTEM); final boolean hasLockWallpaper = (lockFd != null); IoUtils.closeQuietly(lockFd); if (DEBUG) { Slog.v(TAG, "sysGen=" + sysGeneration + " : sysChanged=" + sysChanged); Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged); Slog.v(TAG, "sysEligble=" + sysEligible); Slog.v(TAG, "lockEligible=" + lockEligible); Slog.v(TAG, "hasLockWallpaper=" + hasLockWallpaper); } // only back up the wallpapers if we've been told they're eligible if (mWallpaperInfo.exists()) { if (sysChanged || lockChanged || !infoStage.exists()) { backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data); backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data); backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data); } catch (Exception e) { Slog.e(TAG, "Unable to back up wallpaper", e); } finally { // Even if this time we had to back off on attempting to store the lock image // due to exceeding the data quota, try again next time. This will alternate // between "try both" and "only store the primary image" until either there // is no lock image to store, or the quota is raised, or both fit under the // quota. mQuotaFile.delete(); } } private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data) throws IOException { final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile(); if (wallpaperInfoFd == null) { Slog.w(TAG, "Wallpaper metadata file doesn't exist"); return; } final File infoStage = new File(getFilesDir(), WALLPAPER_INFO_STAGE); if (sysOrLockChanged || !infoStage.exists()) { if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying"); FileUtils.copyFileOrThrow(mWallpaperInfo, infoStage); copyFromPfdToFileAndClosePfd(wallpaperInfoFd, infoStage); } if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata"); backupFile(infoStage, data); } else { Slog.w(TAG, "Wallpaper metadata file doesn't exist: " + mWallpaperFile.getPath()); } if (sysEligible && mWallpaperFile.exists()) { private void backupSystemWallpaperFile(SharedPreferences sharedPrefs, boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException { if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) { Slog.d(TAG, "System wallpaper ineligible for backup"); return; } final ParcelFileDescriptor systemWallpaperImageFd = mWallpaperManager.getWallpaperFile( FLAG_SYSTEM, /* getCropped= */ false); if (systemWallpaperImageFd == null) { Slog.w(TAG, "System wallpaper doesn't exist"); return; } final File imageStage = new File(getFilesDir(), SYSTEM_WALLPAPER_STAGE); if (sysChanged || !imageStage.exists()) { if (DEBUG) Slog.v(TAG, "New system wallpaper; copying"); FileUtils.copyFileOrThrow(mWallpaperFile, imageStage); copyFromPfdToFileAndClosePfd(systemWallpaperImageFd, imageStage); } if (DEBUG) Slog.v(TAG, "Storing system wallpaper image"); backupFile(imageStage, data); prefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply(); } else { Slog.w(TAG, "Not backing up wallpaper as one of conditions is not " + "met: sysEligible = " + sysEligible + " wallpaperFileExists = " + mWallpaperFile.exists()); sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply(); } // If there's no lock wallpaper, then we have nothing to add to the backup. private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs, boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException { final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE); // This means there's no lock wallpaper set by the user. if (lockGeneration == -1) { if (lockChanged && lockImageStage.exists()) { if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting"); lockImageStage.delete(); } Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup"); prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); return; } if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) { Slog.d(TAG, "Lock screen wallpaper ineligible for backup"); return; } final ParcelFileDescriptor lockWallpaperFd = mWallpaperManager.getWallpaperFile( FLAG_LOCK, /* getCropped= */ false); // If we get to this point, that means lockGeneration != -1 so there's a lock wallpaper // set, but we can't find it. if (lockWallpaperFd == null) { Slog.w(TAG, "Lock wallpaper doesn't exist"); return; } if (mQuotaExceeded) { Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time"); return; } // Don't try to store the lock image if we overran our quota last time if (lockEligible && hasLockWallpaper && mLockWallpaperFile.exists() && !mQuotaExceeded) { if (lockChanged || !lockImageStage.exists()) { if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying"); FileUtils.copyFileOrThrow(mLockWallpaperFile, lockImageStage); copyFromPfdToFileAndClosePfd(lockWallpaperFd, lockImageStage); } if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image"); backupFile(lockImageStage, data); prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); } else { Slog.w(TAG, "Not backing up lockscreen wallpaper as one of conditions is not " + "met: lockEligible = " + lockEligible + " hasLockWallpaper = " + hasLockWallpaper + " lockWallpaperFileExists = " + mLockWallpaperFile.exists() + " quotaExceeded (last time) = " + mQuotaExceeded); sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply(); } } catch (Exception e) { Slog.e(TAG, "Unable to back up wallpaper", e); } finally { // Even if this time we had to back off on attempting to store the lock image // due to exceeding the data quota, try again next time. This will alternate // between "try both" and "only store the primary image" until either there // is no lock image to store, or the quota is raised, or both fit under the // quota. mQuotaFile.delete(); /** * Copies the contents of the given {@code pfd} to the given {@code file}. * * All resources used in the process including the {@code pfd} will be closed. */ private static void copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file) throws IOException { try (ParcelFileDescriptor.AutoCloseInputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); FileOutputStream outputStream = new FileOutputStream(file) ) { FileUtils.copy(inputStream, outputStream); } } Loading @@ -244,9 +294,9 @@ public class WallpaperBackupAgent extends BackupAgent { public void onRestoreFinished() { Slog.v(TAG, "onRestoreFinished()"); final File filesDir = getFilesDir(); final File infoStage = new File(filesDir, INFO_STAGE); final File imageStage = new File (filesDir, IMAGE_STAGE); final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE); final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE); final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE); final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE); // If we restored separate lock imagery, the system wallpaper should be // applied as system-only; but if there's no separate lock image, make Loading Loading @@ -283,11 +333,11 @@ public class WallpaperBackupAgent extends BackupAgent { void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException { if (servicePackageExists(wpService)) { Slog.i(TAG, "Using wallpaper service " + wpService); mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM); mWallpaperManager.setWallpaperComponent(wpService); if (applyToLock) { // We have a live wallpaper and no static lock image, // allow live wallpaper to show "through" on lock screen. mWm.clear(FLAG_LOCK); mWallpaperManager.clear(FLAG_LOCK); } } else { // If we've restored a live wallpaper, but the component doesn't exist, Loading @@ -311,8 +361,9 @@ public class WallpaperBackupAgent extends BackupAgent { Slog.i(TAG, "Got restored wallpaper; applying which=" + which + "; cropHint = " + cropHint); try (FileInputStream in = new FileInputStream(stage)) { mWm.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); } finally {} // auto-closes 'in' mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); } } } else { Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath()); Loading Loading @@ -384,7 +435,7 @@ public class WallpaperBackupAgent extends BackupAgent { if (comp != null) { final IPackageManager pm = AppGlobals.getPackageManager(); final PackageInfo info = pm.getPackageInfo(comp.getPackageName(), 0, UserHandle.USER_SYSTEM); 0, getUserId()); return (info != null); } } catch (RemoteException e) { Loading @@ -393,16 +444,14 @@ public class WallpaperBackupAgent extends BackupAgent { return false; } // // Key/value API: abstract, therefore required; but not used // /** Unused Key/Value API. */ @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { // Intentionally blank } /** Unused Key/Value API. */ @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { Loading @@ -427,10 +476,10 @@ public class WallpaperBackupAgent extends BackupAgent { if (componentName.getPackageName().equals(packageName)) { Slog.d(TAG, "Applying component " + componentName); mWm.setWallpaperComponent(componentName); mWallpaperManager.setWallpaperComponent(componentName); if (applyToLock) { try { mWm.clear(FLAG_LOCK); mWallpaperManager.clear(FLAG_LOCK); } catch (IOException e) { Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e); } Loading
packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +264 −74 File changed.Preview size limit exceeded, changes collapsed. Show changes