summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmds/bu/src/com/android/commands/bu/Backup.java24
-rw-r--r--core/java/android/app/ActivityThread.java22
-rw-r--r--core/java/android/app/IApplicationThread.java1
-rw-r--r--core/java/android/app/IBackupAgent.aidl19
-rw-r--r--core/java/android/app/backup/BackupAgent.java32
-rw-r--r--core/java/android/app/backup/FullBackup.java110
-rw-r--r--core/java/android/app/backup/FullBackupAgent.java42
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl8
-rw-r--r--libs/utils/BackupHelpers.cpp10
-rw-r--r--packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java152
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java6
-rw-r--r--services/java/com/android/server/BackupManagerService.java1008
-rw-r--r--services/java/com/android/server/SystemBackupAgent.java72
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java1
-rw-r--r--services/java/com/android/server/am/BackupRecord.java1
16 files changed, 1416 insertions, 94 deletions
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index f3f0432..e81f799 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -34,11 +34,12 @@ public final class Backup {
IBackupManager mBackupManager;
public static void main(String[] args) {
+ Log.d(TAG, "Beginning: " + args[0]);
mArgs = args;
try {
new Backup().run();
} catch (Exception e) {
- Log.e(TAG, "Error running backup", e);
+ Log.e(TAG, "Error running backup/restore", e);
}
Log.d(TAG, "Finished.");
}
@@ -46,7 +47,7 @@ public final class Backup {
public void run() {
mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
if (mBackupManager == null) {
- System.err.println("ERROR: could not contact backup manager");
+ Log.e(TAG, "Can't obtain Backup Manager binder");
return;
}
@@ -56,7 +57,7 @@ public final class Backup {
} else if (arg.equals("restore")) {
doFullRestore();
} else {
- System.err.println("ERROR: invalid operation '" + arg + "'");
+ Log.e(TAG, "Invalid operation '" + arg + "'");
}
}
@@ -80,7 +81,6 @@ public final class Backup {
} else if ("-all".equals(arg)) {
doEverything = true;
} else {
- System.err.println("WARNING: unknown backup flag " + arg);
Log.w(TAG, "Unknown backup flag " + arg);
continue;
}
@@ -91,13 +91,10 @@ public final class Backup {
}
if (doEverything && packages.size() > 0) {
- System.err.println("WARNING: -all used with explicit backup package set");
Log.w(TAG, "-all passed for backup along with specific package names");
}
if (!doEverything && !saveShared && packages.size() == 0) {
- System.err.println(
- "ERROR: no packages supplied for backup and neither -shared nor -all given");
Log.e(TAG, "no backup packages supplied and neither -shared nor -all given");
return;
}
@@ -108,13 +105,22 @@ public final class Backup {
mBackupManager.fullBackup(fd, saveApks, saveShared, doEverything,
packages.toArray(packArray));
} catch (IOException e) {
- System.err.println("ERROR: cannot dup System.out");
+ Log.e(TAG, "Can't dup out");
} catch (RemoteException e) {
- System.err.println("ERROR: unable to invoke backup manager service");
+ Log.e(TAG, "Unable to invoke backup manager for backup");
}
}
private void doFullRestore() {
+ // No arguments to restore
+ try {
+ ParcelFileDescriptor fd = ParcelFileDescriptor.dup(FileDescriptor.in);
+ mBackupManager.fullRestore(fd);
+ } catch (IOException e) {
+ Log.e(TAG, "Can't dup System.in");
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to invoke backup manager for restore");
+ }
}
private String nextArg() {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 85e59b3..955cef2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1980,7 +1980,8 @@ public final class ActivityThread {
BackupAgent agent = null;
String classname = data.appInfo.backupAgentName;
- if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL) {
+ if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL
+ || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL) {
classname = "android.app.backup.FullBackupAgent";
if ((data.appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
// system packages can supply their own full-backup agent
@@ -2011,7 +2012,8 @@ public final class ActivityThread {
// If this is during restore, fail silently; otherwise go
// ahead and let the user see the crash.
Slog.e(TAG, "Agent threw during creation: " + e);
- if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE) {
+ if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE
+ && data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE_FULL) {
throw e;
}
// falling through with 'binder' still null
@@ -3658,12 +3660,16 @@ public final class ActivityThread {
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
- List<ProviderInfo> providers = data.providers;
- if (providers != null) {
- installContentProviders(app, providers);
- // For process that contains content providers, we want to
- // ensure that the JIT is enabled "at some point".
- mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
+ // don't bring up providers in restricted mode; they may depend on the
+ // app's custom Application class
+ if (!data.restrictedBackupMode){
+ List<ProviderInfo> providers = data.providers;
+ if (providers != null) {
+ installContentProviders(app, providers);
+ // For process that contains content providers, we want to
+ // ensure that the JIT is enabled "at some point".
+ mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
+ }
}
try {
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 8c31559..05a68a8 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -68,6 +68,7 @@ public interface IApplicationThread extends IInterface {
static final int BACKUP_MODE_INCREMENTAL = 0;
static final int BACKUP_MODE_FULL = 1;
static final int BACKUP_MODE_RESTORE = 2;
+ static final int BACKUP_MODE_RESTORE_FULL = 3;
void scheduleCreateBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo,
int backupMode) throws RemoteException;
void scheduleDestroyBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo)
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 52fc623..8af78fa 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -79,4 +79,23 @@ oneway interface IBackupAgent {
*/
void doRestore(in ParcelFileDescriptor data, int appVersionCode,
in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder);
+
+ /**
+ * Restore a single "file" to the application. The file was typically obtained from
+ * a full-backup dataset. The agent reads 'size' bytes of file content
+ * from the provided file descriptor.
+ *
+ * @param data Read-only pipe delivering the file content itself.
+ *
+ * @param size Size of the file being restored.
+ * @param type Type of file system entity, e.g. FullBackup.TYPE_DIRECTORY.
+ * @param domain Name of the file's semantic domain to which the 'path' argument is a
+ * relative path. e.g. FullBackup.DATABASE_TREE_TOKEN.
+ * @param path Relative path of the file within its semantic domain.
+ * @param mode Access mode of the file system entity, e.g. 0660.
+ * @param mtime Last modification time of the file system entity.
+ */
+ void doRestoreFile(in ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime,
+ int token, IBackupManager callbackBinder);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index dc60e24..17f8adb 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -179,10 +179,18 @@ public abstract class BackupAgent extends ContextWrapper {
throws IOException;
/**
+ * @hide
+ */
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime)
+ throws IOException {
+ // empty stub implementation
+ }
+
+ /**
* Package-private, used only for dispatching an extra step during full backup
*/
void onSaveApk(BackupDataOutput data) {
- if (DEBUG) Log.v(TAG, "--- base onSaveApk() ---");
}
// ----- Core implementation -----
@@ -203,6 +211,7 @@ public abstract class BackupAgent extends ContextWrapper {
private class BackupServiceBinder extends IBackupAgent.Stub {
private static final String TAG = "BackupServiceBinder";
+ @Override
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState,
@@ -236,6 +245,7 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
+ @Override
public void doRestore(ParcelFileDescriptor data, int appVersionCode,
ParcelFileDescriptor newState,
int token, IBackupManager callbackBinder) throws RemoteException {
@@ -261,5 +271,25 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
}
+
+ @Override
+ public void doRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime,
+ int token, IBackupManager callbackBinder) throws RemoteException {
+ long ident = Binder.clearCallingIdentity();
+ try {
+Log.d(TAG, "doRestoreFile() => onRestoreFile()");
+ BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ try {
+ callbackBinder.opComplete(token);
+ } catch (RemoteException e) {
+ // we'll time out anyway, so we're safe
+ }
+ }
+ }
}
}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 9850566..dfb0dd7 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,6 +16,17 @@
package android.app.backup;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+
/**
* Global constant definitions et cetera related to the full-backup-to-fd
* binary format.
@@ -23,18 +34,95 @@ package android.app.backup;
* @hide
*/
public class FullBackup {
- public static String APK_TREE_TOKEN = "a";
- public static String OBB_TREE_TOKEN = "obb";
- public static String ROOT_TREE_TOKEN = "r";
- public static String DATA_TREE_TOKEN = "f";
- public static String DATABASE_TREE_TOKEN = "db";
- public static String SHAREDPREFS_TREE_TOKEN = "sp";
- public static String CACHE_TREE_TOKEN = "c";
-
- public static String FULL_BACKUP_INTENT_ACTION = "fullback";
- public static String FULL_RESTORE_INTENT_ACTION = "fullrest";
- public static String CONF_TOKEN_INTENT_EXTRA = "conftoken";
+ static final String TAG = "FullBackup";
+
+ public static final String APK_TREE_TOKEN = "a";
+ public static final String OBB_TREE_TOKEN = "obb";
+ public static final String ROOT_TREE_TOKEN = "r";
+ public static final String DATA_TREE_TOKEN = "f";
+ public static final String DATABASE_TREE_TOKEN = "db";
+ public static final String SHAREDPREFS_TREE_TOKEN = "sp";
+ public static final String CACHE_TREE_TOKEN = "c";
+ public static final String SHARED_STORAGE_TOKEN = "shared";
+
+ public static final String APPS_PREFIX = "apps/";
+ public static final String SHARED_PREFIX = "shared/";
+
+ public static final String FULL_BACKUP_INTENT_ACTION = "fullback";
+ public static final String FULL_RESTORE_INTENT_ACTION = "fullrest";
+ public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken";
+
+ public static final int TYPE_EOF = 0;
+ public static final int TYPE_FILE = 1;
+ public static final int TYPE_DIRECTORY = 2;
+ public static final int TYPE_SYMLINK = 3;
static public native int backupToTar(String packageName, String domain,
String linkdomain, String rootpath, String path, BackupDataOutput output);
+
+ static public void restoreToFile(ParcelFileDescriptor data,
+ long size, int type, long mode, long mtime, File outFile) throws IOException {
+ if (type == FullBackup.TYPE_DIRECTORY) {
+ // Canonically a directory has no associated content, so we don't need to read
+ // anything from the pipe in this case. Just create the directory here and
+ // drop down to the final metadata adjustment.
+ if (outFile != null) outFile.mkdirs();
+ } else {
+ FileOutputStream out = null;
+
+ // Pull the data from the pipe, copying it to the output file, until we're done
+ try {
+ if (outFile != null) {
+ File parent = outFile.getParentFile();
+ if (!parent.exists()) {
+ // in practice this will only be for the default semantic directories,
+ // and using the default mode for those is appropriate.
+ // TODO: support the edge case of apps that have adjusted the
+ // permissions on these core directories
+ parent.mkdirs();
+ }
+ out = new FileOutputStream(outFile);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to create/open file " + outFile.getPath(), e);
+ }
+
+ byte[] buffer = new byte[32 * 1024];
+ final long origSize = size;
+ FileInputStream in = new FileInputStream(data.getFileDescriptor());
+ while (size > 0) {
+ int toRead = (size > buffer.length) ? buffer.length : (int)size;
+ int got = in.read(buffer, 0, toRead);
+ if (got <= 0) {
+ Log.w(TAG, "Incomplete read: expected " + size + " but got "
+ + (origSize - size));
+ break;
+ }
+ if (out != null) {
+ try {
+ out.write(buffer, 0, got);
+ } catch (IOException e) {
+ // Problem writing to the file. Quit copying data and delete
+ // the file, but of course keep consuming the input stream.
+ Log.e(TAG, "Unable to write to file " + outFile.getPath(), e);
+ out.close();
+ out = null;
+ outFile.delete();
+ }
+ }
+ size -= got;
+ }
+ if (out != null) out.close();
+ }
+
+ // Now twiddle the state to match the backup, assuming all went well
+ if (outFile != null) {
+ try {
+ Libcore.os.chmod(outFile.getPath(), (int)mode);
+ } catch (ErrnoException e) {
+ e.rethrowAsIOException();
+ }
+ outFile.setLastModified(mtime);
+ }
+ }
}
diff --git a/core/java/android/app/backup/FullBackupAgent.java b/core/java/android/app/backup/FullBackupAgent.java
index f0a1f2a..4dca593 100644
--- a/core/java/android/app/backup/FullBackupAgent.java
+++ b/core/java/android/app/backup/FullBackupAgent.java
@@ -28,6 +28,9 @@ import libcore.io.OsConstants;
import libcore.io.StructStat;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
@@ -53,8 +56,12 @@ public class FullBackupAgent extends BackupAgent {
private String mCacheDir;
private String mLibDir;
+ private File NULL_FILE;
+
@Override
public void onCreate() {
+ NULL_FILE = new File("/dev/null");
+
mPm = getPackageManager();
try {
ApplicationInfo appInfo = mPm.getApplicationInfo(getPackageName(), 0);
@@ -177,7 +184,40 @@ public class FullBackupAgent extends BackupAgent {
}
}
+ /**
+ * Dummy -- We're never used for restore of an incremental dataset
+ */
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ }
+
+ /**
+ * Restore the described file from the given pipe.
+ */
@Override
- public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String relpath, long mode, long mtime)
+ throws IOException {
+ String basePath = null;
+ File outFile = null;
+
+ if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type
+ + " domain=" + domain + " relpath=" + relpath + " mode=" + mode
+ + " mtime=" + mtime);
+
+ // Parse out the semantic domains into the correct physical location
+ if (domain.equals(FullBackup.DATA_TREE_TOKEN)) basePath = mFilesDir;
+ else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) basePath = mDatabaseDir;
+ else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) basePath = mMainDir;
+ else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) basePath = mSharedPrefsDir;
+
+ // Not a supported output location? We need to consume the data
+ // anyway, so send it to /dev/null
+ outFile = (basePath != null) ? new File(basePath, relpath) : null;
+ if (DEBUG) Log.i(TAG, "[" + domain + " : " + relpath + "] mapped to " + outFile.getPath());
+
+ // Now that we've figured out where the data goes, send it on its way
+ FullBackup.restoreToFile(data, size, type, mode, mtime, outFile);
}
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 94e31a8..bac874e 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -147,6 +147,14 @@ interface IBackupManager {
boolean allApps, in String[] packageNames);
/**
+ * Restore device content from the data stream passed through the given socket. The
+ * data stream must be in the format emitted by fullBackup().
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void fullRestore(in ParcelFileDescriptor fd);
+
+ /**
* Confirm that the requested full backup/restore operation can proceed. The system will
* not actually perform the operation described to fullBackup() / fullRestore() unless the
* UI calls back into the Backup Manager to confirm, passing the correct token. At
diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp
index e15875f..f933199 100644
--- a/libs/utils/BackupHelpers.cpp
+++ b/libs/utils/BackupHelpers.cpp
@@ -503,6 +503,16 @@ int write_tarfile(const String8& packageName, const String8& domain,
needExtended = true;
}
+ // Non-7bit-clean path also means needing pax extended format
+ if (!needExtended) {
+ for (size_t i = 0; i < filepath.length(); i++) {
+ if ((filepath[i] & 0x80) != 0) {
+ needExtended = true;
+ break;
+ }
+ }
+ }
+
int err = 0;
struct stat64 s;
if (lstat64(filepath.string(), &s) != 0) {
diff --git a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
index 4b42067..52bfc28 100644
--- a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
+++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
@@ -98,6 +98,8 @@ public class BackupRestoreConfirmation extends Activity {
break;
case MSG_RESTORE_PACKAGE: {
+ String name = (String) msg.obj;
+ mStatusView.setText(name);
}
break;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 45bb2b6..0c4ef7d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -55,6 +55,7 @@ import android.util.Log;
*/
public class SettingsBackupAgent extends BackupAgentHelper {
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_BACKUP = DEBUG || true;
private static final String KEY_SYSTEM = "system";
private static final String KEY_SECURE = "secure";
@@ -75,6 +76,9 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private static final int STATE_WIFI_CONFIG = 4;
private static final int STATE_SIZE = 5; // The number of state items
+ // Versioning of the 'full backup' format
+ private static final int FULL_BACKUP_VERSION = 1;
+
private static String[] sortedSystemKeys = null;
private static String[] sortedSecureKeys = null;
@@ -109,6 +113,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private static String mWifiConfigFile;
public void onCreate() {
+ if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked");
+
mSettingsHelper = new SettingsHelper(this);
super.onCreate();
@@ -151,26 +157,32 @@ public class SettingsBackupAgent extends BackupAgentHelper {
// representation of the backed-up settings.
String root = getFilesDir().getAbsolutePath();
File stage = new File(root, STAGE_FILE);
- FileOutputStream filestream = new FileOutputStream(stage);
- BufferedOutputStream bufstream = new BufferedOutputStream(filestream);
- DataOutputStream out = new DataOutputStream(bufstream);
-
- out.writeInt(systemSettingsData.length);
- out.write(systemSettingsData);
- out.writeInt(secureSettingsData.length);
- out.write(secureSettingsData);
- out.writeInt(locale.length);
- out.write(locale);
- out.writeInt(wifiSupplicantData.length);
- out.write(wifiSupplicantData);
- out.writeInt(wifiConfigData.length);
- out.write(wifiConfigData);
-
- out.flush(); // also flushes downstream
-
- // now we're set to emit the tar stream
- FullBackup.backupToTar(getPackageName(), FullBackup.DATA_TREE_TOKEN, null,
- root, stage.getAbsolutePath(), data);
+ try {
+ FileOutputStream filestream = new FileOutputStream(stage);
+ BufferedOutputStream bufstream = new BufferedOutputStream(filestream);
+ DataOutputStream out = new DataOutputStream(bufstream);
+
+ out.writeInt(FULL_BACKUP_VERSION);
+
+ out.writeInt(systemSettingsData.length);
+ out.write(systemSettingsData);
+ out.writeInt(secureSettingsData.length);
+ out.write(secureSettingsData);
+ out.writeInt(locale.length);
+ out.write(locale);
+ out.writeInt(wifiSupplicantData.length);
+ out.write(wifiSupplicantData);
+ out.writeInt(wifiConfigData.length);
+ out.write(wifiConfigData);
+
+ out.flush(); // also flushes downstream
+
+ // now we're set to emit the tar stream
+ FullBackup.backupToTar(getPackageName(), FullBackup.DATA_TREE_TOKEN, null,
+ root, stage.getAbsolutePath(), data);
+ } finally {
+ stage.delete();
+ }
}
}
@@ -199,7 +211,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
} else if (KEY_LOCALE.equals(key)) {
byte[] localeData = new byte[size];
data.readEntityData(localeData, 0, size);
- mSettingsHelper.setLocaleData(localeData);
+ mSettingsHelper.setLocaleData(localeData, size);
} else if (KEY_WIFI_CONFIG.equals(key)) {
restoreFileData(mWifiConfigFile, data);
} else {
@@ -208,6 +220,70 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
}
+ @Override
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String relpath, long mode, long mtime)
+ throws IOException {
+ if (DEBUG_BACKUP) Log.d(TAG, "onRestoreFile() invoked");
+ // Our data is actually a blob of flattened settings data identical to that
+ // produced during incremental backups. Just unpack and apply it all in
+ // turn.
+ FileInputStream instream = new FileInputStream(data.getFileDescriptor());
+ DataInputStream in = new DataInputStream(instream);
+
+ int version = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, "Flattened data version " + version);
+ if (version == FULL_BACKUP_VERSION) {
+ // system settings data first
+ int nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of settings data");
+ byte[] buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI);
+
+ // secure settings
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI);
+
+ // locale
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of locale data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ mSettingsHelper.setLocaleData(buffer, nBytes);
+
+ // wifi supplicant
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi supplicant data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ int retainedWifiState = enableWifi(false);
+ restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes);
+ FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR |
+ FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+ Process.myUid(), Process.WIFI_UID);
+ // retain the previous WIFI state.
+ enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED ||
+ retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
+
+ // wifi config
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi config data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.read(buffer, 0, nBytes);
+ restoreFileData(mWifiConfigFile, buffer, nBytes);
+
+ if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
+ } else {
+ data.close();
+ throw new IOException("Invalid file schema");
+ }
+ }
+
private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException {
long[] stateChecksums = new long[STATE_SIZE];
@@ -287,6 +363,17 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
private void restoreSettings(BackupDataInput data, Uri contentUri) {
+ byte[] settings = new byte[data.getDataSize()];
+ try {
+ data.readEntityData(settings, 0, settings.length);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Couldn't read entity data");
+ return;
+ }
+ restoreSettings(settings, settings.length, contentUri);
+ }
+
+ private void restoreSettings(byte[] settings, int bytes, Uri contentUri) {
if (DEBUG) Log.i(TAG, "restoreSettings: " + contentUri);
String[] whitelist = null;
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
@@ -296,15 +383,8 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
ContentValues cv = new ContentValues(2);
- byte[] settings = new byte[data.getDataSize()];
- try {
- data.readEntityData(settings, 0, settings.length);
- } catch (IOException ioe) {
- Log.e(TAG, "Couldn't read entity data");
- return;
- }
int pos = 0;
- while (pos < settings.length) {
+ while (pos < bytes) {
int length = readInt(settings, pos);
pos += 4;
String settingName = length > 0? new String(settings, pos, length) : null;
@@ -451,13 +531,16 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private void restoreFileData(String filename, BackupDataInput data) {
byte[] bytes = new byte[data.getDataSize()];
if (bytes.length <= 0) return;
+ restoreFileData(filename, bytes, bytes.length);
+ }
+
+ private void restoreFileData(String filename, byte[] bytes, int size) {
try {
- data.readEntityData(bytes, 0, bytes.length);
File file = new File(filename);
if (file.exists()) file.delete();
OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
- os.write(bytes);
+ os.write(bytes, 0, size);
os.close();
} catch (IOException ioe) {
Log.w(TAG, "Couldn't restore " + filename);
@@ -506,15 +589,18 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private void restoreWifiSupplicant(String filename, BackupDataInput data) {
byte[] bytes = new byte[data.getDataSize()];
if (bytes.length <= 0) return;
+ restoreWifiSupplicant(filename, bytes, bytes.length);
+ }
+
+ private void restoreWifiSupplicant(String filename, byte[] bytes, int size) {
try {
- data.readEntityData(bytes, 0, bytes.length);
File supplicantFile = new File(FILE_WIFI_SUPPLICANT);
if (supplicantFile.exists()) supplicantFile.delete();
copyWifiSupplicantTemplate();
OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
os.write("\n".getBytes());
- os.write(bytes);
+ os.write(bytes, 0, size);
os.close();
} catch (IOException ioe) {
Log.w(TAG, "Couldn't restore " + filename);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 0e75fbc..3e7d86a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -147,7 +147,7 @@ public class SettingsHelper {
* "ll" is the language code and "cc" is the country code.
* @param data the locale string in bytes.
*/
- void setLocaleData(byte[] data) {
+ void setLocaleData(byte[] data, int size) {
// Check if locale was set by the user:
Configuration conf = mContext.getResources().getConfiguration();
Locale loc = conf.locale;
@@ -157,9 +157,9 @@ public class SettingsHelper {
if (conf.userSetLocale) return; // Don't change if user set it in the SetupWizard
final String[] availableLocales = mContext.getAssets().getLocales();
- String localeCode = new String(data);
+ String localeCode = new String(data, 0, size);
String language = new String(data, 0, 2);
- String country = data.length > 4 ? new String(data, 3, 2) : "";
+ String country = size > 4 ? new String(data, 3, 2) : "";
loc = null;
for (int i = 0; i < availableLocales.length; i++) {
if (availableLocales[i].equals(localeCode)) {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 455b4c2..cd58b9b 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -39,6 +39,7 @@ import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -46,6 +47,7 @@ import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -74,12 +76,16 @@ import com.android.server.PackageManagerBackupAgent.Metadata;
import java.io.EOFException;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -398,6 +404,13 @@ class BackupManagerService extends IBackupManager.Stub {
break;
}
+ case MSG_RUN_FULL_RESTORE:
+ {
+ FullRestoreParams params = (FullRestoreParams)msg.obj;
+ (new PerformFullRestoreTask(params.fd, params.observer, params.latch)).run();
+ break;
+ }
+
case MSG_RUN_CLEAR:
{
ClearParams params = (ClearParams)msg.obj;
@@ -1236,7 +1249,7 @@ class BackupManagerService extends IBackupManager.Stub {
Slog.d(TAG, "awaiting agent for " + app);
// success; wait for the agent to arrive
- // only wait 10 seconds for the clear data to happen
+ // only wait 10 seconds for the bind to happen
long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
while (mConnecting && mConnectedAgent == null
&& (System.currentTimeMillis() < timeoutMark)) {
@@ -1677,6 +1690,7 @@ class BackupManagerService extends IBackupManager.Stub {
public void run() {
final List<PackageInfo> packagesToBackup;
+ Slog.i(TAG, "--- Performing full-dataset restore ---");
sendStartBackup();
// doAllApps supersedes the package set if any
@@ -1779,7 +1793,9 @@ class BackupManagerService extends IBackupManager.Stub {
// Version 1:
// package name
// package's versionCode
- // boolean: "1" if archive includes .apk, "0" otherwise
+ // platform versionCode
+ // getInstallerPackageName() for this package (maybe empty)
+ // boolean: "1" if archive includes .apk; any other string means not
// number of signatures == N
// N*: signature byte array in ascii format per Signature.toCharsString()
StringBuilder builder = new StringBuilder(4096);
@@ -1788,6 +1804,11 @@ class BackupManagerService extends IBackupManager.Stub {
printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
printer.println(pkg.packageName);
printer.println(Integer.toString(pkg.versionCode));
+ printer.println(Integer.toString(Build.VERSION.SDK_INT));
+
+ String installerName = mPackageManager.getInstallerPackageName(pkg.packageName);
+ printer.println((installerName != null) ? installerName : "");
+
printer.println(withApk ? "1" : "0");
if (pkg.signatures == null) {
printer.println("0");
@@ -1861,6 +1882,896 @@ class BackupManagerService extends IBackupManager.Stub {
}
+ // ----- Full restore from a file/socket -----
+
+ // Description of a file in the restore datastream
+ static class FileMetadata {
+ String packageName; // name of the owning app
+ String installerPackageName; // name of the market-type app that installed the owner
+ int type; // e.g. FullBackup.TYPE_DIRECTORY
+ String domain; // e.g. FullBackup.DATABASE_TREE_TOKEN
+ String path; // subpath within the semantic domain
+ long mode; // e.g. 0666 (actually int)
+ long mtime; // last mod time, UTC time_t (actually int)
+ long size; // bytes of content
+ }
+
+ enum RestorePolicy {
+ IGNORE,
+ ACCEPT,
+ ACCEPT_IF_APK
+ }
+
+ class PerformFullRestoreTask implements Runnable {
+ ParcelFileDescriptor mInputFile;
+ IFullBackupRestoreObserver mObserver;
+ AtomicBoolean mLatchObject;
+ IBackupAgent mAgent;
+ String mAgentPackage;
+ ApplicationInfo mTargetApp;
+ ParcelFileDescriptor[] mPipes = null;
+
+ // possible handling states for a given package in the restore dataset
+ final HashMap<String, RestorePolicy> mPackagePolicies
+ = new HashMap<String, RestorePolicy>();
+
+ // installer package names for each encountered app, derived from the manifests
+ final HashMap<String, String> mPackageInstallers = new HashMap<String, String>();
+
+ // Signatures for a given package found in its manifest file
+ final HashMap<String, Signature[]> mManifestSignatures
+ = new HashMap<String, Signature[]>();
+
+ // Packages we've already wiped data on when restoring their first file
+ final HashSet<String> mClearedPackages = new HashSet<String>();
+
+ PerformFullRestoreTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
+ AtomicBoolean latch) {
+ mInputFile = fd;
+ mObserver = observer;
+ mLatchObject = latch;
+ mAgent = null;
+ mAgentPackage = null;
+ mTargetApp = null;
+
+ // Which packages we've already wiped data on. We prepopulate this
+ // with a whitelist of packages known to be unclearable.
+ mClearedPackages.add("android");
+ mClearedPackages.add("com.android.backupconfirm");
+ mClearedPackages.add("com.android.providers.settings");
+ }
+
+ class RestoreFileRunnable implements Runnable {
+ IBackupAgent mAgent;
+ FileMetadata mInfo;
+ ParcelFileDescriptor mSocket;
+ int mToken;
+
+ RestoreFileRunnable(IBackupAgent agent, FileMetadata info,
+ ParcelFileDescriptor socket, int token) throws IOException {
+ mAgent = agent;
+ mInfo = info;
+ mToken = token;
+
+ // This class is used strictly for process-local binder invocations. The
+ // semantics of ParcelFileDescriptor differ in this case; in particular, we
+ // do not automatically get a 'dup'ed descriptor that we can can continue
+ // to use asynchronously from the caller. So, we make sure to dup it ourselves
+ // before proceeding to do the restore.
+ mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor());
+ }
+
+ @Override
+ public void run() {
+ try {
+ mAgent.doRestoreFile(mSocket, mInfo.size, mInfo.type,
+ mInfo.domain, mInfo.path, mInfo.mode, mInfo.mtime,
+ mToken, mBackupManagerBinder);
+ } catch (RemoteException e) {
+ // never happens; this is used strictly for local binder calls
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ Slog.i(TAG, "--- Performing full-dataset restore ---");
+ sendStartRestore();
+
+ try {
+ byte[] buffer = new byte[32 * 1024];
+ FileInputStream instream = new FileInputStream(mInputFile.getFileDescriptor());
+
+ boolean didRestore;
+ do {
+ didRestore = restoreOneFile(instream, buffer);
+ } while (didRestore);
+
+ if (DEBUG) Slog.v(TAG, "Done consuming input tarfile");
+ } finally {
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
+
+ try {
+ mInputFile.close();
+ } catch (IOException e) {
+ /* nothing we can do about this */
+ }
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.clear();
+ }
+ synchronized (mLatchObject) {
+ mLatchObject.set(true);
+ mLatchObject.notifyAll();
+ }
+ sendEndRestore();
+ mWakelock.release();
+ if (DEBUG) Slog.d(TAG, "Full restore pass complete.");
+ }
+ }
+
+ boolean restoreOneFile(InputStream instream, byte[] buffer) {
+ FileMetadata info;
+ try {
+ info = readTarHeaders(instream);
+ if (info != null) {
+ if (DEBUG) {
+ dumpFileMetadata(info);
+ }
+
+ final String pkg = info.packageName;
+ if (!pkg.equals(mAgentPackage)) {
+ // okay, change in package; set up our various
+ // bookkeeping if we haven't seen it yet
+ if (!mPackagePolicies.containsKey(pkg)) {
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+
+ // 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");
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
+ mTargetApp = null;
+ mAgentPackage = null;
+ }
+ }
+
+ if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
+ mPackagePolicies.put(pkg, readAppManifest(info, instream));
+ mPackageInstallers.put(pkg, info.installerPackageName);
+ // We've read only the manifest content itself at this point,
+ // so consume the footer before looping around to the next
+ // input file
+ skipTarPadding(info.size, instream);
+ sendOnRestorePackage(pkg);
+ } else {
+ // Non-manifest, so it's actual file data. Is this a package
+ // we're ignoring?
+ boolean okay = true;
+ RestorePolicy policy = mPackagePolicies.get(pkg);
+ switch (policy) {
+ case IGNORE:
+ okay = false;
+ break;
+
+ case ACCEPT_IF_APK:
+ // If we're in accept-if-apk state, then the first file we
+ // see MUST be the apk.
+ if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
+ if (DEBUG) Slog.d(TAG, "APK file; installing");
+ // Try to install the app.
+ String installerName = mPackageInstallers.get(pkg);
+ okay = installApk(info, installerName, instream);
+ // good to go; promote to ACCEPT
+ mPackagePolicies.put(pkg, (okay)
+ ? RestorePolicy.ACCEPT
+ : RestorePolicy.IGNORE);
+ // At this point we've consumed this file entry
+ // ourselves, so just strip the tar footer and
+ // go on to the next file in the input stream
+ skipTarPadding(info.size, instream);
+ return true;
+ } else {
+ // File data before (or without) the apk. We can't
+ // handle it coherently in this case so ignore it.
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ okay = false;
+ }
+ break;
+
+ case ACCEPT:
+ if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
+ if (DEBUG) Slog.d(TAG, "apk present but ACCEPT");
+ // we can take the data without the apk, so we
+ // *want* to do so. skip the apk by declaring this
+ // one file not-okay without changing the restore
+ // policy for the package.
+ okay = false;
+ }
+ break;
+
+ default:
+ // Something has gone dreadfully wrong when determining
+ // the restore policy from the manifest. Ignore the
+ // rest of this package's data.
+ Slog.e(TAG, "Invalid policy from manifest");
+ okay = false;
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ break;
+ }
+
+ // If the policy is satisfied, go ahead and set up to pipe the
+ // data to the agent.
+ if (DEBUG && okay && mAgent != null) {
+ Slog.i(TAG, "Reusing existing agent instance");
+ }
+ if (okay && mAgent == null) {
+ if (DEBUG) Slog.d(TAG, "Need to launch agent for " + pkg);
+
+ try {
+ mTargetApp = mPackageManager.getApplicationInfo(pkg, 0);
+
+ // If we haven't sent any data to this app yet, we probably
+ // need to clear it first. Check that.
+ if (!mClearedPackages.contains(pkg)) {
+ // apps with their own full backup agents are
+ // responsible for coherently managing a full
+ // restore.
+ if (mTargetApp.fullBackupAgentName == null) {
+ if (DEBUG) Slog.d(TAG, "Clearing app data preparatory to full restore");
+ clearApplicationDataSynchronous(pkg);
+ } else {
+ if (DEBUG) Slog.d(TAG, "full backup agent ("
+ + mTargetApp.fullBackupAgentName + ") => no clear");
+ }
+ mClearedPackages.add(pkg);
+ } else {
+ if (DEBUG) Slog.d(TAG, "We've initialized this app already; no clear required");
+ }
+
+ // All set; now set up the IPC and launch the agent
+ setUpPipes();
+ mAgent = bindToAgentSynchronous(mTargetApp,
+ IApplicationThread.BACKUP_MODE_RESTORE_FULL);
+ mAgentPackage = pkg;
+ } catch (IOException e) {
+ // fall through to error handling
+ } catch (NameNotFoundException e) {
+ // fall through to error handling
+ }
+
+ if (mAgent == null) {
+ if (DEBUG) Slog.d(TAG, "Unable to create agent for " + pkg);
+ okay = false;
+ tearDownPipes();
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+ }
+
+ // Sanity check: make sure we never give data to the wrong app. This
+ // should never happen but a little paranoia here won't go amiss.
+ if (okay && !pkg.equals(mAgentPackage)) {
+ Slog.e(TAG, "Restoring data for " + pkg
+ + " but agent is for " + mAgentPackage);
+ okay = false;
+ }
+
+ // At this point we have an agent ready to handle the full
+ // restore data as well as a pipe for sending data to
+ // that agent. Tell the agent to start reading from the
+ // pipe.
+ if (okay) {
+ boolean agentSuccess = true;
+ long toCopy = info.size;
+ final int token = generateToken();
+ try {
+ if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ + info.path);
+ prepareOperationTimeout(token,
+ TIMEOUT_FULL_BACKUP_INTERVAL);
+ // fire up the app's agent listening on the socket. If
+ // the agent is running in the system process we can't
+ // just invoke it asynchronously, so we provide a thread
+ // for it here.
+ if (mTargetApp.processName.equals("system")) {
+ Slog.d(TAG, "system process agent - spinning a thread");
+ RestoreFileRunnable runner = new RestoreFileRunnable(
+ mAgent, info, mPipes[0], token);
+ new Thread(runner).start();
+ } else {
+ mAgent.doRestoreFile(mPipes[0], info.size, info.type,
+ info.domain, info.path, info.mode, info.mtime,
+ token, mBackupManagerBinder);
+ }
+ } catch (IOException e) {
+ // couldn't dup the socket for a process-local restore
+ Slog.d(TAG, "Couldn't establish restore");
+ agentSuccess = false;
+ okay = false;
+ } catch (RemoteException e) {
+ // whoops, remote agent went away. We'll eat the content
+ // ourselves, then, and not copy it over.
+ Slog.e(TAG, "Agent crashed during full restore");
+ agentSuccess = false;
+ okay = false;
+ }
+
+ // Copy over the data if the agent is still good
+ if (okay) {
+ boolean pipeOkay = true;
+ FileOutputStream pipe = new FileOutputStream(
+ mPipes[1].getFileDescriptor());
+ if (DEBUG) Slog.d(TAG, "Piping data to agent");
+ while (toCopy > 0) {
+ int toRead = (toCopy > buffer.length)
+ ? buffer.length : (int)toCopy;
+ int nRead = instream.read(buffer, 0, toRead);
+ if (nRead <= 0) break;
+ toCopy -= nRead;
+
+ // send it to the output pipe as long as things
+ // are still good
+ if (pipeOkay) {
+ try {
+ pipe.write(buffer, 0, nRead);
+ } catch (IOException e) {
+ Slog.e(TAG,
+ "Failed to write to restore pipe", e);
+ pipeOkay = false;
+ }
+ }
+ }
+
+ // done sending that file! Now we just need to consume
+ // the delta from info.size to the end of block.
+ skipTarPadding(info.size, instream);
+
+ // and now that we've sent it all, wait for the remote
+ // side to acknowledge receipt
+ agentSuccess = waitUntilOperationComplete(token);
+ }
+
+ // okay, if the remote end failed at any point, deal with
+ // it by ignoring the rest of the restore on it
+ if (!agentSuccess) {
+ mBackupHandler.removeMessages(MSG_TIMEOUT);
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
+ mAgent = null;
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+ }
+
+ // Problems setting up the agent communication, or an already-
+ // ignored package: skip to the next tar stream entry by
+ // reading and discarding this file.
+ if (!okay) {
+ if (DEBUG) Slog.d(TAG, "[discarding file content]");
+ long bytesToConsume = (info.size + 511) & ~511;
+ while (bytesToConsume > 0) {
+ int toRead = (bytesToConsume > buffer.length)
+ ? buffer.length : (int)bytesToConsume;
+ long nRead = instream.read(buffer, 0, toRead);
+ if (nRead <= 0) break;
+ bytesToConsume -= nRead;
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "io exception on restore socket read", e);
+ // treat as EOF
+ info = null;
+ }
+
+ return (info != null);
+ }
+
+ void setUpPipes() throws IOException {
+ mPipes = ParcelFileDescriptor.createPipe();
+ }
+
+ void tearDownPipes() {
+ if (mPipes != null) {
+ if (mPipes[0] != null) {
+ try {
+ mPipes[0].close();
+ mPipes[0] = null;
+ mPipes[1].close();
+ mPipes[1] = null;
+ } catch (IOException e) {
+ Slog.w(TAG, "Couldn't close agent pipes", e);
+ }
+ }
+ mPipes = null;
+ }
+ }
+
+ void tearDownAgent(ApplicationInfo app) {
+ if (mAgent != null) {
+ try {
+ // unbind and tidy up even on timeout or failure, just in case
+ mActivityManager.unbindBackupAgent(app);
+
+ // The agent was running with a stub Application object, so shut it down.
+ // !!! We hardcode the confirmation UI's package name here rather than use a
+ // manifest flag! TODO something less direct.
+ if (app.uid != Process.SYSTEM_UID
+ && !app.packageName.equals("com.android.backupconfirm")) {
+ if (DEBUG) Slog.d(TAG, "Killing host process");
+ mActivityManager.killApplicationProcess(app.processName, app.uid);
+ } else {
+ if (DEBUG) Slog.d(TAG, "Not killing after full restore");
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Lost app trying to shut down");
+ }
+ mAgent = null;
+ }
+ }
+
+ class RestoreInstallObserver extends IPackageInstallObserver.Stub {
+ final AtomicBoolean mDone = new AtomicBoolean();
+ int mResult;
+
+ public void reset() {
+ synchronized (mDone) {
+ mDone.set(false);
+ }
+ }
+
+ public void waitForCompletion() {
+ synchronized (mDone) {
+ while (mDone.get() == false) {
+ try {
+ mDone.wait();
+ } catch (InterruptedException e) { }
+ }
+ }
+ }
+
+ int getResult() {
+ return mResult;
+ }
+
+ @Override
+ public void packageInstalled(String packageName, int returnCode)
+ throws RemoteException {
+ synchronized (mDone) {
+ mResult = returnCode;
+ mDone.set(true);
+ mDone.notifyAll();
+ }
+ }
+ }
+ final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
+
+ boolean installApk(FileMetadata info, String installerPackage, InputStream instream) {
+ boolean okay = true;
+
+ if (DEBUG) Slog.d(TAG, "Installing from backup: " + info.packageName);
+
+ // The file content is an .apk file. Copy it out to a staging location and
+ // attempt to install it.
+ File apkFile = new File(mDataDir, info.packageName);
+ try {
+ FileOutputStream apkStream = new FileOutputStream(apkFile);
+ byte[] buffer = new byte[32 * 1024];
+ long size = info.size;
+ while (size > 0) {
+ long toRead = (buffer.length < size) ? buffer.length : size;
+ int didRead = instream.read(buffer, 0, (int)toRead);
+ apkStream.write(buffer, 0, didRead);
+ size -= didRead;
+ }
+ apkStream.close();
+
+ // make sure the installer can read it
+ apkFile.setReadable(true, false);
+
+ // Now install it
+ Uri packageUri = Uri.fromFile(apkFile);
+ mInstallObserver.reset();
+ mPackageManager.installPackage(packageUri, mInstallObserver,
+ PackageManager.INSTALL_REPLACE_EXISTING, installerPackage);
+ mInstallObserver.waitForCompletion();
+
+ if (mInstallObserver.getResult() != PackageManager.INSTALL_SUCCEEDED) {
+ // The only time we continue to accept install of data even if the
+ // apk install failed is if we had already determined that we could
+ // accept the data regardless.
+ if (mPackagePolicies.get(info.packageName) != RestorePolicy.ACCEPT) {
+ okay = false;
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to transcribe restored apk for install");
+ okay = false;
+ } finally {
+ apkFile.delete();
+ }
+
+ return okay;
+ }
+
+ // Given an actual file content size, consume the post-content padding mandated
+ // by the tar format.
+ void skipTarPadding(long size, InputStream instream) throws IOException {
+ long partial = (size + 512) % 512;
+ if (partial > 0) {
+ byte[] buffer = new byte[512];
+ instream.read(buffer, 0, 512 - (int)partial);
+ }
+ }
+
+ // Returns a policy constant; takes a buffer arg to reduce memory churn
+ RestorePolicy readAppManifest(FileMetadata info, InputStream instream)
+ throws IOException {
+ // Fail on suspiciously large manifest files
+ if (info.size > 64 * 1024) {
+ throw new IOException("Restore manifest too big; corrupt? size=" + info.size);
+ }
+ byte[] buffer = new byte[(int) info.size];
+ int nRead = 0;
+ while (nRead < info.size) {
+ nRead += instream.read(buffer, nRead, (int)info.size - nRead);
+ }
+
+ RestorePolicy policy = RestorePolicy.IGNORE;
+ String[] str = new String[1];
+ int offset = 0;
+
+ try {
+ offset = extractLine(buffer, offset, str);
+ int version = Integer.parseInt(str[0]);
+ if (version == BACKUP_MANIFEST_VERSION) {
+ offset = extractLine(buffer, offset, str);
+ String manifestPackage = str[0];
+ // TODO: handle <original-package>
+ if (manifestPackage.equals(info.packageName)) {
+ offset = extractLine(buffer, offset, str);
+ version = Integer.parseInt(str[0]); // app version
+ offset = extractLine(buffer, offset, str);
+ int platformVersion = Integer.parseInt(str[0]);
+ offset = extractLine(buffer, offset, str);
+ info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
+ offset = extractLine(buffer, offset, str);
+ boolean hasApk = str[0].equals("1");
+ offset = extractLine(buffer, offset, str);
+ int numSigs = Integer.parseInt(str[0]);
+ Signature[] sigs = null;
+ if (numSigs > 0) {
+ sigs = new Signature[numSigs];
+ for (int i = 0; i < numSigs; i++) {
+ offset = extractLine(buffer, offset, str);
+ sigs[i] = new Signature(str[0]);
+ }
+
+ // Okay, got the manifest info we need...
+ try {
+ // Verify signatures against any installed version; if they
+ // don't match, then we fall though and ignore the data. The
+ // signatureMatch() method explicitly ignores the signature
+ // check for packages installed on the system partition, because
+ // such packages are signed with the platform cert instead of
+ // the app developer's cert, so they're different on every
+ // device.
+ PackageInfo pkgInfo = mPackageManager.getPackageInfo(
+ info.packageName, PackageManager.GET_SIGNATURES);
+ if (signaturesMatch(sigs, pkgInfo)) {
+ if (pkgInfo.versionCode >= version) {
+ Slog.i(TAG, "Sig + version match; taking data");
+ policy = RestorePolicy.ACCEPT;
+ } else {
+ // The data is from a newer version of the app than
+ // is presently installed. That means we can only
+ // use it if the matching apk is also supplied.
+ Slog.d(TAG, "Data version " + version
+ + " is newer than installed version "
+ + pkgInfo.versionCode + " - requiring apk");
+ policy = RestorePolicy.ACCEPT_IF_APK;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // Okay, the target app isn't installed. We can process
+ // the restore properly only if the dataset provides the
+ // apk file and we can successfully install it.
+ if (DEBUG) Slog.i(TAG, "Package " + info.packageName
+ + " not installed; requiring apk in dataset");
+ policy = RestorePolicy.ACCEPT_IF_APK;
+ }
+
+ if (policy == RestorePolicy.ACCEPT_IF_APK && !hasApk) {
+ Slog.i(TAG, "Cannot restore package " + info.packageName
+ + " without the matching .apk");
+ }
+ } else {
+ Slog.i(TAG, "Missing signature on backed-up package "
+ + info.packageName);
+ }
+ } else {
+ Slog.i(TAG, "Expected package " + info.packageName
+ + " but restore manifest claims " + manifestPackage);
+ }
+ } else {
+ Slog.i(TAG, "Unknown restore manifest version " + version
+ + " for package " + info.packageName);
+ }
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
+ }
+
+ return policy;
+ }
+
+ // Builds a line from a byte buffer starting at 'offset', and returns
+ // the index of the next unconsumed data in the buffer.
+ int extractLine(byte[] buffer, int offset, String[] outStr) throws IOException {
+ final int end = buffer.length;
+ if (offset >= end) throw new IOException("Incomplete data");
+
+ int pos;
+ for (pos = offset; pos < end; pos++) {
+ byte c = buffer[pos];
+ // at LF we declare end of line, and return the next char as the
+ // starting point for the next time through
+ if (c == '\n') {
+ break;
+ }
+ }
+ outStr[0] = new String(buffer, offset, pos - offset);
+ pos++; // may be pointing an extra byte past the end but that's okay
+ return pos;
+ }
+
+ void dumpFileMetadata(FileMetadata info) {
+ if (DEBUG) {
+ StringBuilder b = new StringBuilder(128);
+
+ // mode string
+ b.append((info.type == FullBackup.TYPE_DIRECTORY) ? 'd' : '-');
+ b.append(((info.mode & 0400) != 0) ? 'r' : '-');
+ b.append(((info.mode & 0200) != 0) ? 'w' : '-');
+ b.append(((info.mode & 0100) != 0) ? 'x' : '-');
+ b.append(((info.mode & 0040) != 0) ? 'r' : '-');
+ b.append(((info.mode & 0020) != 0) ? 'w' : '-');
+ b.append(((info.mode & 0010) != 0) ? 'x' : '-');
+ b.append(((info.mode & 0004) != 0) ? 'r' : '-');
+ b.append(((info.mode & 0002) != 0) ? 'w' : '-');
+ b.append(((info.mode & 0001) != 0) ? 'x' : '-');
+ b.append(String.format(" %9d ", info.size));
+
+ Date stamp = new Date(info.mtime);
+ b.append(new SimpleDateFormat("MMM dd kk:mm:ss ").format(stamp));
+
+ b.append(info.packageName);
+ b.append(" :: ");
+ b.append(info.domain);
+ b.append(" :: ");
+ b.append(info.path);
+
+ Slog.i(TAG, b.toString());
+ }
+ }
+ // Consume a tar file header block [sequence] and accumulate the relevant metadata
+ FileMetadata readTarHeaders(InputStream instream) throws IOException {
+ byte[] block = new byte[512];
+ FileMetadata info = null;
+
+ boolean gotHeader = readTarHeader(instream, block);
+ if (gotHeader) {
+ // okay, presume we're okay, and extract the various metadata
+ info = new FileMetadata();
+ info.size = extractRadix(block, 124, 12, 8);
+ info.mtime = extractRadix(block, 136, 12, 8);
+ info.mode = extractRadix(block, 100, 8, 8);
+
+ info.path = extractString(block, 345, 155); // prefix
+ String path = extractString(block, 0, 100);
+ if (path.length() > 0) {
+ if (info.path.length() > 0) info.path += '/';
+ info.path += path;
+ }
+
+ // tar link indicator field: 1 byte at offset 156 in the header.
+ int typeChar = block[156];
+ if (typeChar == 'x') {
+ // pax extended header, so we need to read that
+ gotHeader = readPaxExtendedHeader(instream, info);
+ if (gotHeader) {
+ // and after a pax extended header comes another real header -- read
+ // that to find the real file type
+ gotHeader = readTarHeader(instream, block);
+ }
+ if (!gotHeader) throw new IOException("Bad or missing pax header");
+
+ typeChar = block[156];
+ }
+
+ switch (typeChar) {
+ case '0': info.type = FullBackup.TYPE_FILE; break;
+ case '5': info.type = FullBackup.TYPE_DIRECTORY; break;
+ case 0: {
+ // presume EOF
+ return null;
+ }
+ default: {
+ Slog.e(TAG, "Unknown tar entity type: " + typeChar);
+ throw new IOException("Unknown entity type " + typeChar);
+ }
+ }
+
+ // Parse out the path
+ //
+ // first: apps/shared/unrecognized
+ if (FullBackup.SHARED_PREFIX.regionMatches(0,
+ info.path, 0, FullBackup.SHARED_PREFIX.length())) {
+ // File in shared storage. !!! TODO: implement this.
+ info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
+ info.domain = FullBackup.SHARED_STORAGE_TOKEN;
+ } else if (FullBackup.APPS_PREFIX.regionMatches(0,
+ info.path, 0, FullBackup.APPS_PREFIX.length())) {
+ // App content! Parse out the package name and domain
+
+ // strip the apps/ prefix
+ info.path = info.path.substring(FullBackup.APPS_PREFIX.length());
+
+ // extract the package name
+ int slash = info.path.indexOf('/');
+ if (slash < 0) throw new IOException("Illegal semantic path in " + info.path);
+ info.packageName = info.path.substring(0, slash);
+ info.path = info.path.substring(slash+1);
+
+ // if it's a manifest we're done, otherwise parse out the domains
+ if (!info.path.equals(BACKUP_MANIFEST_FILENAME)) {
+ slash = info.path.indexOf('/');
+ if (slash < 0) throw new IOException("Illegal semantic path in non-manifest " + info.path);
+ info.domain = info.path.substring(0, slash);
+ // validate that it's one of the domains we understand
+ if (!info.domain.equals(FullBackup.APK_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.DATA_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.DATABASE_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.ROOT_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.OBB_TREE_TOKEN)
+ && !info.domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
+ throw new IOException("Unrecognized domain " + info.domain);
+ }
+
+ info.path = info.path.substring(slash + 1);
+ }
+ }
+ }
+ return info;
+ }
+
+ boolean readTarHeader(InputStream instream, byte[] block) throws IOException {
+ int nRead = instream.read(block, 0, 512);
+ if (nRead > 0 && nRead != 512) {
+ // if we read only a partial block, then things are
+ // clearly screwed up. terminate the restore.
+ throw new IOException("Partial header block: " + nRead);
+ }
+ return (nRead > 0);
+ }
+
+ // overwrites 'info' fields based on the pax extended header
+ boolean readPaxExtendedHeader(InputStream instream, FileMetadata info)
+ throws IOException {
+ // We should never see a pax extended header larger than this
+ if (info.size > 32*1024) {
+ Slog.w(TAG, "Suspiciously large pax header size " + info.size
+ + " - aborting");
+ throw new IOException("Sanity failure: pax header size " + info.size);
+ }
+
+ // read whole blocks, not just the content size
+ int numBlocks = (int)((info.size + 511) >> 9);
+ byte[] data = new byte[numBlocks * 512];
+ int nRead = instream.read(data);
+ if (nRead != data.length) {
+ return false;
+ }
+
+ final int contentSize = (int) info.size;
+ int offset = 0;
+ do {
+ // extract the line at 'offset'
+ int eol = offset+1;
+ while (eol < contentSize && data[eol] != ' ') eol++;
+ if (eol >= contentSize) {
+ // error: we just hit EOD looking for the end of the size field
+ throw new IOException("Invalid pax data");
+ }
+ // eol points to the space between the count and the key
+ int linelen = (int) extractRadix(data, offset, eol - offset, 10);
+ int key = eol + 1; // start of key=value
+ eol = offset + linelen - 1; // trailing LF
+ int value;
+ for (value = key+1; data[value] != '=' && value <= eol; value++);
+ if (value > eol) {
+ throw new IOException("Invalid pax declaration");
+ }
+
+ // pax requires that key/value strings be in UTF-8
+ String keyStr = new String(data, key, value-key, "UTF-8");
+ // -1 to strip the trailing LF
+ String valStr = new String(data, value+1, eol-value-1, "UTF-8");
+
+ if ("path".equals(keyStr)) {
+ info.path = valStr;
+ } else if ("size".equals(keyStr)) {
+ info.size = Long.parseLong(valStr);
+ } else {
+ if (DEBUG) Slog.i(TAG, "Unhandled pax key: " + key);
+ }
+
+ offset += linelen;
+ } while (offset < contentSize);
+
+ return true;
+ }
+
+ long extractRadix(byte[] data, int offset, int maxChars, int radix)
+ throws IOException {
+ long value = 0;
+ final int end = offset + maxChars;
+ for (int i = offset; i < end; i++) {
+ final byte b = data[i];
+ if (b == 0 || b == ' ') break;
+ if (b < '0' || b > ('0' + radix - 1)) {
+ throw new IOException("Invalid number in header");
+ }
+ value = radix * value + (b - '0');
+ }
+ return value;
+ }
+
+ String extractString(byte[] data, int offset, int maxChars) throws IOException {
+ final int end = offset + maxChars;
+ int eos = offset;
+ // tar string fields can end with either NUL or SPC
+ while (eos < end && data[eos] != 0 && data[eos] != ' ') eos++;
+ return new String(data, offset, eos-offset, "US-ASCII");
+ }
+
+ void sendStartRestore() {
+ if (mObserver != null) {
+ try {
+ mObserver.onStartRestore();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: startRestore");
+ mObserver = null;
+ }
+ }
+ }
+
+ void sendOnRestorePackage(String name) {
+ if (mObserver != null) {
+ try {
+ // TODO: use a more user-friendly name string
+ mObserver.onRestorePackage(name);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: restorePackage");
+ mObserver = null;
+ }
+ }
+ }
+
+ void sendEndRestore() {
+ if (mObserver != null) {
+ try {
+ mObserver.onEndRestore();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: endRestore");
+ mObserver = null;
+ }
+ }
+ }
+ }
+
// ----- Restore handling -----
private boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
@@ -2583,43 +3494,97 @@ class BackupManagerService extends IBackupManager.Stub {
mFullConfirmations.put(token, params);
}
- // start up the confirmation UI, making sure the screen lights up
- if (DEBUG) Slog.d(TAG, "Starting confirmation UI, token=" + token);
- try {
- Intent confIntent = new Intent(FullBackup.FULL_BACKUP_INTENT_ACTION);
- confIntent.setClassName("com.android.backupconfirm",
- "com.android.backupconfirm.BackupRestoreConfirmation");
- confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token);
- confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(confIntent);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "Unable to launch full backup confirmation", e);
+ // start up the confirmation UI
+ if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
+ if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
+ Slog.e(TAG, "Unable to launch full backup confirmation");
mFullConfirmations.delete(token);
return;
}
+
+ // make sure the screen is lit for the user interaction
mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
// start the confirmation countdown
- if (DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
- + TIMEOUT_FULL_CONFIRMATION + " millis");
- Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
- token, 0, params);
- mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
+ startConfirmationTimeout(token, params);
// wait for the backup to be performed
if (DEBUG) Slog.d(TAG, "Waiting for full backup completion...");
waitForCompletion(params);
- if (DEBUG) Slog.d(TAG, "...Full backup operation complete!");
} finally {
- Binder.restoreCallingIdentity(oldId);
try {
fd.close();
} catch (IOException e) {
// just eat it
}
+ Binder.restoreCallingIdentity(oldId);
+ }
+ if (DEBUG) Slog.d(TAG, "Full backup done; returning to caller");
+ }
+
+ public void fullRestore(ParcelFileDescriptor fd) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
+ Slog.i(TAG, "Beginning full restore...");
+
+ long oldId = Binder.clearCallingIdentity();
+
+ try {
+ FullRestoreParams params = new FullRestoreParams(fd);
+ final int token = generateToken();
+ synchronized (mFullConfirmations) {
+ mFullConfirmations.put(token, params);
+ }
+
+ // start up the confirmation UI
+ if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
+ if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
+ Slog.e(TAG, "Unable to launch full restore confirmation");
+ mFullConfirmations.delete(token);
+ return;
+ }
+
+ // make sure the screen is lit for the user interaction
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+
+ // start the confirmation countdown
+ startConfirmationTimeout(token, params);
+
+ // wait for the restore to be performed
+ if (DEBUG) Slog.d(TAG, "Waiting for full restore completion...");
+ waitForCompletion(params);
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Error trying to close fd after full restore: " + e);
+ }
+ Binder.restoreCallingIdentity(oldId);
+ Slog.i(TAG, "Full restore completed");
}
}
+ boolean startConfirmationUi(int token, String action) {
+ try {
+ Intent confIntent = new Intent(action);
+ confIntent.setClassName("com.android.backupconfirm",
+ "com.android.backupconfirm.BackupRestoreConfirmation");
+ confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token);
+ confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(confIntent);
+ } catch (ActivityNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ void startConfirmationTimeout(int token, FullParams params) {
+ if (DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
+ + TIMEOUT_FULL_CONFIRMATION + " millis");
+ Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
+ token, 0, params);
+ mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
+ }
+
void waitForCompletion(FullParams params) {
synchronized (params.latch) {
while (params.latch.get() == false) {
@@ -2661,9 +3626,10 @@ class BackupManagerService extends IBackupManager.Stub {
if (allow) {
params.observer = observer;
final int verb = params instanceof FullBackupParams
- ? MSG_RUN_FULL_BACKUP
+ ? MSG_RUN_FULL_BACKUP
: MSG_RUN_FULL_RESTORE;
+ if (DEBUG) Slog.d(TAG, "Sending conf message with verb " + verb);
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(verb, params);
mBackupHandler.sendMessage(msg);
diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java
index 54555bb..99c8af6 100644
--- a/services/java/com/android/server/SystemBackupAgent.java
+++ b/services/java/com/android/server/SystemBackupAgent.java
@@ -37,16 +37,25 @@ import java.io.IOException;
public class SystemBackupAgent extends BackupAgentHelper {
private static final String TAG = "SystemBackupAgent";
- // These paths must match what the WallpaperManagerService uses
+ // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
+ // are also used in the full-backup file format, so must not change unless steps are
+ // taken to support the legacy backed-up datasets.
+ private static final String WALLPAPER_IMAGE_FILENAME = "wallpaper";
+ private static final String WALLPAPER_INFO_FILENAME = "wallpaper_info.xml";
+
private static final String WALLPAPER_IMAGE_DIR = "/data/data/com.android.settings/files";
- private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/wallpaper";
+ private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/" + WALLPAPER_IMAGE_FILENAME;
+
private static final String WALLPAPER_INFO_DIR = "/data/system";
- private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/wallpaper_info.xml";
+ private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/" + WALLPAPER_INFO_FILENAME;
+
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
if (oldState == null) {
+ // Ah, it's a full backup dataset, being restored piecemeal. Just
+ // pop over to the full restore handling and we're done.
runFullBackup(data);
return;
}
@@ -66,11 +75,18 @@ public class SystemBackupAgent extends BackupAgentHelper {
}
private void runFullBackup(BackupDataOutput output) {
- // Back up the data files directly
- FullBackup.backupToTar(getPackageName(), null, null,
- WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output);
- FullBackup.backupToTar(getPackageName(), null, null,
+ fullWallpaperBackup(output);
+ }
+
+ private void fullWallpaperBackup(BackupDataOutput output) {
+ // Back up the data files directly. We do them in this specific order --
+ // info file followed by image -- because then we need take no special
+ // steps during restore; the restore will happen properly when the individual
+ // files are restored piecemeal.
+ FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null,
WALLPAPER_INFO_DIR, WALLPAPER_INFO, output);
+ FullBackup.backupToTar(getPackageName(), FullBackup.ROOT_TREE_TOKEN, null,
+ WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output);
}
@Override
@@ -96,4 +112,46 @@ public class SystemBackupAgent extends BackupAgentHelper {
(new File(WALLPAPER_INFO)).delete();
}
}
+
+ @Override
+ public void onRestoreFile(ParcelFileDescriptor data, long size,
+ int type, String domain, String path, long mode, long mtime)
+ throws IOException {
+ Slog.i(TAG, "Restoring file domain=" + domain + " path=" + path);
+
+ // Bits to indicate postprocessing we may need to perform
+ boolean restoredWallpaper = false;
+
+ File outFile = null;
+ // Various domain+files we understand a priori
+ if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) {
+ if (path.equals(WALLPAPER_INFO_FILENAME)) {
+ outFile = new File(WALLPAPER_INFO);
+ restoredWallpaper = true;
+ } else if (path.equals(WALLPAPER_IMAGE_FILENAME)) {
+ outFile = new File(WALLPAPER_IMAGE);
+ restoredWallpaper = true;
+ }
+ }
+
+ try {
+ if (outFile == null) {
+ Slog.w(TAG, "Skipping unrecognized system file: [ " + domain + " : " + path + " ]");
+ }
+ FullBackup.restoreToFile(data, size, type, mode, mtime, outFile);
+
+ if (restoredWallpaper) {
+ WallpaperManagerService wallpaper =
+ (WallpaperManagerService)ServiceManager.getService(
+ Context.WALLPAPER_SERVICE);
+ wallpaper.settingsRestored();
+ }
+ } catch (IOException e) {
+ if (restoredWallpaper) {
+ // Make sure we wind up in a good state
+ (new File(WALLPAPER_IMAGE)).delete();
+ (new File(WALLPAPER_INFO)).delete();
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 262e5ce..b463e56 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -3676,6 +3676,7 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean isRestrictedBackupMode = false;
if (mBackupTarget != null && mBackupAppName.equals(processName)) {
isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE)
+ || (mBackupTarget.backupMode == BackupRecord.RESTORE_FULL)
|| (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL);
}
diff --git a/services/java/com/android/server/am/BackupRecord.java b/services/java/com/android/server/am/BackupRecord.java
index 6590b91..7e73106 100644
--- a/services/java/com/android/server/am/BackupRecord.java
+++ b/services/java/com/android/server/am/BackupRecord.java
@@ -26,6 +26,7 @@ class BackupRecord {
public static final int BACKUP_NORMAL = 0;
public static final int BACKUP_FULL = 1;
public static final int RESTORE = 2;
+ public static final int RESTORE_FULL = 3;
final BatteryStatsImpl.Uid.Pkg.Serv stats;
String stringName; // cached toString() output