Merge change 5196 into donut

* changes:
  Modify the IBackupTransport API to support bulk restore operations. Change the BackupManagerService and LocalTransport to support the new API.
This commit is contained in:
Android (Google) Code Review
2009-06-24 16:52:16 -07:00
5 changed files with 291 additions and 336 deletions

View File

@@ -43,14 +43,14 @@ public class RestoreSet implements Parcelable {
* transport. This is guaranteed to be valid for the duration of a restore * transport. This is guaranteed to be valid for the duration of a restore
* session, but is meaningless once the session has ended. * session, but is meaningless once the session has ended.
*/ */
public int token; public long token;
public RestoreSet() { public RestoreSet() {
// Leave everything zero / null // Leave everything zero / null
} }
public RestoreSet(String _name, String _dev, int _token) { public RestoreSet(String _name, String _dev, long _token) {
name = _name; name = _name;
device = _dev; device = _dev;
token = _token; token = _token;
@@ -65,7 +65,7 @@ public class RestoreSet implements Parcelable {
public void writeToParcel(Parcel out, int flags) { public void writeToParcel(Parcel out, int flags) {
out.writeString(name); out.writeString(name);
out.writeString(device); out.writeString(device);
out.writeInt(token); out.writeLong(token);
} }
public static final Parcelable.Creator<RestoreSet> CREATOR public static final Parcelable.Creator<RestoreSet> CREATOR
@@ -82,6 +82,6 @@ public class RestoreSet implements Parcelable {
private RestoreSet(Parcel in) { private RestoreSet(Parcel in) {
name = in.readString(); name = in.readString();
device = in.readString(); device = in.readString();
token = in.readInt(); token = in.readLong();
} }
} }

View File

@@ -54,63 +54,70 @@ interface IBackupTransport {
long requestBackupTime(); long requestBackupTime();
/** /**
* Establish a connection to the back-end data repository, if necessary. If the transport * Send one application's data to the backup destination. The transport may send
* needs to initialize state that is not tied to individual applications' backup operations, * the data immediately, or may buffer it. After this is called, {@link #finishBackup}
* this is where it should be done. * must be called to ensure the data is sent and recorded successfully.
*
* @return Zero on success; a nonzero error code on failure.
*/
int startSession();
/**
* Send one application's data to the backup destination.
* *
* @param packageInfo The identity of the application whose data is being backed up. * @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package. * This specifically includes the signature list for the package.
* @param data The data stream that resulted from invoking the application's * @param data The data stream that resulted from invoking the application's
* BackupService.doBackup() method. This may be a pipe rather than a file on * BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable. * persistent media, so it may not be seekable.
* @return Zero on success; a nonzero error code on failure. * @return false if errors occurred (the backup should be aborted and rescheduled),
* true if everything is OK so far (but {@link #finishBackup} must be called).
*/ */
int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor data); boolean performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd);
/**
* Finish sending application data to the backup destination. This must be
* called after {@link #performBackup} to ensure that all data is sent. Only
* when this method returns true can the backup be assumed to have succeeded.
*
* @return false if errors occurred (the backup should be aborted and rescheduled),
* true if everything is OK so far (but {@link #finishBackup} must be called).
*/
boolean finishBackup();
/** /**
* Get the set of backups currently available over this transport. * Get the set of backups currently available over this transport.
* *
* @return Descriptions of the set of restore images available for this device. * @return Descriptions of the set of restore images available for this device,
* or null if an error occurred (the attempt should be rescheduled).
**/ **/
RestoreSet[] getAvailableRestoreSets(); RestoreSet[] getAvailableRestoreSets();
/** /**
* Get the set of applications from a given restore image. * Start restoring application data from backup. After calling this function,
* alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
* to walk through the actual application data.
* *
* @param token A backup token as returned by {@link #getAvailableRestoreSets}. * @param token A backup token as returned by {@link #getAvailableRestoreSets}.
* @return An array of PackageInfo objects describing all of the applications * @param packages List of applications to restore (if data is available).
* available for restore from this restore image. This should include the list * Application data will be restored in the order given.
* of signatures for each package so that the Backup Manager can filter using that * @return false if errors occurred (the restore should be aborted and rescheduled),
* information. * true if everything is OK so far (go ahead and call {@link #nextRestorePackage}).
*/ */
PackageInfo[] getAppSet(int token); boolean startRestore(long token, in PackageInfo[] packages);
/** /**
* Retrieve one application's data from the backing store. * Get the package name of the next application with data in the backup store.
* * @return The name of one of the packages supplied to {@link #startRestore},
* @param token The backup record from which a restore is being requested. * or "" (the empty string) if no more backup data is available,
* @param packageInfo The identity of the application whose data is being restored. * or null if an error occurred (the restore should be aborted and rescheduled).
* This must include the signature list for the package; it is up to the transport
* to verify that the requested app's signatures match the saved backup record
* because the transport cannot necessarily trust the client device.
* @param data An open, writable file into which the backup image should be stored.
* @return Zero on success; a nonzero error code on failure.
*/ */
int getRestoreData(int token, in PackageInfo packageInfo, in ParcelFileDescriptor data); String nextRestorePackage();
/** /**
* Terminate the backup session, closing files, freeing memory, and cleaning up whatever * Get the data for the application returned by {@link #nextRestorePackage}.
* other state the transport required. * @param data An open, writable file into which the backup data should be stored.
* * @return false if errors occurred (the restore should be aborted and rescheduled),
* @return Zero on success; a nonzero error code on failure. Even on failure, the session * true if everything is OK so far (go ahead and call {@link #nextRestorePackage}).
* is torn down and must be restarted if another backup is attempted.
*/ */
int endSession(); boolean getRestoreData(in ParcelFileDescriptor outFd);
/**
* End a restore session (aborting any in-process data transfer as necessary),
* freeing any resources and connections used during the restore process.
*/
void finishRestore();
} }

View File

@@ -33,11 +33,8 @@ public class LocalTransport extends IBackupTransport.Stub {
private Context mContext; private Context mContext;
private PackageManager mPackageManager; private PackageManager mPackageManager;
private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
private FileFilter mDirFileFilter = new FileFilter() { private PackageInfo[] mRestorePackages = null;
public boolean accept(File f) { private int mRestorePackage = -1; // Index into mRestorePackages
return f.isDirectory();
}
};
public LocalTransport(Context context) { public LocalTransport(Context context) {
@@ -51,21 +48,9 @@ public class LocalTransport extends IBackupTransport.Stub {
return 0; return 0;
} }
public int startSession() throws RemoteException { public boolean performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
if (DEBUG) Log.v(TAG, "session started");
mDataDir.mkdirs();
return 0;
}
public int endSession() throws RemoteException {
if (DEBUG) Log.v(TAG, "session ended");
return 0;
}
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
throws RemoteException { throws RemoteException {
if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName); if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
int err = 0;
File packageDir = new File(mDataDir, packageInfo.packageName); File packageDir = new File(mDataDir, packageInfo.packageName);
packageDir.mkdirs(); packageDir.mkdirs();
@@ -101,9 +86,8 @@ public class LocalTransport extends IBackupTransport.Stub {
try { try {
entity.write(buf, 0, dataSize); entity.write(buf, 0, dataSize);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Unable to update key file " Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
+ entityFile.getAbsolutePath()); return false;
err = -1;
} finally { } finally {
entity.close(); entity.close();
} }
@@ -111,14 +95,17 @@ public class LocalTransport extends IBackupTransport.Stub {
entityFile.delete(); entityFile.delete();
} }
} }
return true;
} catch (IOException e) { } catch (IOException e) {
// oops, something went wrong. abort the operation and return error. // oops, something went wrong. abort the operation and return error.
Log.v(TAG, "Exception reading backup input:"); Log.v(TAG, "Exception reading backup input:", e);
e.printStackTrace(); return false;
err = -1;
} }
}
return err; public boolean finishBackup() throws RemoteException {
if (DEBUG) Log.v(TAG, "finishBackup()");
return true;
} }
// Restore handling // Restore handling
@@ -129,72 +116,66 @@ public class LocalTransport extends IBackupTransport.Stub {
return array; return array;
} }
public PackageInfo[] getAppSet(int token) throws android.os.RemoteException { public boolean startRestore(long token, PackageInfo[] packages) {
if (DEBUG) Log.v(TAG, "getting app set " + token); if (DEBUG) Log.v(TAG, "start restore " + token);
// the available packages are the extant subdirs of mDatadir mRestorePackages = packages;
File[] packageDirs = mDataDir.listFiles(mDirFileFilter); mRestorePackage = -1;
ArrayList<PackageInfo> packages = new ArrayList<PackageInfo>(); return true;
for (File dir : packageDirs) {
try {
PackageInfo pkg = mPackageManager.getPackageInfo(dir.getName(),
PackageManager.GET_SIGNATURES);
if (pkg != null) {
packages.add(pkg);
}
} catch (NameNotFoundException e) {
// restore set contains data for a package not installed on the
// phone -- just ignore it.
}
}
if (DEBUG) {
Log.v(TAG, "Built app set of " + packages.size() + " entries:");
for (PackageInfo p : packages) {
Log.v(TAG, " + " + p.packageName);
}
}
PackageInfo[] result = new PackageInfo[packages.size()];
return packages.toArray(result);
} }
public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor outFd) public String nextRestorePackage() {
throws android.os.RemoteException { if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
if (DEBUG) Log.v(TAG, "getting restore data " + token + " : " + packageInfo.packageName); while (++mRestorePackage < mRestorePackages.length) {
// we only support one hardcoded restore set String name = mRestorePackages[mRestorePackage].packageName;
if (token != 0) return -1; if (new File(mDataDir, name).isDirectory()) {
if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
return name;
}
}
// the data for a given package is at a known location if (DEBUG) Log.v(TAG, " no more packages to restore");
File packageDir = new File(mDataDir, packageInfo.packageName); return "";
}
public boolean getRestoreData(ParcelFileDescriptor outFd) {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
// The restore set is the concatenation of the individual record blobs, // The restore set is the concatenation of the individual record blobs,
// each of which is a file in the package's directory // each of which is a file in the package's directory
File[] blobs = packageDir.listFiles(); File[] blobs = packageDir.listFiles();
if (DEBUG) Log.v(TAG, " found " + blobs.length + " key files"); if (blobs == null) {
int err = 0; Log.e(TAG, "Error listing directory: " + packageDir);
if (blobs != null && blobs.length > 0) { return false; // nextRestorePackage() ensures the dir exists, so this is an error
BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor()); }
try {
for (File f : blobs) { // We expect at least some data if the directory exists in the first place
copyToRestoreData(f, out); if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
} BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
} catch (Exception e) { try {
Log.e(TAG, "Unable to read backup records"); for (File f : blobs) {
err = -1; FileInputStream in = new FileInputStream(f);
} try {
int size = (int) f.length();
byte[] buf = new byte[size];
in.read(buf);
String key = new String(Base64.decode(f.getName()));
if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size);
out.writeEntityHeader(key, size);
out.writeEntityData(buf, size);
} finally {
in.close();
}
}
return true;
} catch (IOException e) {
Log.e(TAG, "Unable to read backup records", e);
return false;
} }
return err;
} }
private void copyToRestoreData(File f, BackupDataOutput out) throws IOException { public void finishRestore() {
FileInputStream in = new FileInputStream(f); if (DEBUG) Log.v(TAG, "finishRestore()");
int size = (int) f.length();
byte[] buf = new byte[size];
in.read(buf);
String key = new String(Base64.decode(f.getName()));
if (DEBUG) Log.v(TAG, " ... copy to stream: key=" + key
+ " size=" + size);
out.writeEntityHeader(key, size);
out.writeEntityData(buf, size);
} }
} }

View File

@@ -351,28 +351,28 @@ class BackupManagerService extends IBackupManager.Stub {
void addPackageParticipantsLocked(String packageName) { void addPackageParticipantsLocked(String packageName) {
// Look for apps that define the android:backupAgent attribute // Look for apps that define the android:backupAgent attribute
if (DEBUG) Log.v(TAG, "addPackageParticipantsLocked: " + packageName); if (DEBUG) Log.v(TAG, "addPackageParticipantsLocked: " + packageName);
List<ApplicationInfo> targetApps = allAgentApps(); List<PackageInfo> targetApps = allAgentPackages();
addPackageParticipantsLockedInner(packageName, targetApps); addPackageParticipantsLockedInner(packageName, targetApps);
} }
private void addPackageParticipantsLockedInner(String packageName, private void addPackageParticipantsLockedInner(String packageName,
List<ApplicationInfo> targetApps) { List<PackageInfo> targetPkgs) {
if (DEBUG) { if (DEBUG) {
Log.v(TAG, "Adding " + targetApps.size() + " backup participants:"); Log.v(TAG, "Adding " + targetPkgs.size() + " backup participants:");
for (ApplicationInfo a : targetApps) { for (PackageInfo p : targetPkgs) {
Log.v(TAG, " " + a + " agent=" + a.backupAgentName); Log.v(TAG, " " + p + " agent=" + p.applicationInfo.backupAgentName);
} }
} }
for (ApplicationInfo app : targetApps) { for (PackageInfo pkg : targetPkgs) {
if (packageName == null || app.packageName.equals(packageName)) { if (packageName == null || pkg.packageName.equals(packageName)) {
int uid = app.uid; int uid = pkg.applicationInfo.uid;
HashSet<ApplicationInfo> set = mBackupParticipants.get(uid); HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
if (set == null) { if (set == null) {
set = new HashSet<ApplicationInfo>(); set = new HashSet<ApplicationInfo>();
mBackupParticipants.put(uid, set); mBackupParticipants.put(uid, set);
} }
set.add(app); set.add(pkg.applicationInfo);
backUpPackageManagerData(); backUpPackageManagerData();
} }
} }
@@ -382,67 +382,67 @@ class BackupManagerService extends IBackupManager.Stub {
// 'packageName' is null, *all* participating apps will be removed. // 'packageName' is null, *all* participating apps will be removed.
void removePackageParticipantsLocked(String packageName) { void removePackageParticipantsLocked(String packageName) {
if (DEBUG) Log.v(TAG, "removePackageParticipantsLocked: " + packageName); if (DEBUG) Log.v(TAG, "removePackageParticipantsLocked: " + packageName);
List<ApplicationInfo> allApps = null; List<PackageInfo> allApps = null;
if (packageName != null) { if (packageName != null) {
allApps = new ArrayList<ApplicationInfo>(); allApps = new ArrayList<PackageInfo>();
try { try {
ApplicationInfo app = mPackageManager.getApplicationInfo(packageName, 0); int flags = PackageManager.GET_SIGNATURES;
allApps.add(app); allApps.add(mPackageManager.getPackageInfo(packageName, flags));
} catch (Exception e) { } catch (Exception e) {
// just skip it // just skip it (???)
} }
} else { } else {
// all apps with agents // all apps with agents
allApps = allAgentApps(); allApps = allAgentPackages();
} }
removePackageParticipantsLockedInner(packageName, allApps); removePackageParticipantsLockedInner(packageName, allApps);
} }
private void removePackageParticipantsLockedInner(String packageName, private void removePackageParticipantsLockedInner(String packageName,
List<ApplicationInfo> agents) { List<PackageInfo> agents) {
if (DEBUG) { if (DEBUG) {
Log.v(TAG, "removePackageParticipantsLockedInner (" + packageName Log.v(TAG, "removePackageParticipantsLockedInner (" + packageName
+ ") removing " + agents.size() + " entries"); + ") removing " + agents.size() + " entries");
for (ApplicationInfo a : agents) { for (PackageInfo p : agents) {
Log.v(TAG, " - " + a); Log.v(TAG, " - " + p);
} }
} }
for (ApplicationInfo app : agents) { for (PackageInfo pkg : agents) {
if (packageName == null || app.packageName.equals(packageName)) { if (packageName == null || pkg.packageName.equals(packageName)) {
int uid = app.uid; int uid = pkg.applicationInfo.uid;
HashSet<ApplicationInfo> set = mBackupParticipants.get(uid); HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
if (set != null) { if (set != null) {
// Find the existing entry with the same package name, and remove it. // Find the existing entry with the same package name, and remove it.
// We can't just remove(app) because the instances are different. // We can't just remove(app) because the instances are different.
for (ApplicationInfo entry: set) { for (ApplicationInfo entry: set) {
if (entry.packageName.equals(app.packageName)) { if (entry.packageName.equals(pkg.packageName)) {
set.remove(entry); set.remove(entry);
backUpPackageManagerData(); backUpPackageManagerData();
break; break;
} }
} }
if (set.size() == 0) { if (set.size() == 0) {
mBackupParticipants.delete(uid); } mBackupParticipants.delete(uid);
}
} }
} }
} }
} }
// Returns the set of all applications that define an android:backupAgent attribute // Returns the set of all applications that define an android:backupAgent attribute
private List<ApplicationInfo> allAgentApps() { private List<PackageInfo> allAgentPackages() {
// !!! TODO: cache this and regenerate only when necessary // !!! TODO: cache this and regenerate only when necessary
List<ApplicationInfo> allApps = mPackageManager.getInstalledApplications(0); int flags = PackageManager.GET_SIGNATURES;
int N = allApps.size(); List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags);
if (N > 0) { int N = packages.size();
for (int a = N-1; a >= 0; a--) { for (int a = N-1; a >= 0; a--) {
ApplicationInfo app = allApps.get(a); ApplicationInfo app = packages.get(a).applicationInfo;
if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) if (((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
|| app.backupAgentName == null) { || app.backupAgentName == null) {
allApps.remove(a); packages.remove(a);
}
} }
} }
return allApps; return packages;
} }
// Reset the given package's known backup participants. Unlike add/remove, the update // Reset the given package's known backup participants. Unlike add/remove, the update
@@ -455,7 +455,7 @@ class BackupManagerService extends IBackupManager.Stub {
if (DEBUG) Log.v(TAG, "updatePackageParticipantsLocked: " + packageName); if (DEBUG) Log.v(TAG, "updatePackageParticipantsLocked: " + packageName);
// brute force but small code size // brute force but small code size
List<ApplicationInfo> allApps = allAgentApps(); List<PackageInfo> allApps = allAgentPackages();
removePackageParticipantsLockedInner(packageName, allApps); removePackageParticipantsLockedInner(packageName, allApps);
addPackageParticipantsLockedInner(packageName, allApps); addPackageParticipantsLockedInner(packageName, allApps);
} }
@@ -578,17 +578,7 @@ class BackupManagerService extends IBackupManager.Stub {
public void run() { public void run() {
if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets"); if (DEBUG) Log.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
// start up the transport // First, back up the package manager metadata if necessary
try {
mTransport.startSession();
} catch (Exception e) {
Log.e(TAG, "Error session transport");
e.printStackTrace();
return;
}
// The transport is up and running. First, back up the package manager
// metadata if necessary
boolean doPackageManager; boolean doPackageManager;
synchronized (BackupManagerService.this) { synchronized (BackupManagerService.this) {
doPackageManager = mDoPackageManager; doPackageManager = mDoPackageManager;
@@ -601,7 +591,7 @@ class BackupManagerService extends IBackupManager.Stub {
if (DEBUG) Log.i(TAG, "Running PM backup pass as well"); if (DEBUG) Log.i(TAG, "Running PM backup pass as well");
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
mPackageManager, allAgentApps()); mPackageManager, allAgentPackages());
BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false);
pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL;
processOneBackup(pmRequest, processOneBackup(pmRequest,
@@ -614,10 +604,12 @@ class BackupManagerService extends IBackupManager.Stub {
// Finally, tear down the transport // Finally, tear down the transport
try { try {
mTransport.endSession(); if (!mTransport.finishBackup()) {
} catch (Exception e) { // STOPSHIP TODO: handle errors
Log.e(TAG, "Error ending transport"); Log.e(TAG, "Backup failure in finishBackup()");
e.printStackTrace(); }
} catch (RemoteException e) {
Log.e(TAG, "Error in finishBackup()", e);
} }
if (!mJournal.delete()) { if (!mJournal.delete()) {
@@ -712,24 +704,18 @@ class BackupManagerService extends IBackupManager.Stub {
if (DEBUG) Log.v(TAG, "doBackup() success; calling transport"); if (DEBUG) Log.v(TAG, "doBackup() success; calling transport");
backupData = backupData =
ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY); ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
int error = transport.performBackup(packInfo, backupData); if (!transport.performBackup(packInfo, backupData)) {
// STOPSHIP TODO: handle errors
Log.e(TAG, "Backup failure in performBackup()");
}
// !!! TODO: After successful transport, delete the now-stale data // !!! TODO: After successful transport, delete the now-stale data
// and juggle the files so that next time the new state is passed // and juggle the files so that next time the new state is passed
//backupDataName.delete(); //backupDataName.delete();
newStateName.renameTo(savedStateName); newStateName.renameTo(savedStateName);
} }
} catch (NameNotFoundException e) {
Log.e(TAG, "Package not found on backup: " + packageName);
} catch (FileNotFoundException fnf) {
Log.w(TAG, "File not found on backup: ");
fnf.printStackTrace();
} catch (RemoteException e) {
Log.d(TAG, "Remote target " + request.appInfo.packageName + " threw during backup:");
e.printStackTrace();
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, "Final exception guard in backup: "); Log.e(TAG, "Error backing up " + packageName, e);
e.printStackTrace();
} }
} }
} }
@@ -737,25 +723,6 @@ class BackupManagerService extends IBackupManager.Stub {
// ----- Restore handling ----- // ----- Restore handling -----
// Is the given package restorable on this device? Returns the on-device app's
// ApplicationInfo struct if it is; null if not.
//
// !!! TODO: also consider signatures
PackageInfo isRestorable(PackageInfo packageInfo) {
if (packageInfo.packageName != null) {
try {
PackageInfo app = mPackageManager.getPackageInfo(packageInfo.packageName,
PackageManager.GET_SIGNATURES);
if ((app.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
return app;
}
} catch (Exception e) {
// doesn't exist on this device, or other error -- just ignore it.
}
}
return null;
}
private boolean signaturesMatch(Signature[] storedSigs, Signature[] deviceSigs) { private boolean signaturesMatch(Signature[] storedSigs, Signature[] deviceSigs) {
// Allow unsigned apps, but not signed on one device and unsigned on the other // Allow unsigned apps, but not signed on one device and unsigned on the other
// !!! TODO: is this the right policy? // !!! TODO: is this the right policy?
@@ -816,130 +783,141 @@ class BackupManagerService extends IBackupManager.Stub {
/** /**
* Restore sequence: * Restore sequence:
* *
* 1. start up the transport session * 1. get the restore set description for our identity
* 2. get the restore set description for our identity * 2. for each app in the restore set:
* 3. for each app in the restore set:
* 3.a. if it's restorable on this device, add it to the restore queue * 3.a. if it's restorable on this device, add it to the restore queue
* 4. for each app in the restore queue: * 3. for each app in the restore queue:
* 4.a. clear the app data * 3.a. clear the app data
* 4.b. get the restore data for the app from the transport * 3.b. get the restore data for the app from the transport
* 4.c. launch the backup agent for the app * 3.c. launch the backup agent for the app
* 4.d. agent.doRestore() with the data from the server * 3.d. agent.doRestore() with the data from the server
* 4.e. unbind the agent [and kill the app?] * 3.e. unbind the agent [and kill the app?]
* 5. shut down the transport * 4. shut down the transport
*/ */
int err = -1; // build the set of apps to restore
try { try {
err = mTransport.startSession(); RestoreSet[] images = mTransport.getAvailableRestoreSets();
} catch (Exception e) { if (images == null) {
Log.e(TAG, "Error starting transport for restore"); // STOPSHIP TODO: Handle the failure somehow?
e.printStackTrace(); Log.e(TAG, "Error getting restore sets");
} return;
}
if (err == 0) { if (images.length == 0) {
// build the set of apps to restore Log.i(TAG, "No restore sets available");
try { return;
RestoreSet[] images = mTransport.getAvailableRestoreSets(); }
if (images.length > 0) {
// !!! TODO: pick out the set for this token
mImage = images[0];
// Pull the Package Manager metadata from the restore set first mImage = images[0];
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
mPackageManager, allAgentApps());
PackageInfo pmApp = new PackageInfo();
pmApp.packageName = PACKAGE_MANAGER_SENTINEL;
// !!! TODO: version currently ignored when 'restoring' the PM metadata
processOneRestore(pmApp, 0,
IBackupAgent.Stub.asInterface(pmAgent.onBind()));
// build the set of apps we will attempt to restore // Get the list of all packages which have backup enabled.
PackageInfo[] packages = mTransport.getAppSet(mImage.token); // (Include the Package Manager metadata pseudo-package first.)
HashSet<RestoreRequest> appsToRestore = new HashSet<RestoreRequest>(); ArrayList<PackageInfo> restorePackages = new ArrayList<PackageInfo>();
for (PackageInfo pkg: packages) { PackageInfo omPackage = new PackageInfo();
// get the real PackageManager idea of the package omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
PackageInfo app = isRestorable(pkg); restorePackages.add(omPackage);
if (app != null) {
// Validate against the backed-up signature block, too
Metadata info = pmAgent.getRestoredMetadata(app.packageName);
if (info != null) {
if (app.versionCode >= info.versionCode) {
if (DEBUG) Log.v(TAG, "Restore version "
+ info.versionCode
+ " compatible with app version "
+ app.versionCode);
if (signaturesMatch(info.signatures, app.signatures)) {
appsToRestore.add(
new RestoreRequest(app, info.versionCode));
} else {
Log.w(TAG, "Sig mismatch restoring "
+ app.packageName);
}
} else {
Log.i(TAG, "Restore set for " + app.packageName
+ " is too new [" + info.versionCode
+ "] for installed app version "
+ app.versionCode);
}
} else {
Log.d(TAG, "Unable to get metadata for "
+ app.packageName);
}
}
}
// now run the restore queue List<PackageInfo> agentPackages = allAgentPackages();
doQueuedRestores(appsToRestore); restorePackages.addAll(agentPackages);
// STOPSHIP TODO: pick out the set for this token (instead of images[0])
long token = images[0].token;
if (!mTransport.startRestore(token, restorePackages.toArray(new PackageInfo[0]))) {
// STOPSHIP TODO: Handle the failure somehow?
Log.e(TAG, "Error starting restore operation");
return;
}
String packageName = mTransport.nextRestorePackage();
if (packageName == null) {
// STOPSHIP TODO: Handle the failure somehow?
Log.e(TAG, "Error getting first restore package");
return;
} else if (packageName.equals("")) {
Log.i(TAG, "No restore data available");
return;
} else if (!packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
Log.e(TAG, "Expected restore data for \"" + PACKAGE_MANAGER_SENTINEL
+ "\", found only \"" + packageName + "\"");
return;
}
// Pull the Package Manager metadata from the restore set first
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
mPackageManager, agentPackages);
processOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(pmAgent.onBind()));
for (;;) {
packageName = mTransport.nextRestorePackage();
if (packageName == null) {
// STOPSHIP TODO: Handle the failure somehow?
Log.e(TAG, "Error getting next restore package");
return;
} else if (packageName.equals("")) {
break;
} }
} catch (RemoteException e) {
// can't happen; transports run locally
}
// done; shut down the transport Metadata metaInfo = pmAgent.getRestoredMetadata(packageName);
try { if (metaInfo == null) {
mTransport.endSession(); Log.e(TAG, "Missing metadata for " + packageName);
} catch (Exception e) { continue;
Log.e(TAG, "Error ending transport for restore"); }
e.printStackTrace();
}
}
// even if the initial session startup failed, report that we're done here int flags = PackageManager.GET_SIGNATURES;
} PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
if (metaInfo.versionCode > packageInfo.versionCode) {
Log.w(TAG, "Package " + packageName
+ " restore version [" + metaInfo.versionCode
+ "] is too new for installed version ["
+ packageInfo.versionCode + "]");
continue;
}
// restore each app in the queue if (!signaturesMatch(metaInfo.signatures, packageInfo.signatures)) {
void doQueuedRestores(HashSet<RestoreRequest> appsToRestore) { Log.w(TAG, "Signature mismatch restoring " + packageName);
for (RestoreRequest req : appsToRestore) { continue;
PackageInfo app = req.app; }
Log.d(TAG, "starting agent for restore of " + app);
try { if (DEBUG) Log.v(TAG, "Package " + packageName
// Remove the app's data first + " restore version [" + metaInfo.versionCode
clearApplicationDataSynchronous(app.packageName); + "] is compatible with installed version ["
+ packageInfo.versionCode + "]");
// Now perform the restore into the clean app // Now perform the actual restore
IBackupAgent agent = bindToAgentSynchronous(app.applicationInfo, clearApplicationDataSynchronous(packageName);
IBackupAgent agent = bindToAgentSynchronous(
packageInfo.applicationInfo,
IApplicationThread.BACKUP_MODE_RESTORE); IApplicationThread.BACKUP_MODE_RESTORE);
if (agent != null) { if (agent == null) {
processOneRestore(app, req.storedAppVersion, agent); Log.w(TAG, "Can't find backup agent for " + packageName);
continue;
} }
// unbind even on timeout, just in case try {
mActivityManager.unbindBackupAgent(app.applicationInfo); processOneRestore(packageInfo, metaInfo.versionCode, agent);
} catch (SecurityException ex) { } finally {
// Try for the next one. // unbind even on timeout or failure, just in case
Log.d(TAG, "error in bind", ex); mActivityManager.unbindBackupAgent(packageInfo.applicationInfo);
} catch (RemoteException e) { }
// can't happen }
} catch (NameNotFoundException e) {
// STOPSHIP TODO: Handle the failure somehow?
Log.e(TAG, "Invalid paackage restoring data", e);
} catch (RemoteException e) {
// STOPSHIP TODO: Handle the failure somehow?
Log.e(TAG, "Error restoring data", e);
} finally {
try {
mTransport.finishRestore();
} catch (RemoteException e) {
Log.e(TAG, "Error finishing restore", e);
} }
} }
} }
// Do the guts of a restore of one application, derived from the 'mImage' // Do the guts of a restore of one application, using mTransport.getRestoreData().
// restore set via the 'mTransport' transport. void processOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent) {
void processOneRestore(PackageInfo app, int storedAppVersion, IBackupAgent agent) {
// !!! TODO: actually run the restore through mTransport // !!! TODO: actually run the restore through mTransport
final String packageName = app.packageName; final String packageName = app.packageName;
@@ -954,11 +932,12 @@ class BackupManagerService extends IBackupManager.Stub {
// Run the transport's restore pass // Run the transport's restore pass
// Run the target's backup pass // Run the target's backup pass
int err = -1;
try { try {
err = mTransport.getRestoreData(mImage.token, app, backupData); if (!mTransport.getRestoreData(backupData)) {
} catch (RemoteException e) { // STOPSHIP TODO: Handle this error somehow?
// can't happen Log.e(TAG, "Error getting restore data for " + packageName);
return;
}
} finally { } finally {
backupData.close(); backupData.close();
} }
@@ -973,30 +952,18 @@ class BackupManagerService extends IBackupManager.Stub {
backupData = ParcelFileDescriptor.open(backupDataName, backupData = ParcelFileDescriptor.open(backupDataName,
ParcelFileDescriptor.MODE_READ_ONLY); ParcelFileDescriptor.MODE_READ_ONLY);
boolean success = false;
try { try {
agent.doRestore(backupData, storedAppVersion, newState); agent.doRestore(backupData, appVersionCode, newState);
success = true;
} catch (Exception e) {
Log.e(TAG, "Restore failed for " + packageName);
e.printStackTrace();
} finally { } finally {
newState.close(); newState.close();
backupData.close(); backupData.close();
} }
// if everything went okay, remember the recorded state now // if everything went okay, remember the recorded state now
if (success) { File savedStateName = new File(mStateDir, packageName);
File savedStateName = new File(mStateDir, packageName); newStateName.renameTo(savedStateName);
newStateName.renameTo(savedStateName);
}
} catch (FileNotFoundException fnfe) {
Log.v(TAG, "Couldn't open file for restore: " + fnfe);
} catch (IOException ioe) {
Log.e(TAG, "Unable to process restore file: " + ioe);
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Final exception guard in restore:"); Log.e(TAG, "Error restoring data for " + packageName, e);
e.printStackTrace();
} }
} }
} }
@@ -1206,7 +1173,7 @@ class BackupManagerService extends IBackupManager.Stub {
mContext.enforceCallingPermission("android.permission.BACKUP", mContext.enforceCallingPermission("android.permission.BACKUP",
"endRestoreSession"); "endRestoreSession");
mRestoreTransport.endSession(); mRestoreTransport.finishRestore();
mRestoreTransport = null; mRestoreTransport = null;
synchronized(BackupManagerService.this) { synchronized(BackupManagerService.this) {
if (BackupManagerService.this.mActiveRestoreSession == this) { if (BackupManagerService.this.mActiveRestoreSession == this) {

View File

@@ -57,7 +57,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
// is stored using the package name as a key) // is stored using the package name as a key)
private static final String GLOBAL_METADATA_KEY = "@meta@"; private static final String GLOBAL_METADATA_KEY = "@meta@";
private List<ApplicationInfo> mAllApps; private List<PackageInfo> mAllPackages;
private PackageManager mPackageManager; private PackageManager mPackageManager;
private HashMap<String, Metadata> mRestoredSignatures; private HashMap<String, Metadata> mRestoredSignatures;
@@ -73,9 +73,9 @@ public class PackageManagerBackupAgent extends BackupAgent {
// We're constructed with the set of applications that are participating // We're constructed with the set of applications that are participating
// in backup. This set changes as apps are installed & removed. // in backup. This set changes as apps are installed & removed.
PackageManagerBackupAgent(PackageManager packageMgr, List<ApplicationInfo> apps) { PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
mPackageManager = packageMgr; mPackageManager = packageMgr;
mAllApps = apps; mAllPackages = packages;
mRestoredSignatures = null; mRestoredSignatures = null;
} }
@@ -118,8 +118,8 @@ public class PackageManagerBackupAgent extends BackupAgent {
// For each app we have on device, see if we've backed it up yet. If not, // For each app we have on device, see if we've backed it up yet. If not,
// write its signature block to the output, keyed on the package name. // write its signature block to the output, keyed on the package name.
for (ApplicationInfo app : mAllApps) { for (PackageInfo pkg : mAllPackages) {
String packName = app.packageName; String packName = pkg.packageName;
if (!existing.contains(packName)) { if (!existing.contains(packName)) {
// We haven't stored this app's signatures yet, so we do that now // We haven't stored this app's signatures yet, so we do that now
try { try {
@@ -186,7 +186,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
} }
// Finally, write the new state blob -- just the list of all apps we handled // Finally, write the new state blob -- just the list of all apps we handled
writeStateFile(mAllApps, newState); writeStateFile(mAllPackages, newState);
} }
// "Restore" here is a misnomer. What we're really doing is reading back the // "Restore" here is a misnomer. What we're really doing is reading back the
@@ -327,7 +327,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
} }
// Util: write out our new backup state file // Util: write out our new backup state file
private void writeStateFile(List<ApplicationInfo> apps, ParcelFileDescriptor stateFile) { private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor()); FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
DataOutputStream out = new DataOutputStream(outstream); DataOutputStream out = new DataOutputStream(outstream);
@@ -338,8 +338,8 @@ public class PackageManagerBackupAgent extends BackupAgent {
out.write(metaNameBuf); out.write(metaNameBuf);
// now write all the app names too // now write all the app names too
for (ApplicationInfo app : apps) { for (PackageInfo pkg : pkgs) {
byte[] pkgNameBuf = app.packageName.getBytes(); byte[] pkgNameBuf = pkg.packageName.getBytes();
out.writeInt(pkgNameBuf.length); out.writeInt(pkgNameBuf.length);
out.write(pkgNameBuf); out.write(pkgNameBuf);
} }