summaryrefslogtreecommitdiffstats
path: root/services/backup
diff options
context:
space:
mode:
authorChristopher Tate <ctate@google.com>2014-02-04 16:23:32 -0800
committerChristopher Tate <ctate@google.com>2014-03-20 12:30:51 -0700
commitadfe8b86e9178a553b6db9722340fa4ff5201cf1 (patch)
tree2054d99dc17b0ada61693b97d9f3e306b4fe4d4a /services/backup
parentaef4f6ebc8f8eb1f9fbfbe4ae2556c9f1a26a63c (diff)
downloadframeworks_base-adfe8b86e9178a553b6db9722340fa4ff5201cf1.zip
frameworks_base-adfe8b86e9178a553b6db9722340fa4ff5201cf1.tar.gz
frameworks_base-adfe8b86e9178a553b6db9722340fa4ff5201cf1.tar.bz2
App widget backup/restore infrastructure
Backup/restore now supports app widgets. An application involved with app widgets, either hosting or publishing, now has associated data in its backup dataset related to the state of widget instantiation on the ancestral device. That data is processed by the OS during restore so that the matching widget instances can be "automatically" regenerated. To take advantage of this facility, widget-using apps need to do two things: first, implement a backup agent and store whatever widget state they need to properly deal with them post-restore (e.g. the widget instance size & location, for a host); and second, implement handlers for new AppWidgetManager broadcasts that describe how to translate ancestral-dataset widget id numbers to the post-restore world. Note that a host or provider doesn't technically need to store *any* data on its own via its agent; it just needs to opt in to the backup/restore process by publishing an agent. The OS will then store a small amount of data on behalf of each widget-savvy app within the backup dataset, and act on that data at restore time. The broadcasts are AppWidgetManager.ACTION_APPWIDGET_RESTORED and ACTION_APPWIDGET_HOST_RESTORED, and have three associated extras: EXTRA_APPWIDGET_OLD_IDS EXTRA_APPWIDGET_IDS EXTRA_HOST_ID [for the host-side broadcast] The first two are same-sized arrays of integer widget IDs. The _OLD_IDS values are the widget IDs as known to the ancestral device. The _IDS array holds the corresponding widget IDs in the new post- restore environment. The app should simply update the stored widget IDs in its bookkeeping to the new values, and things are off and running. The HOST_ID extra, as one might expect, is the app-defined host ID value of the particular host instance which has just been restored. The broadcasts are sent following the conclusion of the overall restore pass. This is because the restore might have occurred in a tightly restricted lifecycle environment without content providers or the package's custom Application class. The _RESTORED broadcast, however, is always delivered into a normal application environment, so that the app can use its content provider etc as expected. *All* widget instances that were processed over the course of the system restore are indicated in the _RESTORED broadcast, even if the backing provider or host is not yet installed. The widget participant is responsible for understanding that these are promises that might be fulfilled later rather than necessarily reflecting the immediate presentable widget state. (Remember that following a cloud restore, apps may be installed piecemeal over a lengthy period of time.) Telling the hosts up front about all intended widget instances allows them to show placeholder UI or similarly useful information rather than surprising the user with piecemeal unexpected appearances. The AppWidgetProvider helper class has been updated to add a new callback, onRestored(...), invoked when the _RESTORED broadcast is received. The call to onRestored() is immediately followed by an invocation of onUpdate() for the affected widgets because they will need to have their RemoteViews regenerated under the new ID values. Bug 10622506 Bug 10707117 Change-Id: Ie0007cdf809600b880d91989c00c3c3b8a4f988b
Diffstat (limited to 'services/backup')
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java481
1 files changed, 390 insertions, 91 deletions
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8eaefef..1a1512f 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -24,6 +24,7 @@ import android.app.IApplicationThread;
import android.app.IBackupAgent;
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.FullBackup;
import android.app.backup.RestoreSet;
@@ -82,12 +83,14 @@ import android.util.StringBuilderPrinter;
import com.android.internal.backup.BackupConstants;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.backup.IObbBackupService;
+import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.SystemService;
import com.android.server.backup.PackageManagerBackupAgent.Metadata;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -115,10 +118,13 @@ import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
+import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
@@ -136,22 +142,43 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+
public class BackupManagerService extends IBackupManager.Stub {
private static final String TAG = "BackupManagerService";
private static final boolean DEBUG = true;
private static final boolean MORE_DEBUG = false;
+ // System-private key used for backing up an app's widget state. Must
+ // begin with U+FFxx by convention (we reserve all keys starting
+ // with U+FF00 or higher for system use).
+ static final String KEY_WIDGET_STATE = "\uffed\uffedwidget";
+
// Historical and current algorithm names
static final String PBKDF_CURRENT = "PBKDF2WithHmacSHA1";
static final String PBKDF_FALLBACK = "PBKDF2WithHmacSHA1And8bit";
// Name and current contents version of the full-backup manifest file
+ //
+ // Manifest version history:
+ //
+ // 1 : initial release
static final String BACKUP_MANIFEST_FILENAME = "_manifest";
static final int BACKUP_MANIFEST_VERSION = 1;
+
+ // External archive format version history:
+ //
+ // 1 : initial release
+ // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
+ // 3 : introduced "_meta" metadata file; no other format change per se
+ static final int BACKUP_FILE_VERSION = 3;
static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
- static final int BACKUP_FILE_VERSION = 2;
static final int BACKUP_PW_FILE_VERSION = 2;
+ static final String BACKUP_METADATA_FILENAME = "_meta";
+ static final int BACKUP_METADATA_VERSION = 1;
+ static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
@@ -186,6 +213,7 @@ public class BackupManagerService extends IBackupManager.Stub {
private static final int MSG_RUN_FULL_RESTORE = 10;
private static final int MSG_RETRY_INIT = 11;
private static final int MSG_RETRY_CLEAR = 12;
+ private static final int MSG_WIDGET_BROADCAST = 13;
// backup task state machine tick
static final int MSG_BACKUP_RESTORE_STEP = 20;
@@ -337,42 +365,47 @@ public class BackupManagerService extends IBackupManager.Stub {
public long token;
public PackageInfo pkgInfo;
public int pmToken; // in post-install restore, the PM's token for this transaction
- public boolean needFullBackup;
+ public boolean isSystemRestore;
public String[] filterSet;
+ // Restore a single package
RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
- long _token, PackageInfo _pkg, int _pmToken, boolean _needFullBackup) {
+ long _token, PackageInfo _pkg, int _pmToken) {
transport = _transport;
dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = _pkg;
pmToken = _pmToken;
- needFullBackup = _needFullBackup;
+ isSystemRestore = false;
filterSet = null;
}
+ // Restore everything possible. This is the form that Setup Wizard or similar
+ // restore UXes use.
RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
- long _token, boolean _needFullBackup) {
+ long _token) {
transport = _transport;
dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = null;
pmToken = 0;
- needFullBackup = _needFullBackup;
+ isSystemRestore = true;
filterSet = null;
}
+ // Restore some set of packages. Leave this one up to the caller to specify
+ // whether it's to be considered a system-level restore.
RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
- long _token, String[] _filterSet, boolean _needFullBackup) {
+ long _token, String[] _filterSet, boolean _isSystemRestore) {
transport = _transport;
dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = null;
pmToken = 0;
- needFullBackup = _needFullBackup;
+ isSystemRestore = _isSystemRestore;
filterSet = _filterSet;
}
}
@@ -413,16 +446,19 @@ public class BackupManagerService extends IBackupManager.Stub {
public boolean includeApks;
public boolean includeObbs;
public boolean includeShared;
+ public boolean doWidgets;
public boolean allApps;
public boolean includeSystem;
public String[] packages;
FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
- boolean saveShared, boolean doAllApps, boolean doSystem, String[] pkgList) {
+ boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem,
+ String[] pkgList) {
fd = output;
includeApks = saveApks;
includeObbs = saveObbs;
includeShared = saveShared;
+ doWidgets = alsoWidgets;
allApps = doAllApps;
includeSystem = doSystem;
packages = pkgList;
@@ -618,7 +654,8 @@ public class BackupManagerService extends IBackupManager.Stub {
FullBackupParams params = (FullBackupParams)msg.obj;
PerformFullBackupTask task = new PerformFullBackupTask(params.fd,
params.observer, params.includeApks, params.includeObbs,
- params.includeShared, params.curPassword, params.encryptPassword,
+ params.includeShared, params.doWidgets,
+ params.curPassword, params.encryptPassword,
params.allApps, params.includeSystem, params.packages, params.latch);
(new Thread(task)).start();
break;
@@ -631,7 +668,7 @@ public class BackupManagerService extends IBackupManager.Stub {
PerformRestoreTask task = new PerformRestoreTask(
params.transport, params.dirName, params.observer,
params.token, params.pkgInfo, params.pmToken,
- params.needFullBackup, params.filterSet);
+ params.isSystemRestore, params.filterSet);
Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
sendMessage(restoreMsg);
break;
@@ -770,6 +807,13 @@ public class BackupManagerService extends IBackupManager.Stub {
}
break;
}
+
+ case MSG_WIDGET_BROADCAST:
+ {
+ final Intent intent = (Intent) msg.obj;
+ mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ break;
+ }
}
}
}
@@ -2360,10 +2404,40 @@ public class BackupManagerService extends IBackupManager.Stub {
@Override
public void operationComplete() {
- // Okay, the agent successfully reported back to us. Spin the data off to the
+ // Okay, the agent successfully reported back to us. The next thing we do is
+ // push the app widget state for the app, if any.
+ final String pkgName = mCurrentPackage.packageName;
+ final long filepos = mBackupDataName.length();
+ FileDescriptor fd = mBackupData.getFileDescriptor();
+ try {
+ BackupDataOutput out = new BackupDataOutput(fd);
+ byte[] widgetState = AppWidgetBackupBridge.getWidgetState(pkgName,
+ UserHandle.USER_OWNER);
+ if (widgetState != null) {
+ out.writeEntityHeader(KEY_WIDGET_STATE, widgetState.length);
+ out.writeEntityData(widgetState, widgetState.length);
+ } else {
+ // No widget state for this app, but push a 'delete' operation for it
+ // in case they're trying to play games with the payload.
+ out.writeEntityHeader(KEY_WIDGET_STATE, -1);
+ }
+ } catch (IOException e) {
+ // Hard disk error; recovery/failure policy TBD. For now roll back,
+ // but we may want to consider this a transport-level failure (i.e.
+ // we're in such a bad state that we can't contemplate doing backup
+ // operations any more during this pass).
+ Slog.w(TAG, "Unable to save widget state for " + pkgName);
+ try {
+ Libcore.os.ftruncate(fd, filepos);
+ } catch (ErrnoException ee) {
+ Slog.w(TAG, "Unable to roll back!");
+ }
+ }
+
+ // Spin the data off to the
// transport and proceed with the next stage.
if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
- + mCurrentPackage.packageName);
+ + pkgName);
mBackupHandler.removeMessages(MSG_TIMEOUT);
clearAgentState();
addBackupTrace("operation complete");
@@ -2402,17 +2476,14 @@ public class BackupManagerService extends IBackupManager.Stub {
if (mStatus == BackupConstants.TRANSPORT_OK) {
mBackupDataName.delete();
mNewStateName.renameTo(mSavedStateName);
- EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE,
- mCurrentPackage.packageName, size);
- logBackupComplete(mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
+ logBackupComplete(pkgName);
} else {
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
- mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
}
} catch (Exception e) {
- Slog.e(TAG, "Transport error backing up " + mCurrentPackage.packageName, e);
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
- mCurrentPackage.packageName);
+ Slog.e(TAG, "Transport error backing up " + pkgName, e);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
mStatus = BackupConstants.TRANSPORT_ERROR;
} finally {
try { if (backupData != null) backupData.close(); } catch (IOException e) {}
@@ -2631,18 +2702,21 @@ public class BackupManagerService extends IBackupManager.Stub {
boolean mIncludeApks;
boolean mIncludeObbs;
boolean mIncludeShared;
+ boolean mDoWidgets;
boolean mAllApps;
final boolean mIncludeSystem;
- String[] mPackages;
+ ArrayList<String> mPackages;
String mCurrentPassword;
String mEncryptPassword;
AtomicBoolean mLatchObject;
File mFilesDir;
File mManifestFile;
+ File mMetadataFile;
class FullBackupRunner implements Runnable {
PackageInfo mPackage;
+ byte[] mWidgetData;
IBackupAgent mAgent;
ParcelFileDescriptor mPipe;
int mToken;
@@ -2650,8 +2724,10 @@ public class BackupManagerService extends IBackupManager.Stub {
boolean mWriteManifest;
FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe,
- int token, boolean sendApk, boolean writeManifest) throws IOException {
+ int token, boolean sendApk, boolean writeManifest, byte[] widgetData)
+ throws IOException {
mPackage = pack;
+ mWidgetData = widgetData;
mAgent = agent;
mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
mToken = token;
@@ -2666,12 +2742,24 @@ public class BackupManagerService extends IBackupManager.Stub {
mPipe.getFileDescriptor());
if (mWriteManifest) {
+ final boolean writeWidgetData = mWidgetData != null;
if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
- writeAppManifest(mPackage, mManifestFile, mSendApk);
+ writeAppManifest(mPackage, mManifestFile, mSendApk, writeWidgetData);
FullBackup.backupToTar(mPackage.packageName, null, null,
mFilesDir.getAbsolutePath(),
mManifestFile.getAbsolutePath(),
output);
+ mManifestFile.delete();
+
+ // We only need to write a metadata file if we have widget data to stash
+ if (writeWidgetData) {
+ writeMetadata(mPackage, mMetadataFile, mWidgetData);
+ FullBackup.backupToTar(mPackage.packageName, null, null,
+ mFilesDir.getAbsolutePath(),
+ mMetadataFile.getAbsolutePath(),
+ output);
+ mMetadataFile.delete();
+ }
}
if (mSendApk) {
@@ -2696,16 +2784,19 @@ public class BackupManagerService extends IBackupManager.Stub {
PerformFullBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
boolean includeApks, boolean includeObbs, boolean includeShared,
- String curPassword, String encryptPassword, boolean doAllApps,
+ boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
boolean doSystem, String[] packages, AtomicBoolean latch) {
mOutputFile = fd;
mObserver = observer;
mIncludeApks = includeApks;
mIncludeObbs = includeObbs;
mIncludeShared = includeShared;
+ mDoWidgets = doWidgets;
mAllApps = doAllApps;
mIncludeSystem = doSystem;
- mPackages = packages;
+ mPackages = (packages == null)
+ ? new ArrayList<String>()
+ : new ArrayList<String>(Arrays.asList(packages));
mCurrentPassword = curPassword;
// when backing up, if there is a current backup password, we require that
// the user use a nonempty encryption password as well. if one is supplied
@@ -2720,13 +2811,28 @@ public class BackupManagerService extends IBackupManager.Stub {
mFilesDir = new File("/data/system");
mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
+ mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
+ }
+
+ void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
+ for (String pkgName : pkgNames) {
+ if (!set.containsKey(pkgName)) {
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(pkgName,
+ PackageManager.GET_SIGNATURES);
+ set.put(pkgName, info);
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
+ }
+ }
+ }
}
@Override
public void run() {
Slog.i(TAG, "--- Performing full-dataset backup ---");
- List<PackageInfo> packagesToBackup = new ArrayList<PackageInfo>();
+ TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>();
FullBackupObbConnection obbConnection = new FullBackupObbConnection();
obbConnection.establish(); // we'll want this later
@@ -2734,18 +2840,35 @@ public class BackupManagerService extends IBackupManager.Stub {
// doAllApps supersedes the package set if any
if (mAllApps) {
- packagesToBackup = mPackageManager.getInstalledPackages(
+ List<PackageInfo> allPackages = mPackageManager.getInstalledPackages(
PackageManager.GET_SIGNATURES);
- // Exclude system apps if we've been asked to do so
- if (mIncludeSystem == false) {
- for (int i = 0; i < packagesToBackup.size(); ) {
- PackageInfo pkg = packagesToBackup.get(i);
- if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- packagesToBackup.remove(i);
- } else {
- i++;
+ for (int i = 0; i < allPackages.size(); i++) {
+ PackageInfo pkg = allPackages.get(i);
+ // Exclude system apps if we've been asked to do so
+ if (mIncludeSystem == true
+ || ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) {
+ packagesToBackup.put(pkg.packageName, pkg);
+ }
+ }
+ }
+
+ // If we're doing widget state as well, ensure that we have all the involved
+ // host & provider packages in the set
+ if (mDoWidgets) {
+ List<String> pkgs =
+ AppWidgetBackupBridge.getWidgetParticipants(UserHandle.USER_OWNER);
+ if (pkgs != null) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Adding widget participants to backup set:");
+ StringBuilder sb = new StringBuilder(128);
+ sb.append(" ");
+ for (String s : pkgs) {
+ sb.append(' ');
+ sb.append(s);
}
+ Slog.i(TAG, sb.toString());
}
+ addPackagesToSet(packagesToBackup, pkgs);
}
}
@@ -2753,44 +2876,34 @@ public class BackupManagerService extends IBackupManager.Stub {
// named system-partition packages will be included even if includeSystem was
// set to false.
if (mPackages != null) {
- for (String pkgName : mPackages) {
- try {
- packagesToBackup.add(mPackageManager.getPackageInfo(pkgName,
- PackageManager.GET_SIGNATURES));
- } catch (NameNotFoundException e) {
- Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
- }
- }
+ addPackagesToSet(packagesToBackup, mPackages);
}
- // Cull any packages that have indicated that backups are not permitted, as well
- // as any explicit mention of the 'special' shared-storage agent package (we
- // handle that one at the end).
- for (int i = 0; i < packagesToBackup.size(); ) {
- PackageInfo pkg = packagesToBackup.get(i);
+ // Now we cull any inapplicable / inappropriate packages from the set
+ Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
+ while (iter.hasNext()) {
+ PackageInfo pkg = iter.next().getValue();
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0
|| pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
- packagesToBackup.remove(i);
- } else {
- i++;
- }
- }
-
- // Cull any packages that run as system-domain uids but do not define their
- // own backup agents
- for (int i = 0; i < packagesToBackup.size(); ) {
- PackageInfo pkg = packagesToBackup.get(i);
- if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
+ // Cull any packages that have indicated that backups are not permitted, as well
+ // as any explicit mention of the 'special' shared-storage agent package (we
+ // handle that one at the end).
+ iter.remove();
+ } else if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
&& (pkg.applicationInfo.backupAgentName == null)) {
+ // Cull any packages that run as system-domain uids but do not define their
+ // own backup agents
if (MORE_DEBUG) {
Slog.i(TAG, "... ignoring non-agent system package " + pkg.packageName);
}
- packagesToBackup.remove(i);
- } else {
- i++;
+ iter.remove();
}
}
+ // flatten the set of packages now so we can explicitly control the ordering
+ ArrayList<PackageInfo> backupQueue =
+ new ArrayList<PackageInfo>(packagesToBackup.values());
+
FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor());
OutputStream out = null;
@@ -2866,16 +2979,16 @@ public class BackupManagerService extends IBackupManager.Stub {
if (mIncludeShared) {
try {
pkg = mPackageManager.getPackageInfo(SHARED_BACKUP_AGENT_PACKAGE, 0);
- packagesToBackup.add(pkg);
+ backupQueue.add(pkg);
} catch (NameNotFoundException e) {
Slog.e(TAG, "Unable to find shared-storage backup handler");
}
}
// Now back up the app data via the agent mechanism
- int N = packagesToBackup.size();
+ int N = backupQueue.size();
for (int i = 0; i < N; i++) {
- pkg = packagesToBackup.get(i);
+ pkg = backupQueue.get(i);
backupOnePackage(pkg, out);
// after the app's agent runs to handle its private filesystem
@@ -3006,11 +3119,13 @@ public class BackupManagerService extends IBackupManager.Stub {
&& ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
(app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+ byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName,
+ UserHandle.USER_OWNER);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
final int token = generateToken();
FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
- token, sendApk, !isSharedStorage);
+ token, sendApk, !isSharedStorage, widgetBlob);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner);
@@ -3086,8 +3201,8 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
- private void writeAppManifest(PackageInfo pkg, File manifestFile, boolean withApk)
- throws IOException {
+ private void writeAppManifest(PackageInfo pkg, File manifestFile,
+ boolean withApk, boolean withWidgets) throws IOException {
// Manifest format. All data are strings ending in LF:
// BACKUP_MANIFEST_VERSION, currently 1
//
@@ -3125,6 +3240,43 @@ public class BackupManagerService extends IBackupManager.Stub {
outstream.close();
}
+ // Widget metadata format. All header entries are strings ending in LF:
+ //
+ // Version 1 header:
+ // BACKUP_METADATA_VERSION, currently "1"
+ // package name
+ //
+ // File data (all integers are binary in network byte order)
+ // *N: 4 : integer token identifying which metadata blob
+ // 4 : integer size of this blob = N
+ // N : raw bytes of this metadata blob
+ //
+ // Currently understood blobs (always in network byte order):
+ //
+ // widgets : metadata token = 0x01FFED01 (BACKUP_WIDGET_METADATA_TOKEN)
+ //
+ // Unrecognized blobs are *ignored*, not errors.
+ private void writeMetadata(PackageInfo pkg, File destination, byte[] widgetData)
+ throws IOException {
+ StringBuilder b = new StringBuilder(512);
+ StringBuilderPrinter printer = new StringBuilderPrinter(b);
+ printer.println(Integer.toString(BACKUP_METADATA_VERSION));
+ printer.println(pkg.packageName);
+
+ FileOutputStream fout = new FileOutputStream(destination);
+ BufferedOutputStream bout = new BufferedOutputStream(fout);
+ DataOutputStream out = new DataOutputStream(bout);
+ bout.write(b.toString().getBytes()); // bypassing DataOutputStream
+
+ if (widgetData != null && widgetData.length > 0) {
+ out.writeInt(BACKUP_WIDGET_METADATA_TOKEN);
+ out.writeInt(widgetData.length);
+ out.write(widgetData);
+ }
+ bout.flush();
+ out.close();
+ }
+
private void tearDown(PackageInfo pkg) {
if (pkg != null) {
final ApplicationInfo app = pkg.applicationInfo;
@@ -3228,6 +3380,7 @@ public class BackupManagerService extends IBackupManager.Stub {
ApplicationInfo mTargetApp;
FullBackupObbConnection mObbConnection = null;
ParcelFileDescriptor[] mPipes = null;
+ byte[] mWidgetData = null;
long mBytes;
@@ -3524,7 +3677,8 @@ public class BackupManagerService extends IBackupManager.Stub {
// Clean up the previous agent relationship if necessary,
// and let the observer know we're considering a new app.
if (mAgent != null) {
- if (DEBUG) Slog.d(TAG, "Saw new package; tearing down old one");
+ if (DEBUG) Slog.d(TAG, "Saw new package; finalizing old one");
+ // Now we're really done
tearDownPipes();
tearDownAgent(mTargetApp);
mTargetApp = null;
@@ -3540,6 +3694,9 @@ public class BackupManagerService extends IBackupManager.Stub {
// input file
skipTarPadding(info.size, instream);
sendOnRestorePackage(pkg);
+ } else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
+ // Metadata blobs!
+ readMetadata(info, instream);
} else {
// Non-manifest, so it's actual file data. Is this a package
// we're ignoring?
@@ -3996,6 +4153,71 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
+ // Read a widget metadata file, returning the restored blob
+ void readMetadata(FileMetadata info, InputStream instream) throws IOException {
+ byte[] data = null;
+
+ // Fail on suspiciously large widget dump files
+ if (info.size > 64 * 1024) {
+ throw new IOException("Metadata too big; corrupt? size=" + info.size);
+ }
+
+ byte[] buffer = new byte[(int) info.size];
+ if (readExactly(instream, buffer, 0, (int)info.size) == info.size) {
+ mBytes += info.size;
+ } else throw new IOException("Unexpected EOF in widget data");
+
+ String[] str = new String[1];
+ int offset = extractLine(buffer, 0, str);
+ int version = Integer.parseInt(str[0]);
+ if (version == BACKUP_MANIFEST_VERSION) {
+ offset = extractLine(buffer, offset, str);
+ final String pkg = str[0];
+ if (info.packageName.equals(pkg)) {
+ // Data checks out -- the rest of the buffer is a concatenation of
+ // binary blobs as described in the comment at writeAppWidgetData()
+ ByteArrayInputStream bin = new ByteArrayInputStream(buffer,
+ offset, buffer.length - offset);
+ DataInputStream in = new DataInputStream(bin);
+ while (bin.available() > 0) {
+ int token = in.readInt();
+ int size = in.readInt();
+ if (size > 64 * 1024) {
+ throw new IOException("Datum "
+ + Integer.toHexString(token)
+ + " too big; corrupt? size=" + info.size);
+ }
+ switch (token) {
+ case BACKUP_WIDGET_METADATA_TOKEN:
+ {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Got widget metadata for " + info.packageName);
+ }
+ mWidgetData = new byte[size];
+ in.read(mWidgetData);
+ break;
+ }
+ default:
+ {
+ if (DEBUG) {
+ Slog.i(TAG, "Ignoring metadata blob "
+ + Integer.toHexString(token)
+ + " for " + info.packageName);
+ }
+ in.skipBytes(size);
+ break;
+ }
+ }
+ }
+ } else {
+ Slog.w(TAG, "Metadata mismatch: package " + info.packageName
+ + " but widget data for " + pkg);
+ }
+ } else {
+ Slog.w(TAG, "Unsupported metadata version " + version);
+ }
+ }
+
// Returns a policy constant; takes a buffer arg to reduce memory churn
RestorePolicy readAppManifest(FileMetadata info, InputStream instream)
throws IOException {
@@ -4486,7 +4708,7 @@ public class BackupManagerService extends IBackupManager.Stub {
private PackageInfo mTargetPackage;
private File mStateDir;
private int mPmToken;
- private boolean mNeedFullBackup;
+ private boolean mIsSystemRestore;
private HashSet<String> mFilterSet;
private long mStartRealtime;
private PackageManagerBackupAgent mPmAgent;
@@ -4497,11 +4719,13 @@ public class BackupManagerService extends IBackupManager.Stub {
private boolean mFinished;
private int mStatus;
private File mBackupDataName;
+ private File mStageName;
private File mNewStateName;
private File mSavedStateName;
private ParcelFileDescriptor mBackupData;
private ParcelFileDescriptor mNewState;
private PackageInfo mCurrentPackage;
+ private byte[] mWidgetData;
class RestoreRequest {
@@ -4516,7 +4740,7 @@ public class BackupManagerService extends IBackupManager.Stub {
PerformRestoreTask(IBackupTransport transport, String dirName, IRestoreObserver observer,
long restoreSetToken, PackageInfo targetPackage, int pmToken,
- boolean needFullBackup, String[] filterSet) {
+ boolean isSystemRestore, String[] filterSet) {
mCurrentState = RestoreState.INITIAL;
mFinished = false;
mPmAgent = null;
@@ -4526,7 +4750,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mToken = restoreSetToken;
mTargetPackage = targetPackage;
mPmToken = pmToken;
- mNeedFullBackup = needFullBackup;
+ mIsSystemRestore = isSystemRestore;
if (filterSet != null) {
mFilterSet = new HashSet<String>();
@@ -4696,8 +4920,7 @@ public class BackupManagerService extends IBackupManager.Stub {
omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
mPmAgent = new PackageManagerBackupAgent(
mPackageManager, mAgentPackages);
- initiateOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(mPmAgent.onBind()),
- mNeedFullBackup);
+ initiateOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(mPmAgent.onBind()));
// The PM agent called operationComplete() already, because our invocation
// of it is process-local and therefore synchronous. That means that a
// RUNNING_QUEUE message is already enqueued. Only if we're unable to
@@ -4727,6 +4950,12 @@ public class BackupManagerService extends IBackupManager.Stub {
// Metadata is intact, so we can now run the restore queue. If we get here,
// we have already enqueued the necessary next-step message on the looper.
+ // We've deferred telling the App Widget service that we might be replacing
+ // the widget environment with something else, but now we know we've got
+ // data coming, so we do it here.
+ if (mIsSystemRestore) {
+ AppWidgetBackupBridge.restoreStarting(UserHandle.USER_OWNER);
+ }
}
void restoreNextAgent() {
@@ -4835,7 +5064,7 @@ public class BackupManagerService extends IBackupManager.Stub {
// And then finally start the restore on this agent
try {
- initiateOneRestore(packageInfo, metaInfo.versionCode, agent, mNeedFullBackup);
+ initiateOneRestore(packageInfo, metaInfo.versionCode, agent);
++mCount;
} catch (Exception e) {
Slog.e(TAG, "Error when attempting restore: " + e.toString());
@@ -4889,6 +5118,9 @@ public class BackupManagerService extends IBackupManager.Stub {
mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
TIMEOUT_RESTORE_INTERVAL);
+ // Kick off any work that may be needed regarding app widget restores
+ AppWidgetBackupBridge.restoreFinished(UserHandle.USER_OWNER);
+
// done; we can finally release the wakelock
Slog.i(TAG, "Restore complete.");
mWakelock.release();
@@ -4896,43 +5128,92 @@ public class BackupManagerService extends IBackupManager.Stub {
// Call asynchronously into the app, passing it the restore data. The next step
// after this is always a callback, either operationComplete() or handleTimeout().
- void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
- boolean needFullBackup) {
+ void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent) {
mCurrentPackage = app;
+ mWidgetData = null;
final String packageName = app.packageName;
if (DEBUG) Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
// !!! TODO: get the dirs from the transport
mBackupDataName = new File(mDataDir, packageName + ".restore");
+ mStageName = new File(mDataDir, packageName + ".stage");
mNewStateName = new File(mStateDir, packageName + ".new");
mSavedStateName = new File(mStateDir, packageName);
+ // don't stage the 'android' package where the wallpaper data lives. this is
+ // an optimization: we know there's no widget data hosted/published by that
+ // package, and this way we avoid doing a spurious copy of MB-sized wallpaper
+ // data following the download.
+ boolean staging = !packageName.equals("android");
+ ParcelFileDescriptor stage;
+ File downloadFile = (staging) ? mStageName : mBackupDataName;
+
final int token = generateToken();
try {
// Run the transport's restore pass
- mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ stage = ParcelFileDescriptor.open(downloadFile,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
if (!SELinux.restorecon(mBackupDataName)) {
- Slog.e(TAG, "SElinux restorecon failed for " + mBackupDataName);
+ Slog.e(TAG, "SElinux restorecon failed for " + downloadFile);
}
- if (mTransport.getRestoreData(mBackupData) != BackupConstants.TRANSPORT_OK) {
+ if (mTransport.getRestoreData(stage) != BackupConstants.TRANSPORT_OK) {
// Transport-level failure, so we wind everything up and
// terminate the restore operation.
Slog.e(TAG, "Error getting restore data for " + packageName);
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
- mBackupData.close();
- mBackupDataName.delete();
+ stage.close();
+ downloadFile.delete();
executeNextState(RestoreState.FINAL);
return;
}
+ // We have the data from the transport. Now we extract and strip
+ // any per-package metadata (typically widget-related information)
+ // if appropriate
+ if (staging) {
+ stage.close();
+ stage = ParcelFileDescriptor.open(downloadFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+
+ BackupDataInput in = new BackupDataInput(stage.getFileDescriptor());
+ BackupDataOutput out = new BackupDataOutput(mBackupData.getFileDescriptor());
+ byte[] buffer = new byte[8192]; // will grow when needed
+ while (in.readNextHeader()) {
+ final String key = in.getKey();
+ final int size = in.getDataSize();
+
+ // is this a special key?
+ if (key.equals(KEY_WIDGET_STATE)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring widget state for " + packageName);
+ }
+ mWidgetData = new byte[size];
+ in.readEntityData(mWidgetData, 0, size);
+ } else {
+ if (size > buffer.length) {
+ buffer = new byte[size];
+ }
+ in.readEntityData(buffer, 0, size);
+ out.writeEntityHeader(key, size);
+ out.writeEntityData(buffer, size);
+ }
+ }
+
+ mBackupData.close();
+ }
+
// Okay, we have the data. Now have the agent do the restore.
- mBackupData.close();
+ stage.close();
mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
@@ -4943,7 +5224,8 @@ public class BackupManagerService extends IBackupManager.Stub {
// Kick off the restore, checking for hung agents
prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
- agent.doRestore(mBackupData, appVersionCode, mNewState, token, mBackupManagerBinder);
+ agent.doRestore(mBackupData, appVersionCode, mNewState,
+ token, mBackupManagerBinder);
} catch (Exception e) {
Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
@@ -4966,6 +5248,7 @@ public class BackupManagerService extends IBackupManager.Stub {
void agentCleanup() {
mBackupDataName.delete();
+ mStageName.delete();
try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
mBackupData = mNewState = null;
@@ -5024,9 +5307,17 @@ public class BackupManagerService extends IBackupManager.Stub {
public void operationComplete() {
int size = (int) mBackupDataName.length();
EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size);
+
// Just go back to running the restore queue
agentCleanup();
+ // If there was widget state associated with this app, get the OS to
+ // incorporate it into current bookeeping and then pass that along to
+ // the app as part of the restore operation.
+ if (mWidgetData != null) {
+ restoreWidgetData(mCurrentPackage.packageName, mWidgetData);
+ }
+
executeNextState(RestoreState.RUNNING_QUEUE);
}
@@ -5050,6 +5341,12 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
+ // Used by both incremental and full restore
+ void restoreWidgetData(String packageName, byte[] widgetData) {
+ // Apply the restored widget state and generate the ID update for the app
+ AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, UserHandle.USER_OWNER);
+ }
+
class PerformClearTask implements Runnable {
IBackupTransport mTransport;
PackageInfo mPackage;
@@ -5345,8 +5642,9 @@ public class BackupManagerService extends IBackupManager.Stub {
// Run a *full* backup pass for the given package, writing the resulting data stream
// to the supplied file descriptor. This method is synchronous and does not return
// to the caller until the backup has been completed.
+ @Override
public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
- boolean includeObbs, boolean includeShared,
+ boolean includeObbs, boolean includeShared, boolean doWidgets,
boolean doAllApps, boolean includeSystem, String[] pkgList) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
@@ -5382,7 +5680,7 @@ public class BackupManagerService extends IBackupManager.Stub {
Slog.i(TAG, "Beginning full backup...");
FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
- includeShared, doAllApps, includeSystem, pkgList);
+ includeShared, doWidgets, doAllApps, includeSystem, pkgList);
final int token = generateToken();
synchronized (mFullConfirmations) {
mFullConfirmations.put(token, params);
@@ -5839,7 +6137,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = new RestoreParams(transport, dirName, null,
- restoreSet, pkg, token, true);
+ restoreSet, pkg, token);
mBackupHandler.sendMessage(msg);
} catch (RemoteException e) {
// Binding to the transport broke; back off and proceed with the installation.
@@ -6020,7 +6318,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = new RestoreParams(mRestoreTransport, dirName,
- observer, token, true);
+ observer, token);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;
@@ -6032,6 +6330,7 @@ public class BackupManagerService extends IBackupManager.Stub {
return -1;
}
+ // Restores of more than a single package are treated as 'system' restores
public synchronized int restoreSome(long token, IRestoreObserver observer,
String[] packages) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
@@ -6090,7 +6389,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, token,
- packages, true);
+ packages, packages.length > 1);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;
@@ -6169,7 +6468,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = new RestoreParams(mRestoreTransport, dirName,
- observer, token, app, 0, false);
+ observer, token, app, 0);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;