diff --git a/api/current.txt b/api/current.txt
index e41b3feb2effa..09102fb234406 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4976,15 +4976,19 @@ package android.appwidget {
field public static final java.lang.String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
field public static final java.lang.String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED";
field public static final java.lang.String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
+ field public static final java.lang.String ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED";
field public static final java.lang.String ACTION_APPWIDGET_OPTIONS_CHANGED = "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS";
field public static final java.lang.String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK";
+ field public static final java.lang.String ACTION_APPWIDGET_RESTORED = "android.appwidget.action.APPWIDGET_RESTORED";
field public static final java.lang.String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
field public static final java.lang.String EXTRA_APPWIDGET_ID = "appWidgetId";
field public static final java.lang.String EXTRA_APPWIDGET_IDS = "appWidgetIds";
+ field public static final java.lang.String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
field public static final java.lang.String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions";
field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
field public static final java.lang.String EXTRA_CUSTOM_EXTRAS = "customExtras";
field public static final java.lang.String EXTRA_CUSTOM_INFO = "customInfo";
+ field public static final java.lang.String EXTRA_HOST_ID = "hostId";
field public static final int INVALID_APPWIDGET_ID = 0; // 0x0
field public static final java.lang.String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider";
field public static final java.lang.String OPTION_APPWIDGET_HOST_CATEGORY = "appWidgetCategory";
@@ -5001,6 +5005,7 @@ package android.appwidget {
method public void onDisabled(android.content.Context);
method public void onEnabled(android.content.Context);
method public void onReceive(android.content.Context, android.content.Intent);
+ method public void onRestored(android.content.Context, int[], int[]);
method public void onUpdate(android.content.Context, android.appwidget.AppWidgetManager, int[]);
}
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index 73fd66072d7fd..2673031ddbafc 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -68,6 +68,7 @@ public final class Backup {
boolean saveObbs = false;
boolean saveShared = false;
boolean doEverything = false;
+ boolean doWidgets = false;
boolean allIncludesSystem = true;
String arg;
@@ -89,6 +90,10 @@ public final class Backup {
allIncludesSystem = true;
} else if ("-nosystem".equals(arg)) {
allIncludesSystem = false;
+ } else if ("-widgets".equals(arg)) {
+ doWidgets = true;
+ } else if ("-nowidgets".equals(arg)) {
+ doWidgets = false;
} else if ("-all".equals(arg)) {
doEverything = true;
} else {
@@ -114,8 +119,8 @@ public final class Backup {
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
String[] packArray = new String[packages.size()];
- mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doEverything,
- allIncludesSystem, packages.toArray(packArray));
+ mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets,
+ doEverything, allIncludesSystem, packages.toArray(packArray));
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for backup");
} finally {
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 087f83c396dd1..4ca06edb34516 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -76,8 +76,9 @@ oneway interface IBackupAgent {
* @param callbackBinder Binder on which to indicate operation completion,
* passed here as a convenience to the agent.
*/
- void doRestore(in ParcelFileDescriptor data, int appVersionCode,
- in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder);
+ void doRestore(in ParcelFileDescriptor data,
+ int appVersionCode, in ParcelFileDescriptor newState,
+ int token, IBackupManager callbackBinder);
/**
* Perform a "full" backup to the given file descriptor. The output file is presumed
@@ -112,8 +113,15 @@ oneway interface IBackupAgent {
* @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.
+ * @param token Opaque token identifying this transaction. This must
+ * be echoed back to the backup service binder once the agent is
+ * finished restoring the application based on the restore data
+ * contents.
+ * @param callbackBinder Binder on which to indicate operation completion,
+ * passed here as a convenience to the agent.
*/
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/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java
index 3a070b602c125..845784f8d91a8 100644
--- a/core/java/android/app/backup/BackupDataOutput.java
+++ b/core/java/android/app/backup/BackupDataOutput.java
@@ -17,6 +17,7 @@
package android.app.backup;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -76,13 +77,19 @@ public class BackupDataOutput {
/**
* Mark the beginning of one record in the backup data stream. This must be called before
* {@link #writeEntityData}.
- * @param key A string key that uniquely identifies the data record within the application
+ * @param key A string key that uniquely identifies the data record within the application.
+ * Keys whose first character is \uFF00 or higher are not valid.
* @param dataSize The size in bytes of this record's data. Passing a dataSize
* of -1 indicates that the record under this key should be deleted.
* @return The number of bytes written to the backup stream
* @throws IOException if the write failed
*/
public int writeEntityHeader(String key, int dataSize) throws IOException {
+ if (key != null && key.charAt(0) >= 0xff00) {
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ throw new IllegalArgumentException("Invalid key " + key);
+ }
+ }
int result = writeEntityHeader_native(mBackupWriter, key, dataSize);
if (result >= 0) {
return result;
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 12ee3b624f63f..c629a2edc5659 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -167,7 +167,7 @@ interface IBackupManager {
* are to be backed up. The allApps parameter supersedes this.
*/
void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
- boolean includeShared, boolean allApps, boolean allIncludesSystem,
+ boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
in String[] packageNames);
/**
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 8a89cbc236c6a..dd3a871a2def6 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -213,6 +213,12 @@ public class AppWidgetManager {
*/
public static final String EXTRA_CUSTOM_INFO = "customInfo";
+ /**
+ * An intent extra attached to the {@link #ACTION_APPWIDGET_HOST_RESTORED} broadcast,
+ * indicating the integer ID of the host whose widgets have just been restored.
+ */
+ public static final String EXTRA_HOST_ID = "hostId";
+
/**
* An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of
* {@link android.os.Bundle} objects to mix in to the list of AppWidgets that are
@@ -309,6 +315,86 @@ public class AppWidgetManager {
*/
public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
+ /**
+ * Sent to providers after AppWidget state related to the provider has been restored from
+ * backup. The intent contains information about how to translate AppWidget ids from the
+ * restored data to their new equivalents.
+ *
+ *
The intent will contain the following extras:
+ *
+ *
+ *
+ * | {@link #EXTRA_APPWIDGET_OLD_IDS} |
+ * The set of appWidgetIds represented in a restored backup that have been successfully
+ * incorporated into the current environment. This may be all of the AppWidgets known
+ * to this application, or just a subset. Each entry in this array of appWidgetIds has
+ * a corresponding entry in the {@link #EXTRA_APPWIDGET_IDS} extra. |
+ *
+ *
+ * | {@link #EXTRA_APPWIDGET_IDS} |
+ * The set of appWidgetIds now valid for this application. The app should look at
+ * its restored widget configuration and translate each appWidgetId in the
+ * {@link #EXTRA_APPWIDGET_OLD_IDS} array to its new value found at the corresponding
+ * index within this array. |
+ *
+ *
+ *
+ * This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see {@link #ACTION_APPWIDGET_HOST_RESTORED} for the corresponding host broadcast
+ */
+ public static final String ACTION_APPWIDGET_RESTORED
+ = "android.appwidget.action.APPWIDGET_RESTORED";
+
+ /**
+ * Sent to widget hosts after AppWidget state related to the host has been restored from
+ * backup. The intent contains information about how to translate AppWidget ids from the
+ * restored data to their new equivalents. If an application maintains multiple separate
+ * widget hosts instances, it will receive this broadcast separately for each one.
+ *
+ *
The intent will contain the following extras:
+ *
+ *
+ *
+ * | {@link #EXTRA_APPWIDGET_OLD_IDS} |
+ * The set of appWidgetIds represented in a restored backup that have been successfully
+ * incorporated into the current environment. This may be all of the AppWidgets known
+ * to this application, or just a subset. Each entry in this array of appWidgetIds has
+ * a corresponding entry in the {@link #EXTRA_APPWIDGET_IDS} extra. |
+ *
+ *
+ * | {@link #EXTRA_APPWIDGET_IDS} |
+ * The set of appWidgetIds now valid for this application. The app should look at
+ * its restored widget configuration and translate each appWidgetId in the
+ * {@link #EXTRA_APPWIDGET_OLD_IDS} array to its new value found at the corresponding
+ * index within this array. |
+ *
+ *
+ * | {@link #EXTRA_HOST_ID} |
+ * The integer ID of the widget host instance whose state has just been restored. |
+ *
+ *
+ *
+ * This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see {@link #ACTION_APPWIDGET_RESTORED} for the corresponding provider broadcast
+ */
+ public static final String ACTION_APPWIDGET_HOST_RESTORED
+ = "android.appwidget.action.APPWIDGET_HOST_RESTORED";
+
+ /**
+ * An intent extra that contains multiple appWidgetIds. These are id values as
+ * they were provided to the application during a recent restore from backup. It is
+ * attached to the {@link #ACTION_APPWIDGET_RESTORED} broadcast intent.
+ *
+ *
+ * The value will be an int array that can be retrieved like this:
+ * {@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/src/com/android/tests/appwidgethost/TestAppWidgetProvider.java getExtra_EXTRA_APPWIDGET_IDS}
+ */
+ public static final String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
+
/**
* Field for the manifest meta-data tag.
*
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index edf142b2726ac..ab91edfeca244 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -66,15 +66,13 @@ public class AppWidgetProvider extends BroadcastReceiver {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
- }
- else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
+ } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
this.onDeleted(context, new int[] { appWidgetId });
}
- }
- else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
+ } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
@@ -83,19 +81,28 @@ public class AppWidgetProvider extends BroadcastReceiver {
this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
appWidgetId, widgetExtras);
}
- }
- else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
+ } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
- }
- else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
+ } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
+ } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
+ int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
+ if (oldIds != null && oldIds.length > 0) {
+ this.onRestored(context, oldIds, newIds);
+ this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
+ }
+ }
}
}
// END_INCLUDE(onReceive)
/**
- * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcast when
- * this AppWidget provider is being asked to provide {@link android.widget.RemoteViews RemoteViews}
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} and
+ * {@link AppWidgetManager#ACTION_APPWIDGET_RESTORED} broadcasts when this AppWidget
+ * provider is being asked to provide {@link android.widget.RemoteViews RemoteViews}
* for a set of AppWidgets. Override this method to implement your own AppWidget functionality.
*
* {@more}
@@ -123,8 +130,8 @@ public class AppWidgetProvider extends BroadcastReceiver {
* running.
* @param appWidgetManager A {@link AppWidgetManager} object you can call {@link
* AppWidgetManager#updateAppWidget} on.
- * @param appWidgetId The appWidgetId of the widget who's size changed.
- * @param newOptions The appWidgetId of the widget who's size changed.
+ * @param appWidgetId The appWidgetId of the widget whose size changed.
+ * @param newOptions The appWidgetId of the widget whose size changed.
*
* @see AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED
*/
@@ -181,4 +188,24 @@ public class AppWidgetProvider extends BroadcastReceiver {
*/
public void onDisabled(Context context) {
}
+
+ /**
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_RESTORED} broadcast
+ * when instances of this AppWidget provider have been restored from backup. If your
+ * provider maintains any persistent data about its widget instances, override this method
+ * to remap the old AppWidgetIds to the new values and update any other app state that may
+ * be relevant.
+ *
+ *
This callback will be followed immediately by a call to {@link #onUpdate} so your
+ * provider can immediately generate new RemoteViews suitable for its newly-restored set
+ * of instances.
+ *
+ * {@more}
+ *
+ * @param context
+ * @param oldWidgetIds
+ * @param newWidgetIds
+ */
+ public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
+ }
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 7ddd5d2cff655..5214dd904af88 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -59,6 +59,5 @@ interface IAppWidgetService {
void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection, int userId);
void unbindRemoteViewsService(int appWidgetId, in Intent intent, int userId);
int[] getAppWidgetIds(in ComponentName provider, int userId);
-
}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index a604d844b4d59..1bfad05a2d592 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -34,6 +34,8 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
import libcore.io.ErrnoException;
import libcore.io.Libcore;
@@ -55,20 +57,24 @@ public class LocalTransport extends IBackupTransport.Stub {
private static final String TRANSPORT_DESTINATION_STRING
= "Backing up to debug-only private cache";
- // The single hardcoded restore set always has the same (nonzero!) token
- private static final long RESTORE_TOKEN = 1;
+ // The currently-active restore set always has the same (nonzero!) token
+ private static final long CURRENT_SET_TOKEN = 1;
private Context mContext;
private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
+ private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN));
+
private PackageInfo[] mRestorePackages = null;
private int mRestorePackage = -1; // Index into mRestorePackages
+ private File mRestoreDataDir;
+ private long mRestoreToken;
public LocalTransport(Context context) {
mContext = context;
- mDataDir.mkdirs();
- if (!SELinux.restorecon(mDataDir)) {
- Log.e(TAG, "SELinux restorecon failed for " + mDataDir);
+ mCurrentSetDir.mkdirs();
+ if (!SELinux.restorecon(mCurrentSetDir)) {
+ Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir);
}
}
@@ -96,7 +102,7 @@ public class LocalTransport extends IBackupTransport.Stub {
public int initializeDevice() {
if (DEBUG) Log.v(TAG, "wiping all data");
- deleteContents(mDataDir);
+ deleteContents(mCurrentSetDir);
return BackupConstants.TRANSPORT_OK;
}
@@ -112,7 +118,7 @@ public class LocalTransport extends IBackupTransport.Stub {
}
}
- File packageDir = new File(mDataDir, packageInfo.packageName);
+ File packageDir = new File(mCurrentSetDir, packageInfo.packageName);
packageDir.mkdirs();
// Each 'record' in the restore set is kept in its own file, named by
@@ -193,7 +199,7 @@ public class LocalTransport extends IBackupTransport.Stub {
public int clearBackupData(PackageInfo packageInfo) {
if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
- File packageDir = new File(mDataDir, packageInfo.packageName);
+ File packageDir = new File(mCurrentSetDir, packageInfo.packageName);
final File[] fileset = packageDir.listFiles();
if (fileset != null) {
for (File f : fileset) {
@@ -210,22 +216,38 @@ public class LocalTransport extends IBackupTransport.Stub {
}
// Restore handling
+ static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 };
public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
- // one hardcoded restore set
- RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
- RestoreSet[] array = { set };
- return array;
+ long[] existing = new long[POSSIBLE_SETS.length + 1];
+ int num = 0;
+
+ // see which possible non-current sets exist, then put the current set at the end
+ for (long token : POSSIBLE_SETS) {
+ if ((new File(mDataDir, Long.toString(token))).exists()) {
+ existing[num++] = token;
+ }
+ }
+ // and always the currently-active set last
+ existing[num++] = CURRENT_SET_TOKEN;
+
+ RestoreSet[] available = new RestoreSet[num];
+ for (int i = 0; i < available.length; i++) {
+ available[i] = new RestoreSet("Local disk image", "flash", existing[i]);
+ }
+ return available;
}
public long getCurrentRestoreSet() {
- // The hardcoded restore set always has the same token
- return RESTORE_TOKEN;
+ // The current restore set always has the same token
+ return CURRENT_SET_TOKEN;
}
public int startRestore(long token, PackageInfo[] packages) {
if (DEBUG) Log.v(TAG, "start restore " + token);
mRestorePackages = packages;
mRestorePackage = -1;
+ mRestoreToken = token;
+ mRestoreDataDir = new File(mDataDir, Long.toString(token));
return BackupConstants.TRANSPORT_OK;
}
@@ -234,7 +256,7 @@ public class LocalTransport extends IBackupTransport.Stub {
while (++mRestorePackage < mRestorePackages.length) {
String name = mRestorePackages[mRestorePackage].packageName;
// skip packages where we have a data dir but no actual contents
- String[] contents = (new File(mDataDir, name)).list();
+ String[] contents = (new File(mRestoreDataDir, name)).list();
if (contents != null && contents.length > 0) {
if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
return name;
@@ -248,29 +270,32 @@ public class LocalTransport extends IBackupTransport.Stub {
public int 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);
+ File packageDir = new File(mRestoreDataDir, mRestorePackages[mRestorePackage].packageName);
// The restore set is the concatenation of the individual record blobs,
- // each of which is a file in the package's directory
- File[] blobs = packageDir.listFiles();
+ // each of which is a file in the package's directory. We return the
+ // data in lexical order sorted by key, so that apps which use synthetic
+ // keys like BLOB_1, BLOB_2, etc will see the date in the most obvious
+ // order.
+ ArrayList blobs = contentsByKey(packageDir);
if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error
- Log.e(TAG, "Error listing directory: " + packageDir);
+ Log.e(TAG, "No keys for package: " + packageDir);
return BackupConstants.TRANSPORT_ERROR;
}
// We expect at least some data if the directory exists in the first place
- if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
+ if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.size() + " key files");
BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
try {
- for (File f : blobs) {
+ for (DecodedFilename keyEntry : blobs) {
+ File f = keyEntry.file;
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);
+ if (DEBUG) Log.v(TAG, " ... key=" + keyEntry.key + " size=" + size);
+ out.writeEntityHeader(keyEntry.key, size);
out.writeEntityData(buf, size);
} finally {
in.close();
@@ -283,6 +308,39 @@ public class LocalTransport extends IBackupTransport.Stub {
}
}
+ static class DecodedFilename implements Comparable {
+ public File file;
+ public String key;
+
+ public DecodedFilename(File f) {
+ file = f;
+ key = new String(Base64.decode(f.getName()));
+ }
+
+ @Override
+ public int compareTo(DecodedFilename other) {
+ // sorts into ascending lexical order by decoded key
+ return key.compareTo(other.key);
+ }
+ }
+
+ // Return a list of the files in the given directory, sorted lexically by
+ // the Base64-decoded file name, not by the on-disk filename
+ private ArrayList contentsByKey(File dir) {
+ File[] allFiles = dir.listFiles();
+ if (allFiles == null || allFiles.length == 0) {
+ return null;
+ }
+
+ // Decode the filenames into keys then sort lexically by key
+ ArrayList contents = new ArrayList();
+ for (File f : allFiles) {
+ contents.add(new DecodedFilename(f));
+ }
+ Collections.sort(contents);
+ return contents;
+ }
+
public void finishRestore() {
if (DEBUG) Log.v(TAG, "finishRestore()");
}
diff --git a/core/java/com/android/server/AppWidgetBackupBridge.java b/core/java/com/android/server/AppWidgetBackupBridge.java
new file mode 100644
index 0000000000000..2ea2f794dac96
--- /dev/null
+++ b/core/java/com/android/server/AppWidgetBackupBridge.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.util.List;
+
+/**
+ * Runtime bridge between the Backup Manager Service and the App Widget Service,
+ * since those two modules are intentionally decoupled for modularity.
+ *
+ * @hide
+ */
+public class AppWidgetBackupBridge {
+ private static WidgetBackupProvider sAppWidgetService;
+
+ public static void register(WidgetBackupProvider instance) {
+ sAppWidgetService = instance;
+ }
+
+ public static List getWidgetParticipants(int userId) {
+ return (sAppWidgetService != null)
+ ? sAppWidgetService.getWidgetParticipants(userId)
+ : null;
+ }
+
+ public static byte[] getWidgetState(String packageName, int userId) {
+ return (sAppWidgetService != null)
+ ? sAppWidgetService.getWidgetState(packageName, userId)
+ : null;
+ }
+
+ public static void restoreStarting(int userId) {
+ if (sAppWidgetService != null) {
+ sAppWidgetService.restoreStarting(userId);
+ }
+ }
+
+ public static void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
+ if (sAppWidgetService != null) {
+ sAppWidgetService.restoreWidgetState(packageName, restoredState, userId);
+ }
+ }
+
+ public static void restoreFinished(int userId) {
+ if (sAppWidgetService != null) {
+ sAppWidgetService.restoreFinished(userId);
+ }
+ }
+}
diff --git a/core/java/com/android/server/WidgetBackupProvider.java b/core/java/com/android/server/WidgetBackupProvider.java
new file mode 100644
index 0000000000000..a2efbdd6f2f86
--- /dev/null
+++ b/core/java/com/android/server/WidgetBackupProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.util.List;
+
+/**
+ * Shim to allow core/backup to communicate with the app widget service
+ * about various important events without needing to be able to see the
+ * implementation of the service.
+ *
+ * @hide
+ */
+public interface WidgetBackupProvider {
+ public List getWidgetParticipants(int userId);
+ public byte[] getWidgetState(String packageName, int userId);
+ public void restoreStarting(int userId);
+ public void restoreWidgetState(String packageName, byte[] restoredState, int userId);
+ public void restoreFinished(int userId);
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 37ef539b5b838..4e1efc61d055c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -83,6 +83,8 @@
+
+
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
index e20867762daa8..77d50769915bc 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
@@ -23,7 +23,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -38,18 +37,19 @@ import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.AppWidgetBackupBridge;
+import com.android.server.WidgetBackupProvider;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
-import java.util.Locale;
/**
* SystemService that publishes an IAppWidgetService.
*/
-public class AppWidgetService extends SystemService {
+public class AppWidgetService extends SystemService implements WidgetBackupProvider {
static final String TAG = "AppWidgetService";
@@ -72,6 +72,7 @@ public class AppWidgetService extends SystemService {
@Override
public void onStart() {
publishBinderService(Context.APPWIDGET_SERVICE, mServiceImpl);
+ AppWidgetBackupBridge.register(this);
}
@Override
@@ -81,13 +82,40 @@ public class AppWidgetService extends SystemService {
}
}
+
+ // backup <-> app widget service bridge surface
+ @Override
+ public List getWidgetParticipants(int userId) {
+ return mServiceImpl.getWidgetParticipants(userId);
+ }
+
+ @Override
+ public byte[] getWidgetState(String packageName, int userId) {
+ return mServiceImpl.getWidgetState(packageName, userId);
+ }
+
+ @Override
+ public void restoreStarting(int userId) {
+ mServiceImpl.restoreStarting(userId);
+ }
+
+ @Override
+ public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
+ mServiceImpl.restoreWidgetState(packageName, restoredState, userId);
+ }
+
+ @Override
+ public void restoreFinished(int userId) {
+ mServiceImpl.restoreFinished(userId);
+ }
+
+
+ // implementation entry point and binder service
private final AppWidgetServiceStub mServiceImpl = new AppWidgetServiceStub();
- private class AppWidgetServiceStub extends IAppWidgetService.Stub {
+ class AppWidgetServiceStub extends IAppWidgetService.Stub {
private boolean mSafeMode;
- private Locale mLocale;
- private PackageManager mPackageManager;
public void systemRunning(boolean safeMode) {
mSafeMode = safeMode;
@@ -227,6 +255,29 @@ public class AppWidgetService extends SystemService {
}
}
+
+ // support of the widget/backup bridge
+ public List getWidgetParticipants(int userId) {
+ return getImplForUser(userId).getWidgetParticipants();
+ }
+
+ public byte[] getWidgetState(String packageName, int userId) {
+ return getImplForUser(userId).getWidgetState(packageName);
+ }
+
+ public void restoreStarting(int userId) {
+ getImplForUser(userId).restoreStarting();
+ }
+
+ public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
+ getImplForUser(userId).restoreWidgetState(packageName, restoredState);
+ }
+
+ public void restoreFinished(int userId) {
+ getImplForUser(userId).restoreFinished();
+ }
+
+
private void checkPermission(int userId) {
int realUserId = ActivityManager.handleIncomingUser(
Binder.getCallingPid(),
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index b6391b64415d0..b84df79e17e9d 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -31,6 +31,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
@@ -51,6 +52,7 @@ import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
import android.view.Display;
@@ -66,6 +68,8 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -79,8 +83,11 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Map.Entry;
import java.util.Set;
+import libcore.util.MutableInt;
+
class AppWidgetServiceImpl {
private static final String KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
@@ -89,8 +96,10 @@ class AppWidgetServiceImpl {
private static final String SETTINGS_FILENAME = "appwidgets.xml";
private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes
private static final int CURRENT_VERSION = 1; // Bump if the stored widgets need to be upgraded.
+ private static final int WIDGET_STATE_VERSION = 1; // version of backed-up widget state
- private static boolean DBG = false;
+ private static boolean DBG = true;
+ private static boolean DEBUG_BACKUP = DBG || true;
/*
* When identifying a Host or Provider based on the calling process, use the uid field. When
@@ -105,6 +114,25 @@ class AppWidgetServiceImpl {
boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
int tag; // for use while saving state (the index)
+
+ // is there an instance of this provider hosted by the given app?
+ public boolean isHostedBy(String packageName) {
+ final int N = instances.size();
+ for (int i = 0; i < N; i++) {
+ AppWidgetId id = instances.get(i);
+ if (packageName.equals(id.host.packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Provider{" + ((info == null) ? "null" : info.provider)
+ + (zombie ? " Z" : "")
+ + '}';
+ }
}
static class Host {
@@ -125,14 +153,62 @@ class AppWidgetServiceImpl {
return this.uid == callingUid;
}
}
+
+ boolean hostsPackage(String pkg) {
+ final int N = instances.size();
+ for (int i = 0; i < N; i++) {
+ Provider p = instances.get(i).provider;
+ if (p.info != null && pkg.equals(p.info.provider.getPackageName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Host{" + packageName + ":" + hostId + '}';
+ }
}
static class AppWidgetId {
int appWidgetId;
+ int restoredId; // tracking & remapping any restored state
Provider provider;
RemoteViews views;
Bundle options;
Host host;
+
+ @Override
+ public String toString() {
+ return "AppWidgetId{" + appWidgetId + ':' + host + ':' + provider + '}';
+ }
+ }
+
+ AppWidgetId findRestoredWidgetLocked(int restoredId, Host host, Provider p) {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "Find restored widget: id=" + restoredId
+ + " host=" + host + " provider=" + p);
+ }
+
+ if (p == null || host == null) {
+ return null;
+ }
+
+ final int N = mAppWidgetIds.size();
+ for (int i = 0; i < N; i++) {
+ AppWidgetId widget = mAppWidgetIds.get(i);
+ if (widget.restoredId == restoredId
+ && widget.host.hostId == host.hostId
+ && widget.host.packageName.equals(host.packageName)
+ && widget.provider.info.provider.equals(p.info.provider)) {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, " Found at " + i + " : " + widget);
+ }
+ return widget;
+ }
+ }
+ return null;
}
/**
@@ -194,6 +270,22 @@ class AppWidgetServiceImpl {
boolean mStateLoaded;
int mMaxWidgetBitmapMemory;
+ // Map old (restored) widget IDs to new AppWidgetId instances. This object is used
+ // as the lock around manipulation of the overall restored-widget state, just as
+ // mAppWidgetIds is used as the lock object around all "live" widget state
+ // manipulations. Methods that must be called with this lock held are decorated
+ // with the suffix "Lr".
+ //
+ // In cases when both of those locks must be held concurrently, mRestoredWidgetIds
+ // must be acquired *first.*
+ private final SparseArray mRestoredWidgetIds = new SparseArray();
+
+ // We need to make sure to wipe the pre-restore widget state only once for
+ // a given package. Keep track of what we've done so far here; the list is
+ // cleared at the start of every system restore pass, but preserved through
+ // any install-time restore operations.
+ HashSet mPrunedApps = new HashSet();
+
private final Handler mSaveStateHandler;
// These are for debugging only -- widgets are going missing in some rare instances
@@ -300,10 +392,19 @@ class AppWidgetServiceImpl {
providersModified |= updateProvidersForPackageLocked(pkgName, null);
}
} else {
- // The package was just added
+ // The package was just added. Fix up the providers...
for (String pkgName : pkgList) {
providersModified |= addProvidersForPackageLocked(pkgName);
}
+ // ...and see if these are hosts we've been awaiting
+ for (String pkg : pkgList) {
+ try {
+ int uid = getUidForPackage(pkg);
+ resolveHostUidLocked(pkg, uid);
+ } catch (NameNotFoundException e) {
+ // shouldn't happen; we just installed it!
+ }
+ }
}
saveStateAsync();
}
@@ -331,6 +432,19 @@ class AppWidgetServiceImpl {
}
}
+ void resolveHostUidLocked(String pkg, int uid) {
+ final int N = mHosts.size();
+ for (int i = 0; i < N; i++) {
+ Host h = mHosts.get(i);
+ if (h.uid == -1 && pkg.equals(h.packageName)) {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "host " + pkg + ":" + h.hostId + " resolved to uid " + uid);
+ }
+ h.uid = uid;
+ }
+ }
+ }
+
private void dumpProvider(Provider p, int index, PrintWriter pw) {
AppWidgetProviderInfo info = p.info;
pw.print(" ["); pw.print(index); pw.print("] provider ");
@@ -433,7 +547,7 @@ class AppWidgetServiceImpl {
if (!mHasFeature) {
return;
}
- loadAppWidgetListLocked();
+ loadWidgetProviderListLocked();
loadStateLocked();
mStateLoaded = true;
}
@@ -516,6 +630,7 @@ class AppWidgetServiceImpl {
}
void deleteHostLocked(Host host) {
+ if (DBG) log("Deleting host " + host);
final int N = host.instances.size();
for (int i = N - 1; i >= 0; i--) {
AppWidgetId id = host.instances.get(i);
@@ -719,7 +834,7 @@ class AppWidgetServiceImpl {
}
final ComponentName componentName = intent.getComponent();
try {
- final ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(componentName,
+ final ServiceInfo si = mPm.getServiceInfo(componentName,
PackageManager.GET_PERMISSIONS, mUserId);
if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) {
throw new SecurityException("Selected service does not require "
@@ -981,7 +1096,6 @@ class AppWidgetServiceImpl {
if (!mHasFeature) {
return;
}
- options = cloneIfLocalBinder(options);
ensureStateLoadedLocked();
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
@@ -991,7 +1105,7 @@ class AppWidgetServiceImpl {
Provider p = id.provider;
// Merge the options
- id.options.putAll(options);
+ id.options.putAll(cloneIfLocalBinder(options));
// send the broacast saying that this appWidgetId has been deleted
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED);
@@ -1278,9 +1392,13 @@ class AppWidgetServiceImpl {
}
Provider lookupProviderLocked(ComponentName provider) {
- final int N = mInstalledProviders.size();
+ return lookupProviderLocked(provider, mInstalledProviders);
+ }
+
+ Provider lookupProviderLocked(ComponentName provider, ArrayList installedProviders) {
+ final int N = installedProviders.size();
for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
+ Provider p = installedProviders.get(i);
if (p.info.provider.equals(provider)) {
return p;
}
@@ -1317,11 +1435,12 @@ class AppWidgetServiceImpl {
void pruneHostLocked(Host host) {
if (host.instances.size() == 0 && host.callbacks == null) {
+ if (DBG) log("Pruning host " + host);
mHosts.remove(host);
}
}
- void loadAppWidgetListLocked() {
+ void loadWidgetProviderListLocked() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
try {
List broadcastReceivers = mPm.queryIntentReceivers(intent,
@@ -1348,7 +1467,23 @@ class AppWidgetServiceImpl {
Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName,
ri.activityInfo.name), ri);
if (p != null) {
- mInstalledProviders.add(p);
+ // we might have an inactive entry for this provider already due to
+ // a preceding restore operation. if so, fix it up in place; otherwise
+ // just add this new one.
+ Provider existing = lookupProviderLocked(p.info.provider);
+ if (existing != null) {
+ if (existing.zombie && !mSafeMode) {
+ // it's a placeholder that was set up during an app restore
+ existing.zombie = false;
+ existing.info = p.info; // the real one filled out from the ResolveInfo
+ existing.uid = p.uid;
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "Provider placeholder now reified: " + existing);
+ }
+ }
+ } else {
+ mInstalledProviders.add(p);
+ }
return true;
} else {
return false;
@@ -1463,6 +1598,554 @@ class AppWidgetServiceImpl {
}
}
+ public List getWidgetParticipants() {
+ HashSet packages = new HashSet();
+ synchronized (mAppWidgetIds) {
+ final int N = mAppWidgetIds.size();
+ for (int i = 0; i < N; i++) {
+ final AppWidgetId id = mAppWidgetIds.get(i);
+ packages.add(id.host.packageName);
+ packages.add(id.provider.info.provider.getPackageName());
+ }
+ }
+ return new ArrayList(packages);
+ }
+
+ private void serializeProvider(XmlSerializer out, Provider p) throws IOException {
+ out.startTag(null, "p");
+ out.attribute(null, "pkg", p.info.provider.getPackageName());
+ out.attribute(null, "cl", p.info.provider.getClassName());
+ out.endTag(null, "p");
+ }
+
+ private void serializeHost(XmlSerializer out, Host host) throws IOException {
+ out.startTag(null, "h");
+ out.attribute(null, "pkg", host.packageName);
+ out.attribute(null, "id", Integer.toHexString(host.hostId));
+ out.endTag(null, "h");
+ }
+
+ private void serializeAppWidgetId(XmlSerializer out, AppWidgetId id) throws IOException {
+ out.startTag(null, "g");
+ out.attribute(null, "id", Integer.toHexString(id.appWidgetId));
+ out.attribute(null, "rid", Integer.toHexString(id.restoredId));
+ out.attribute(null, "h", Integer.toHexString(id.host.tag));
+ if (id.provider != null) {
+ out.attribute(null, "p", Integer.toHexString(id.provider.tag));
+ }
+ if (id.options != null) {
+ out.attribute(null, "min_width", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
+ out.attribute(null, "min_height", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
+ out.attribute(null, "max_width", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
+ out.attribute(null, "max_height", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
+ out.attribute(null, "host_category", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
+ }
+ out.endTag(null, "g");
+ }
+
+ private Bundle parseWidgetIdOptions(XmlPullParser parser) {
+ Bundle options = new Bundle();
+ String minWidthString = parser.getAttributeValue(null, "min_width");
+ if (minWidthString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+ Integer.parseInt(minWidthString, 16));
+ }
+ String minHeightString = parser.getAttributeValue(null, "min_height");
+ if (minHeightString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+ Integer.parseInt(minHeightString, 16));
+ }
+ String maxWidthString = parser.getAttributeValue(null, "max_width");
+ if (maxWidthString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+ Integer.parseInt(maxWidthString, 16));
+ }
+ String maxHeightString = parser.getAttributeValue(null, "max_height");
+ if (maxHeightString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+ Integer.parseInt(maxHeightString, 16));
+ }
+ String categoryString = parser.getAttributeValue(null, "host_category");
+ if (categoryString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ Integer.parseInt(categoryString, 16));
+ }
+ return options;
+ }
+
+ // Does this package either host or provide any active widgets?
+ private boolean packageNeedsWidgetBackupLocked(String packageName) {
+ int N = mAppWidgetIds.size();
+ for (int i = 0; i < N; i++) {
+ AppWidgetId id = mAppWidgetIds.get(i);
+ if (packageName.equals(id.host.packageName)) {
+ // this package is hosting widgets, so it knows widget IDs
+ return true;
+ }
+ Provider p = id.provider;
+ if (p != null && packageName.equals(p.info.provider.getPackageName())) {
+ // someone is hosting this app's widgets, so it knows widget IDs
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // build the widget-state blob that we save for the app during backup.
+ public byte[] getWidgetState(String backupTarget) {
+ if (!mHasFeature) {
+ return null;
+ }
+
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ synchronized (mAppWidgetIds) {
+ // Preflight: if this app neither hosts nor provides any live widgets
+ // we have no work to do.
+ if (!packageNeedsWidgetBackupLocked(backupTarget)) {
+ return null;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, "ws"); // widget state
+ out.attribute(null, "version", String.valueOf(WIDGET_STATE_VERSION));
+ out.attribute(null, "pkg", backupTarget);
+
+ // Remember all the providers that are currently hosted or published
+ // by this package: that is, all of the entities related to this app
+ // which will need to be told about id remapping.
+ int N = mInstalledProviders.size();
+ int index = 0;
+ for (int i = 0; i < N; i++) {
+ Provider p = mInstalledProviders.get(i);
+ if (p.instances.size() > 0) {
+ if (backupTarget.equals(p.info.provider.getPackageName())
+ || p.isHostedBy(backupTarget)) {
+ serializeProvider(out, p);
+ p.tag = index++;
+ }
+ }
+ }
+
+ N = mHosts.size();
+ index = 0;
+ for (int i = 0; i < N; i++) {
+ Host host = mHosts.get(i);
+ if (backupTarget.equals(host.packageName)
+ || host.hostsPackage(backupTarget)) {
+ serializeHost(out, host);
+ host.tag = index++;
+ }
+ }
+
+ // All widget instances involving this package,
+ // either as host or as provider
+ N = mAppWidgetIds.size();
+ for (int i = 0; i < N; i++) {
+ AppWidgetId id = mAppWidgetIds.get(i);
+ if (backupTarget.equals(id.host.packageName)
+ || backupTarget.equals(id.provider.info.provider.getPackageName())) {
+ serializeAppWidgetId(out, id);
+ }
+ }
+
+ out.endTag(null, "ws");
+ out.endDocument();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to save widget state for " + backupTarget);
+ return null;
+ }
+
+ }
+ return stream.toByteArray();
+ }
+
+ public void restoreStarting() {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "restore starting for user " + mUserId);
+ }
+ synchronized (mRestoredWidgetIds) {
+ // We're starting a new "system" restore operation, so any widget restore
+ // state that we see from here on is intended to replace the current
+ // widget configuration of any/all of the affected apps.
+ mPrunedApps.clear();
+ mUpdatesByProvider.clear();
+ mUpdatesByHost.clear();
+ }
+ }
+
+ // We're restoring widget state for 'pkg', so we start by wiping (a) all widget
+ // instances that are hosted by that app, and (b) all instances in other hosts
+ // for which 'pkg' is the provider. We assume that we'll be restoring all of
+ // these hosts & providers, so will be reconstructing a correct live state.
+ private void pruneWidgetStateLr(String pkg) {
+ if (!mPrunedApps.contains(pkg)) {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "pruning widget state for restoring package " + pkg);
+ }
+ for (int i = mAppWidgetIds.size() - 1; i >= 0; i--) {
+ AppWidgetId id = mAppWidgetIds.get(i);
+ Provider p = id.provider;
+ if (id.host.packageName.equals(pkg)
+ || p.info.provider.getPackageName().equals(pkg)) {
+ // 'pkg' is either the host or the provider for this instances,
+ // so we tear it down in anticipation of it (possibly) being
+ // reconstructed due to the restore
+ p.instances.remove(id);
+
+ unbindAppWidgetRemoteViewsServicesLocked(id);
+ mAppWidgetIds.remove(i);
+ }
+ }
+ mPrunedApps.add(pkg);
+ } else {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "already pruned " + pkg + ", continuing normally");
+ }
+ }
+ }
+
+ // Accumulate a list of updates that affect the given provider for a final
+ // coalesced notification broadcast once restore is over.
+ class RestoreUpdateRecord {
+ public int oldId;
+ public int newId;
+ public boolean notified;
+
+ public RestoreUpdateRecord(int theOldId, int theNewId) {
+ oldId = theOldId;
+ newId = theNewId;
+ notified = false;
+ }
+ }
+
+ HashMap> mUpdatesByProvider
+ = new HashMap>();
+ HashMap> mUpdatesByHost
+ = new HashMap>();
+
+ private boolean alreadyStashed(ArrayList stash,
+ final int oldId, final int newId) {
+ final int N = stash.size();
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = stash.get(i);
+ if (r.oldId == oldId && r.newId == newId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void stashProviderRestoreUpdateLr(Provider provider, int oldId, int newId) {
+ ArrayList r = mUpdatesByProvider.get(provider);
+ if (r == null) {
+ r = new ArrayList();
+ mUpdatesByProvider.put(provider, r);
+ } else {
+ // don't duplicate
+ if (alreadyStashed(r, oldId, newId)) {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "ID remap " + oldId + " -> " + newId
+ + " already stashed for " + provider);
+ }
+ return;
+ }
+ }
+ r.add(new RestoreUpdateRecord(oldId, newId));
+ }
+
+ private void stashHostRestoreUpdateLr(Host host, int oldId, int newId) {
+ ArrayList r = mUpdatesByHost.get(host);
+ if (r == null) {
+ r = new ArrayList();
+ mUpdatesByHost.put(host, r);
+ } else {
+ if (alreadyStashed(r, oldId, newId)) {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "ID remap " + oldId + " -> " + newId
+ + " already stashed for " + host);
+ }
+ return;
+ }
+ }
+ r.add(new RestoreUpdateRecord(oldId, newId));
+ }
+
+ public void restoreWidgetState(String packageName, byte[] restoredState) {
+ if (!mHasFeature) {
+ return;
+ }
+
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "Restoring widget state for " + packageName);
+ }
+
+ ByteArrayInputStream stream = new ByteArrayInputStream(restoredState);
+ try {
+ // Providers mentioned in the widget dataset by ordinal
+ ArrayList restoredProviders = new ArrayList();
+
+ // Hosts mentioned in the widget dataset by ordinal
+ ArrayList restoredHosts = new ArrayList();
+
+ //HashSet toNotify = new HashSet();
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+
+ synchronized (mAppWidgetIds) {
+ synchronized (mRestoredWidgetIds) {
+ int type;
+ do {
+ type = parser.next();
+ if (type == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if ("ws".equals(tag)) {
+ String v = parser.getAttributeValue(null, "version");
+ String pkg = parser.getAttributeValue(null, "pkg");
+
+ // TODO: fix up w.r.t. canonical vs current package names
+ if (!packageName.equals(pkg)) {
+ Slog.w(TAG, "Package mismatch in ws");
+ return;
+ }
+
+ int version = Integer.parseInt(v);
+ if (version > WIDGET_STATE_VERSION) {
+ Slog.w(TAG, "Unable to process state version " + version);
+ return;
+ }
+ } else if ("p".equals(tag)) {
+ String pkg = parser.getAttributeValue(null, "pkg");
+ String cl = parser.getAttributeValue(null, "cl");
+
+ // hostedProviders index will match 'p' attribute in widget's
+ // entry in the xml file being restored
+ // If there's no live entry for this provider, add an inactive one
+ // so that widget IDs referring to them can be properly allocated
+ final ComponentName cn = new ComponentName(pkg, cl);
+ Provider p = lookupProviderLocked(cn, mInstalledProviders);
+ if (p == null) {
+ p = new Provider();
+ p.info = new AppWidgetProviderInfo();
+ p.info.provider = cn;
+ p.zombie = true;
+ mInstalledProviders.add(p);
+ }
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, " provider " + cn);
+ }
+ restoredProviders.add(p);
+ } else if ("h".equals(tag)) {
+ // The host app may not yet exist on the device. If it's here we
+ // just use the existing Host entry, otherwise we create a
+ // placeholder whose uid will be fixed up at PACKAGE_ADDED time.
+ String pkg = parser.getAttributeValue(null, "pkg");
+ int uid;
+ try {
+ uid = getUidForPackage(pkg);
+ } catch (NameNotFoundException e) {
+ uid = -1;
+ }
+ int hostId = Integer.parseInt(
+ parser.getAttributeValue(null, "id"), 16);
+ Host h = lookupOrAddHostLocked(uid, pkg, hostId);
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, " host[" + restoredHosts.size()
+ + "]: {" + h.packageName + ":" + h.hostId + "}");
+ }
+ restoredHosts.add(h);
+ } else if ("g".equals(tag)) {
+ int restoredId = Integer.parseInt(
+ parser.getAttributeValue(null, "id"), 16);
+ int hostIndex = Integer.parseInt(
+ parser.getAttributeValue(null, "h"), 16);
+ Host host = restoredHosts.get(hostIndex);
+ Provider p = null;
+ String prov = parser.getAttributeValue(null, "p");
+ if (prov != null) {
+ // could have been null if the app had allocated an id
+ // but not yet established a binding under that id
+ int which = Integer.parseInt(prov, 16);
+ p = restoredProviders.get(which);
+ }
+
+ // We'll be restoring widget state for both the host and
+ // provider sides of this widget ID, so make sure we are
+ // beginning from a clean slate on both fronts.
+ pruneWidgetStateLr(host.packageName);
+ if (p != null) {
+ pruneWidgetStateLr(p.info.provider.getPackageName());
+ }
+
+ // Have we heard about this ancestral widget instance before?
+ AppWidgetId id = findRestoredWidgetLocked(restoredId, host, p);
+ if (id == null) {
+ id = new AppWidgetId();
+ id.appWidgetId = mNextAppWidgetId++;
+ id.restoredId = restoredId;
+ id.options = parseWidgetIdOptions(parser);
+ id.host = host;
+ id.host.instances.add(id);
+ id.provider = p;
+ if (id.provider != null) {
+ id.provider.instances.add(id);
+ }
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "New restored id " + restoredId
+ + " now " + id);
+ }
+ mAppWidgetIds.add(id);
+ }
+ if (id.provider.info != null) {
+ stashProviderRestoreUpdateLr(id.provider,
+ restoredId, id.appWidgetId);
+ } else {
+ Slog.w(TAG, "Missing provider for restored widget " + id);
+ }
+ stashHostRestoreUpdateLr(id.host, restoredId, id.appWidgetId);
+
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, " instance: " + restoredId
+ + " -> " + id.appWidgetId
+ + " :: p=" + id.provider);
+ }
+ }
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+
+ // We've updated our own bookkeeping. We'll need to notify the hosts and
+ // providers about the changes, but we can't do that yet because the restore
+ // target is not necessarily fully live at this moment. Set aside the
+ // information for now; the backup manager will call us once more at the
+ // end of the process when all of the targets are in a known state, and we
+ // will update at that point.
+ }
+ }
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Unable to restore widget state for " + packageName);
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to restore widget state for " + packageName);
+ } finally {
+ saveStateAsync();
+ }
+ }
+
+ // Called once following the conclusion of a restore operation. This is when we
+ // send out updates to apps involved in widget-state restore telling them about
+ // the new widget ID space.
+ public void restoreFinished() {
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "restoreFinished for " + mUserId);
+ }
+
+ final UserHandle userHandle = new UserHandle(mUserId);
+ synchronized (mRestoredWidgetIds) {
+ // Build the providers' broadcasts and send them off
+ Set>> providerEntries
+ = mUpdatesByProvider.entrySet();
+ for (Entry> e : providerEntries) {
+ // For each provider there's a list of affected IDs
+ Provider provider = e.getKey();
+ ArrayList updates = e.getValue();
+ final int pending = countPendingUpdates(updates);
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "Provider " + provider + " pending: " + pending);
+ }
+ if (pending > 0) {
+ int[] oldIds = new int[pending];
+ int[] newIds = new int[pending];
+ final int N = updates.size();
+ int nextPending = 0;
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ r.notified = true;
+ oldIds[nextPending] = r.oldId;
+ newIds[nextPending] = r.newId;
+ nextPending++;
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, " " + r.oldId + " => " + r.newId);
+ }
+ }
+ }
+ sendWidgetRestoreBroadcast(AppWidgetManager.ACTION_APPWIDGET_RESTORED,
+ provider, null, oldIds, newIds, userHandle);
+ }
+ }
+
+ // same thing per host
+ Set>> hostEntries
+ = mUpdatesByHost.entrySet();
+ for (Entry> e : hostEntries) {
+ Host host = e.getKey();
+ if (host.uid > 0) {
+ ArrayList updates = e.getValue();
+ final int pending = countPendingUpdates(updates);
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, "Host " + host + " pending: " + pending);
+ }
+ if (pending > 0) {
+ int[] oldIds = new int[pending];
+ int[] newIds = new int[pending];
+ final int N = updates.size();
+ int nextPending = 0;
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ r.notified = true;
+ oldIds[nextPending] = r.oldId;
+ newIds[nextPending] = r.newId;
+ nextPending++;
+ if (DEBUG_BACKUP) {
+ Slog.i(TAG, " " + r.oldId + " => " + r.newId);
+ }
+ }
+ }
+ sendWidgetRestoreBroadcast(AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED,
+ null, host, oldIds, newIds, userHandle);
+ }
+ }
+ }
+ }
+ }
+
+ private int countPendingUpdates(ArrayList updates) {
+ int pending = 0;
+ final int N = updates.size();
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ pending++;
+ }
+ }
+ return pending;
+ }
+
+ void sendWidgetRestoreBroadcast(String action, Provider provider, Host host,
+ int[] oldIds, int[] newIds, UserHandle userHandle) {
+ Intent intent = new Intent(action);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
+ if (provider != null) {
+ intent.setComponent(provider.info.provider);
+ mContext.sendBroadcastAsUser(intent, userHandle);
+ }
+ if (host != null) {
+ intent.setComponent(null);
+ intent.setPackage(host.packageName);
+ intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.hostId);
+ mContext.sendBroadcastAsUser(intent, userHandle);
+ }
+ }
+
private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) {
Provider p = null;
@@ -1660,10 +2343,7 @@ class AppWidgetServiceImpl {
for (int i = 0; i < N; i++) {
Provider p = mInstalledProviders.get(i);
if (p.instances.size() > 0) {
- out.startTag(null, "p");
- out.attribute(null, "pkg", p.info.provider.getPackageName());
- out.attribute(null, "cl", p.info.provider.getClassName());
- out.endTag(null, "p");
+ serializeProvider(out, p);
p.tag = providerIndex;
providerIndex++;
}
@@ -1672,35 +2352,14 @@ class AppWidgetServiceImpl {
N = mHosts.size();
for (int i = 0; i < N; i++) {
Host host = mHosts.get(i);
- out.startTag(null, "h");
- out.attribute(null, "pkg", host.packageName);
- out.attribute(null, "id", Integer.toHexString(host.hostId));
- out.endTag(null, "h");
+ serializeHost(out, host);
host.tag = i;
}
N = mAppWidgetIds.size();
for (int i = 0; i < N; i++) {
AppWidgetId id = mAppWidgetIds.get(i);
- out.startTag(null, "g");
- out.attribute(null, "id", Integer.toHexString(id.appWidgetId));
- out.attribute(null, "h", Integer.toHexString(id.host.tag));
- if (id.provider != null) {
- out.attribute(null, "p", Integer.toHexString(id.provider.tag));
- }
- if (id.options != null) {
- out.attribute(null, "min_width", Integer.toHexString(id.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
- out.attribute(null, "min_height", Integer.toHexString(id.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
- out.attribute(null, "max_width", Integer.toHexString(id.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
- out.attribute(null, "max_height", Integer.toHexString(id.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
- out.attribute(null, "host_category", Integer.toHexString(id.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
- }
- out.endTag(null, "g");
+ serializeAppWidgetId(out, id);
}
Iterator it = mPackagesWithBindWidgetPermission.iterator();
@@ -1801,6 +2460,11 @@ class AppWidgetServiceImpl {
mNextAppWidgetId = id.appWidgetId + 1;
}
+ // restored ID is allowed to be absent
+ String restoredIdString = parser.getAttributeValue(null, "rid");
+ id.restoredId = (restoredIdString == null) ? 0
+ : Integer.parseInt(restoredIdString, 16);
+
Bundle options = new Bundle();
String minWidthString = parser.getAttributeValue(null, "min_width");
if (minWidthString != null) {
@@ -1975,8 +2639,7 @@ class AppWidgetServiceImpl {
continue;
}
if (pkgName.equals(ai.packageName)) {
- addProviderLocked(ri);
- providersAdded = true;
+ providersAdded = addProviderLocked(ri);
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8eaefefe9f075..1a1512f8a0179 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -24,6 +24,7 @@ import android.app.IApplicationThread;
import android.app.IBackupAgent;
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.FullBackup;
import android.app.backup.RestoreSet;
@@ -82,12 +83,14 @@ import android.util.StringBuilderPrinter;
import com.android.internal.backup.BackupConstants;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.backup.IObbBackupService;
+import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.SystemService;
import com.android.server.backup.PackageManagerBackupAgent.Metadata;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -115,10 +118,13 @@ import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
+import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
@@ -136,22 +142,43 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+
public class BackupManagerService extends IBackupManager.Stub {
private static final String TAG = "BackupManagerService";
private static final boolean DEBUG = true;
private static final boolean MORE_DEBUG = false;
+ // System-private key used for backing up an app's widget state. Must
+ // begin with U+FFxx by convention (we reserve all keys starting
+ // with U+FF00 or higher for system use).
+ static final String KEY_WIDGET_STATE = "\uffed\uffedwidget";
+
// Historical and current algorithm names
static final String PBKDF_CURRENT = "PBKDF2WithHmacSHA1";
static final String PBKDF_FALLBACK = "PBKDF2WithHmacSHA1And8bit";
// Name and current contents version of the full-backup manifest file
+ //
+ // Manifest version history:
+ //
+ // 1 : initial release
static final String BACKUP_MANIFEST_FILENAME = "_manifest";
static final int BACKUP_MANIFEST_VERSION = 1;
+
+ // External archive format version history:
+ //
+ // 1 : initial release
+ // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
+ // 3 : introduced "_meta" metadata file; no other format change per se
+ static final int BACKUP_FILE_VERSION = 3;
static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
- static final int BACKUP_FILE_VERSION = 2;
static final int BACKUP_PW_FILE_VERSION = 2;
+ static final String BACKUP_METADATA_FILENAME = "_meta";
+ static final int BACKUP_METADATA_VERSION = 1;
+ static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
@@ -186,6 +213,7 @@ public class BackupManagerService extends IBackupManager.Stub {
private static final int MSG_RUN_FULL_RESTORE = 10;
private static final int MSG_RETRY_INIT = 11;
private static final int MSG_RETRY_CLEAR = 12;
+ private static final int MSG_WIDGET_BROADCAST = 13;
// backup task state machine tick
static final int MSG_BACKUP_RESTORE_STEP = 20;
@@ -337,42 +365,47 @@ public class BackupManagerService extends IBackupManager.Stub {
public long token;
public PackageInfo pkgInfo;
public int pmToken; // in post-install restore, the PM's token for this transaction
- public boolean needFullBackup;
+ public boolean isSystemRestore;
public String[] filterSet;
+ // Restore a single package
RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
- long _token, PackageInfo _pkg, int _pmToken, boolean _needFullBackup) {
+ long _token, PackageInfo _pkg, int _pmToken) {
transport = _transport;
dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = _pkg;
pmToken = _pmToken;
- needFullBackup = _needFullBackup;
+ isSystemRestore = false;
filterSet = null;
}
+ // Restore everything possible. This is the form that Setup Wizard or similar
+ // restore UXes use.
RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
- long _token, boolean _needFullBackup) {
+ long _token) {
transport = _transport;
dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = null;
pmToken = 0;
- needFullBackup = _needFullBackup;
+ isSystemRestore = true;
filterSet = null;
}
+ // Restore some set of packages. Leave this one up to the caller to specify
+ // whether it's to be considered a system-level restore.
RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
- long _token, String[] _filterSet, boolean _needFullBackup) {
+ long _token, String[] _filterSet, boolean _isSystemRestore) {
transport = _transport;
dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = null;
pmToken = 0;
- needFullBackup = _needFullBackup;
+ isSystemRestore = _isSystemRestore;
filterSet = _filterSet;
}
}
@@ -413,16 +446,19 @@ public class BackupManagerService extends IBackupManager.Stub {
public boolean includeApks;
public boolean includeObbs;
public boolean includeShared;
+ public boolean doWidgets;
public boolean allApps;
public boolean includeSystem;
public String[] packages;
FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
- boolean saveShared, boolean doAllApps, boolean doSystem, String[] pkgList) {
+ boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem,
+ String[] pkgList) {
fd = output;
includeApks = saveApks;
includeObbs = saveObbs;
includeShared = saveShared;
+ doWidgets = alsoWidgets;
allApps = doAllApps;
includeSystem = doSystem;
packages = pkgList;
@@ -618,7 +654,8 @@ public class BackupManagerService extends IBackupManager.Stub {
FullBackupParams params = (FullBackupParams)msg.obj;
PerformFullBackupTask task = new PerformFullBackupTask(params.fd,
params.observer, params.includeApks, params.includeObbs,
- params.includeShared, params.curPassword, params.encryptPassword,
+ params.includeShared, params.doWidgets,
+ params.curPassword, params.encryptPassword,
params.allApps, params.includeSystem, params.packages, params.latch);
(new Thread(task)).start();
break;
@@ -631,7 +668,7 @@ public class BackupManagerService extends IBackupManager.Stub {
PerformRestoreTask task = new PerformRestoreTask(
params.transport, params.dirName, params.observer,
params.token, params.pkgInfo, params.pmToken,
- params.needFullBackup, params.filterSet);
+ params.isSystemRestore, params.filterSet);
Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
sendMessage(restoreMsg);
break;
@@ -770,6 +807,13 @@ public class BackupManagerService extends IBackupManager.Stub {
}
break;
}
+
+ case MSG_WIDGET_BROADCAST:
+ {
+ final Intent intent = (Intent) msg.obj;
+ mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ break;
+ }
}
}
}
@@ -2360,10 +2404,40 @@ public class BackupManagerService extends IBackupManager.Stub {
@Override
public void operationComplete() {
- // Okay, the agent successfully reported back to us. Spin the data off to the
+ // Okay, the agent successfully reported back to us. The next thing we do is
+ // push the app widget state for the app, if any.
+ final String pkgName = mCurrentPackage.packageName;
+ final long filepos = mBackupDataName.length();
+ FileDescriptor fd = mBackupData.getFileDescriptor();
+ try {
+ BackupDataOutput out = new BackupDataOutput(fd);
+ byte[] widgetState = AppWidgetBackupBridge.getWidgetState(pkgName,
+ UserHandle.USER_OWNER);
+ if (widgetState != null) {
+ out.writeEntityHeader(KEY_WIDGET_STATE, widgetState.length);
+ out.writeEntityData(widgetState, widgetState.length);
+ } else {
+ // No widget state for this app, but push a 'delete' operation for it
+ // in case they're trying to play games with the payload.
+ out.writeEntityHeader(KEY_WIDGET_STATE, -1);
+ }
+ } catch (IOException e) {
+ // Hard disk error; recovery/failure policy TBD. For now roll back,
+ // but we may want to consider this a transport-level failure (i.e.
+ // we're in such a bad state that we can't contemplate doing backup
+ // operations any more during this pass).
+ Slog.w(TAG, "Unable to save widget state for " + pkgName);
+ try {
+ Libcore.os.ftruncate(fd, filepos);
+ } catch (ErrnoException ee) {
+ Slog.w(TAG, "Unable to roll back!");
+ }
+ }
+
+ // Spin the data off to the
// transport and proceed with the next stage.
if (MORE_DEBUG) Slog.v(TAG, "operationComplete(): sending data to transport for "
- + mCurrentPackage.packageName);
+ + pkgName);
mBackupHandler.removeMessages(MSG_TIMEOUT);
clearAgentState();
addBackupTrace("operation complete");
@@ -2402,17 +2476,14 @@ public class BackupManagerService extends IBackupManager.Stub {
if (mStatus == BackupConstants.TRANSPORT_OK) {
mBackupDataName.delete();
mNewStateName.renameTo(mSavedStateName);
- EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE,
- mCurrentPackage.packageName, size);
- logBackupComplete(mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
+ logBackupComplete(pkgName);
} else {
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
- mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
}
} catch (Exception e) {
- Slog.e(TAG, "Transport error backing up " + mCurrentPackage.packageName, e);
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE,
- mCurrentPackage.packageName);
+ Slog.e(TAG, "Transport error backing up " + pkgName, e);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
mStatus = BackupConstants.TRANSPORT_ERROR;
} finally {
try { if (backupData != null) backupData.close(); } catch (IOException e) {}
@@ -2631,18 +2702,21 @@ public class BackupManagerService extends IBackupManager.Stub {
boolean mIncludeApks;
boolean mIncludeObbs;
boolean mIncludeShared;
+ boolean mDoWidgets;
boolean mAllApps;
final boolean mIncludeSystem;
- String[] mPackages;
+ ArrayList mPackages;
String mCurrentPassword;
String mEncryptPassword;
AtomicBoolean mLatchObject;
File mFilesDir;
File mManifestFile;
+ File mMetadataFile;
class FullBackupRunner implements Runnable {
PackageInfo mPackage;
+ byte[] mWidgetData;
IBackupAgent mAgent;
ParcelFileDescriptor mPipe;
int mToken;
@@ -2650,8 +2724,10 @@ public class BackupManagerService extends IBackupManager.Stub {
boolean mWriteManifest;
FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe,
- int token, boolean sendApk, boolean writeManifest) throws IOException {
+ int token, boolean sendApk, boolean writeManifest, byte[] widgetData)
+ throws IOException {
mPackage = pack;
+ mWidgetData = widgetData;
mAgent = agent;
mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
mToken = token;
@@ -2666,12 +2742,24 @@ public class BackupManagerService extends IBackupManager.Stub {
mPipe.getFileDescriptor());
if (mWriteManifest) {
+ final boolean writeWidgetData = mWidgetData != null;
if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
- writeAppManifest(mPackage, mManifestFile, mSendApk);
+ writeAppManifest(mPackage, mManifestFile, mSendApk, writeWidgetData);
FullBackup.backupToTar(mPackage.packageName, null, null,
mFilesDir.getAbsolutePath(),
mManifestFile.getAbsolutePath(),
output);
+ mManifestFile.delete();
+
+ // We only need to write a metadata file if we have widget data to stash
+ if (writeWidgetData) {
+ writeMetadata(mPackage, mMetadataFile, mWidgetData);
+ FullBackup.backupToTar(mPackage.packageName, null, null,
+ mFilesDir.getAbsolutePath(),
+ mMetadataFile.getAbsolutePath(),
+ output);
+ mMetadataFile.delete();
+ }
}
if (mSendApk) {
@@ -2696,16 +2784,19 @@ public class BackupManagerService extends IBackupManager.Stub {
PerformFullBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
boolean includeApks, boolean includeObbs, boolean includeShared,
- String curPassword, String encryptPassword, boolean doAllApps,
+ boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
boolean doSystem, String[] packages, AtomicBoolean latch) {
mOutputFile = fd;
mObserver = observer;
mIncludeApks = includeApks;
mIncludeObbs = includeObbs;
mIncludeShared = includeShared;
+ mDoWidgets = doWidgets;
mAllApps = doAllApps;
mIncludeSystem = doSystem;
- mPackages = packages;
+ mPackages = (packages == null)
+ ? new ArrayList()
+ : new ArrayList(Arrays.asList(packages));
mCurrentPassword = curPassword;
// when backing up, if there is a current backup password, we require that
// the user use a nonempty encryption password as well. if one is supplied
@@ -2720,13 +2811,28 @@ public class BackupManagerService extends IBackupManager.Stub {
mFilesDir = new File("/data/system");
mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
+ mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
+ }
+
+ void addPackagesToSet(TreeMap set, List pkgNames) {
+ for (String pkgName : pkgNames) {
+ if (!set.containsKey(pkgName)) {
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(pkgName,
+ PackageManager.GET_SIGNATURES);
+ set.put(pkgName, info);
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
+ }
+ }
+ }
}
@Override
public void run() {
Slog.i(TAG, "--- Performing full-dataset backup ---");
- List packagesToBackup = new ArrayList();
+ TreeMap packagesToBackup = new TreeMap();
FullBackupObbConnection obbConnection = new FullBackupObbConnection();
obbConnection.establish(); // we'll want this later
@@ -2734,63 +2840,70 @@ public class BackupManagerService extends IBackupManager.Stub {
// doAllApps supersedes the package set if any
if (mAllApps) {
- packagesToBackup = mPackageManager.getInstalledPackages(
+ List allPackages = mPackageManager.getInstalledPackages(
PackageManager.GET_SIGNATURES);
- // Exclude system apps if we've been asked to do so
- if (mIncludeSystem == false) {
- for (int i = 0; i < packagesToBackup.size(); ) {
- PackageInfo pkg = packagesToBackup.get(i);
- if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- packagesToBackup.remove(i);
- } else {
- i++;
- }
+ for (int i = 0; i < allPackages.size(); i++) {
+ PackageInfo pkg = allPackages.get(i);
+ // Exclude system apps if we've been asked to do so
+ if (mIncludeSystem == true
+ || ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) {
+ packagesToBackup.put(pkg.packageName, pkg);
}
}
}
+ // If we're doing widget state as well, ensure that we have all the involved
+ // host & provider packages in the set
+ if (mDoWidgets) {
+ List pkgs =
+ AppWidgetBackupBridge.getWidgetParticipants(UserHandle.USER_OWNER);
+ if (pkgs != null) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Adding widget participants to backup set:");
+ StringBuilder sb = new StringBuilder(128);
+ sb.append(" ");
+ for (String s : pkgs) {
+ sb.append(' ');
+ sb.append(s);
+ }
+ Slog.i(TAG, sb.toString());
+ }
+ addPackagesToSet(packagesToBackup, pkgs);
+ }
+ }
+
// Now process the command line argument packages, if any. Note that explicitly-
// named system-partition packages will be included even if includeSystem was
// set to false.
if (mPackages != null) {
- for (String pkgName : mPackages) {
- try {
- packagesToBackup.add(mPackageManager.getPackageInfo(pkgName,
- PackageManager.GET_SIGNATURES));
- } catch (NameNotFoundException e) {
- Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
- }
- }
+ addPackagesToSet(packagesToBackup, mPackages);
}
- // Cull any packages that have indicated that backups are not permitted, as well
- // as any explicit mention of the 'special' shared-storage agent package (we
- // handle that one at the end).
- for (int i = 0; i < packagesToBackup.size(); ) {
- PackageInfo pkg = packagesToBackup.get(i);
+ // Now we cull any inapplicable / inappropriate packages from the set
+ Iterator> iter = packagesToBackup.entrySet().iterator();
+ while (iter.hasNext()) {
+ PackageInfo pkg = iter.next().getValue();
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0
|| pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
- packagesToBackup.remove(i);
- } else {
- i++;
- }
- }
-
- // Cull any packages that run as system-domain uids but do not define their
- // own backup agents
- for (int i = 0; i < packagesToBackup.size(); ) {
- PackageInfo pkg = packagesToBackup.get(i);
- if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
+ // Cull any packages that have indicated that backups are not permitted, as well
+ // as any explicit mention of the 'special' shared-storage agent package (we
+ // handle that one at the end).
+ iter.remove();
+ } else if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
&& (pkg.applicationInfo.backupAgentName == null)) {
+ // Cull any packages that run as system-domain uids but do not define their
+ // own backup agents
if (MORE_DEBUG) {
Slog.i(TAG, "... ignoring non-agent system package " + pkg.packageName);
}
- packagesToBackup.remove(i);
- } else {
- i++;
+ iter.remove();
}
}
+ // flatten the set of packages now so we can explicitly control the ordering
+ ArrayList backupQueue =
+ new ArrayList(packagesToBackup.values());
+
FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor());
OutputStream out = null;
@@ -2866,16 +2979,16 @@ public class BackupManagerService extends IBackupManager.Stub {
if (mIncludeShared) {
try {
pkg = mPackageManager.getPackageInfo(SHARED_BACKUP_AGENT_PACKAGE, 0);
- packagesToBackup.add(pkg);
+ backupQueue.add(pkg);
} catch (NameNotFoundException e) {
Slog.e(TAG, "Unable to find shared-storage backup handler");
}
}
// Now back up the app data via the agent mechanism
- int N = packagesToBackup.size();
+ int N = backupQueue.size();
for (int i = 0; i < N; i++) {
- pkg = packagesToBackup.get(i);
+ pkg = backupQueue.get(i);
backupOnePackage(pkg, out);
// after the app's agent runs to handle its private filesystem
@@ -3006,11 +3119,13 @@ public class BackupManagerService extends IBackupManager.Stub {
&& ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
(app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+ byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName,
+ UserHandle.USER_OWNER);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
final int token = generateToken();
FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
- token, sendApk, !isSharedStorage);
+ token, sendApk, !isSharedStorage, widgetBlob);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner);
@@ -3086,8 +3201,8 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
- private void writeAppManifest(PackageInfo pkg, File manifestFile, boolean withApk)
- throws IOException {
+ private void writeAppManifest(PackageInfo pkg, File manifestFile,
+ boolean withApk, boolean withWidgets) throws IOException {
// Manifest format. All data are strings ending in LF:
// BACKUP_MANIFEST_VERSION, currently 1
//
@@ -3125,6 +3240,43 @@ public class BackupManagerService extends IBackupManager.Stub {
outstream.close();
}
+ // Widget metadata format. All header entries are strings ending in LF:
+ //
+ // Version 1 header:
+ // BACKUP_METADATA_VERSION, currently "1"
+ // package name
+ //
+ // File data (all integers are binary in network byte order)
+ // *N: 4 : integer token identifying which metadata blob
+ // 4 : integer size of this blob = N
+ // N : raw bytes of this metadata blob
+ //
+ // Currently understood blobs (always in network byte order):
+ //
+ // widgets : metadata token = 0x01FFED01 (BACKUP_WIDGET_METADATA_TOKEN)
+ //
+ // Unrecognized blobs are *ignored*, not errors.
+ private void writeMetadata(PackageInfo pkg, File destination, byte[] widgetData)
+ throws IOException {
+ StringBuilder b = new StringBuilder(512);
+ StringBuilderPrinter printer = new StringBuilderPrinter(b);
+ printer.println(Integer.toString(BACKUP_METADATA_VERSION));
+ printer.println(pkg.packageName);
+
+ FileOutputStream fout = new FileOutputStream(destination);
+ BufferedOutputStream bout = new BufferedOutputStream(fout);
+ DataOutputStream out = new DataOutputStream(bout);
+ bout.write(b.toString().getBytes()); // bypassing DataOutputStream
+
+ if (widgetData != null && widgetData.length > 0) {
+ out.writeInt(BACKUP_WIDGET_METADATA_TOKEN);
+ out.writeInt(widgetData.length);
+ out.write(widgetData);
+ }
+ bout.flush();
+ out.close();
+ }
+
private void tearDown(PackageInfo pkg) {
if (pkg != null) {
final ApplicationInfo app = pkg.applicationInfo;
@@ -3228,6 +3380,7 @@ public class BackupManagerService extends IBackupManager.Stub {
ApplicationInfo mTargetApp;
FullBackupObbConnection mObbConnection = null;
ParcelFileDescriptor[] mPipes = null;
+ byte[] mWidgetData = null;
long mBytes;
@@ -3524,7 +3677,8 @@ public class BackupManagerService extends IBackupManager.Stub {
// Clean up the previous agent relationship if necessary,
// and let the observer know we're considering a new app.
if (mAgent != null) {
- if (DEBUG) Slog.d(TAG, "Saw new package; tearing down old one");
+ if (DEBUG) Slog.d(TAG, "Saw new package; finalizing old one");
+ // Now we're really done
tearDownPipes();
tearDownAgent(mTargetApp);
mTargetApp = null;
@@ -3540,6 +3694,9 @@ public class BackupManagerService extends IBackupManager.Stub {
// input file
skipTarPadding(info.size, instream);
sendOnRestorePackage(pkg);
+ } else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
+ // Metadata blobs!
+ readMetadata(info, instream);
} else {
// Non-manifest, so it's actual file data. Is this a package
// we're ignoring?
@@ -3996,6 +4153,71 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
+ // Read a widget metadata file, returning the restored blob
+ void readMetadata(FileMetadata info, InputStream instream) throws IOException {
+ byte[] data = null;
+
+ // Fail on suspiciously large widget dump files
+ if (info.size > 64 * 1024) {
+ throw new IOException("Metadata too big; corrupt? size=" + info.size);
+ }
+
+ byte[] buffer = new byte[(int) info.size];
+ if (readExactly(instream, buffer, 0, (int)info.size) == info.size) {
+ mBytes += info.size;
+ } else throw new IOException("Unexpected EOF in widget data");
+
+ String[] str = new String[1];
+ int offset = extractLine(buffer, 0, str);
+ int version = Integer.parseInt(str[0]);
+ if (version == BACKUP_MANIFEST_VERSION) {
+ offset = extractLine(buffer, offset, str);
+ final String pkg = str[0];
+ if (info.packageName.equals(pkg)) {
+ // Data checks out -- the rest of the buffer is a concatenation of
+ // binary blobs as described in the comment at writeAppWidgetData()
+ ByteArrayInputStream bin = new ByteArrayInputStream(buffer,
+ offset, buffer.length - offset);
+ DataInputStream in = new DataInputStream(bin);
+ while (bin.available() > 0) {
+ int token = in.readInt();
+ int size = in.readInt();
+ if (size > 64 * 1024) {
+ throw new IOException("Datum "
+ + Integer.toHexString(token)
+ + " too big; corrupt? size=" + info.size);
+ }
+ switch (token) {
+ case BACKUP_WIDGET_METADATA_TOKEN:
+ {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Got widget metadata for " + info.packageName);
+ }
+ mWidgetData = new byte[size];
+ in.read(mWidgetData);
+ break;
+ }
+ default:
+ {
+ if (DEBUG) {
+ Slog.i(TAG, "Ignoring metadata blob "
+ + Integer.toHexString(token)
+ + " for " + info.packageName);
+ }
+ in.skipBytes(size);
+ break;
+ }
+ }
+ }
+ } else {
+ Slog.w(TAG, "Metadata mismatch: package " + info.packageName
+ + " but widget data for " + pkg);
+ }
+ } else {
+ Slog.w(TAG, "Unsupported metadata version " + version);
+ }
+ }
+
// Returns a policy constant; takes a buffer arg to reduce memory churn
RestorePolicy readAppManifest(FileMetadata info, InputStream instream)
throws IOException {
@@ -4486,7 +4708,7 @@ public class BackupManagerService extends IBackupManager.Stub {
private PackageInfo mTargetPackage;
private File mStateDir;
private int mPmToken;
- private boolean mNeedFullBackup;
+ private boolean mIsSystemRestore;
private HashSet mFilterSet;
private long mStartRealtime;
private PackageManagerBackupAgent mPmAgent;
@@ -4497,11 +4719,13 @@ public class BackupManagerService extends IBackupManager.Stub {
private boolean mFinished;
private int mStatus;
private File mBackupDataName;
+ private File mStageName;
private File mNewStateName;
private File mSavedStateName;
private ParcelFileDescriptor mBackupData;
private ParcelFileDescriptor mNewState;
private PackageInfo mCurrentPackage;
+ private byte[] mWidgetData;
class RestoreRequest {
@@ -4516,7 +4740,7 @@ public class BackupManagerService extends IBackupManager.Stub {
PerformRestoreTask(IBackupTransport transport, String dirName, IRestoreObserver observer,
long restoreSetToken, PackageInfo targetPackage, int pmToken,
- boolean needFullBackup, String[] filterSet) {
+ boolean isSystemRestore, String[] filterSet) {
mCurrentState = RestoreState.INITIAL;
mFinished = false;
mPmAgent = null;
@@ -4526,7 +4750,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mToken = restoreSetToken;
mTargetPackage = targetPackage;
mPmToken = pmToken;
- mNeedFullBackup = needFullBackup;
+ mIsSystemRestore = isSystemRestore;
if (filterSet != null) {
mFilterSet = new HashSet();
@@ -4696,8 +4920,7 @@ public class BackupManagerService extends IBackupManager.Stub {
omPackage.packageName = PACKAGE_MANAGER_SENTINEL;
mPmAgent = new PackageManagerBackupAgent(
mPackageManager, mAgentPackages);
- initiateOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(mPmAgent.onBind()),
- mNeedFullBackup);
+ initiateOneRestore(omPackage, 0, IBackupAgent.Stub.asInterface(mPmAgent.onBind()));
// The PM agent called operationComplete() already, because our invocation
// of it is process-local and therefore synchronous. That means that a
// RUNNING_QUEUE message is already enqueued. Only if we're unable to
@@ -4727,6 +4950,12 @@ public class BackupManagerService extends IBackupManager.Stub {
// Metadata is intact, so we can now run the restore queue. If we get here,
// we have already enqueued the necessary next-step message on the looper.
+ // We've deferred telling the App Widget service that we might be replacing
+ // the widget environment with something else, but now we know we've got
+ // data coming, so we do it here.
+ if (mIsSystemRestore) {
+ AppWidgetBackupBridge.restoreStarting(UserHandle.USER_OWNER);
+ }
}
void restoreNextAgent() {
@@ -4835,7 +5064,7 @@ public class BackupManagerService extends IBackupManager.Stub {
// And then finally start the restore on this agent
try {
- initiateOneRestore(packageInfo, metaInfo.versionCode, agent, mNeedFullBackup);
+ initiateOneRestore(packageInfo, metaInfo.versionCode, agent);
++mCount;
} catch (Exception e) {
Slog.e(TAG, "Error when attempting restore: " + e.toString());
@@ -4889,6 +5118,9 @@ public class BackupManagerService extends IBackupManager.Stub {
mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
TIMEOUT_RESTORE_INTERVAL);
+ // Kick off any work that may be needed regarding app widget restores
+ AppWidgetBackupBridge.restoreFinished(UserHandle.USER_OWNER);
+
// done; we can finally release the wakelock
Slog.i(TAG, "Restore complete.");
mWakelock.release();
@@ -4896,43 +5128,92 @@ public class BackupManagerService extends IBackupManager.Stub {
// Call asynchronously into the app, passing it the restore data. The next step
// after this is always a callback, either operationComplete() or handleTimeout().
- void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent,
- boolean needFullBackup) {
+ void initiateOneRestore(PackageInfo app, int appVersionCode, IBackupAgent agent) {
mCurrentPackage = app;
+ mWidgetData = null;
final String packageName = app.packageName;
if (DEBUG) Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
// !!! TODO: get the dirs from the transport
mBackupDataName = new File(mDataDir, packageName + ".restore");
+ mStageName = new File(mDataDir, packageName + ".stage");
mNewStateName = new File(mStateDir, packageName + ".new");
mSavedStateName = new File(mStateDir, packageName);
+ // don't stage the 'android' package where the wallpaper data lives. this is
+ // an optimization: we know there's no widget data hosted/published by that
+ // package, and this way we avoid doing a spurious copy of MB-sized wallpaper
+ // data following the download.
+ boolean staging = !packageName.equals("android");
+ ParcelFileDescriptor stage;
+ File downloadFile = (staging) ? mStageName : mBackupDataName;
+
final int token = generateToken();
try {
// Run the transport's restore pass
- mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ stage = ParcelFileDescriptor.open(downloadFile,
ParcelFileDescriptor.MODE_READ_WRITE |
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
if (!SELinux.restorecon(mBackupDataName)) {
- Slog.e(TAG, "SElinux restorecon failed for " + mBackupDataName);
+ Slog.e(TAG, "SElinux restorecon failed for " + downloadFile);
}
- if (mTransport.getRestoreData(mBackupData) != BackupConstants.TRANSPORT_OK) {
+ if (mTransport.getRestoreData(stage) != BackupConstants.TRANSPORT_OK) {
// Transport-level failure, so we wind everything up and
// terminate the restore operation.
Slog.e(TAG, "Error getting restore data for " + packageName);
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
- mBackupData.close();
- mBackupDataName.delete();
+ stage.close();
+ downloadFile.delete();
executeNextState(RestoreState.FINAL);
return;
}
+ // We have the data from the transport. Now we extract and strip
+ // any per-package metadata (typically widget-related information)
+ // if appropriate
+ if (staging) {
+ stage.close();
+ stage = ParcelFileDescriptor.open(downloadFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+
+ BackupDataInput in = new BackupDataInput(stage.getFileDescriptor());
+ BackupDataOutput out = new BackupDataOutput(mBackupData.getFileDescriptor());
+ byte[] buffer = new byte[8192]; // will grow when needed
+ while (in.readNextHeader()) {
+ final String key = in.getKey();
+ final int size = in.getDataSize();
+
+ // is this a special key?
+ if (key.equals(KEY_WIDGET_STATE)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring widget state for " + packageName);
+ }
+ mWidgetData = new byte[size];
+ in.readEntityData(mWidgetData, 0, size);
+ } else {
+ if (size > buffer.length) {
+ buffer = new byte[size];
+ }
+ in.readEntityData(buffer, 0, size);
+ out.writeEntityHeader(key, size);
+ out.writeEntityData(buffer, size);
+ }
+ }
+
+ mBackupData.close();
+ }
+
// Okay, we have the data. Now have the agent do the restore.
- mBackupData.close();
+ stage.close();
mBackupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
@@ -4943,7 +5224,8 @@ public class BackupManagerService extends IBackupManager.Stub {
// Kick off the restore, checking for hung agents
prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, this);
- agent.doRestore(mBackupData, appVersionCode, mNewState, token, mBackupManagerBinder);
+ agent.doRestore(mBackupData, appVersionCode, mNewState,
+ token, mBackupManagerBinder);
} catch (Exception e) {
Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, e.toString());
@@ -4966,6 +5248,7 @@ public class BackupManagerService extends IBackupManager.Stub {
void agentCleanup() {
mBackupDataName.delete();
+ mStageName.delete();
try { if (mBackupData != null) mBackupData.close(); } catch (IOException e) {}
try { if (mNewState != null) mNewState.close(); } catch (IOException e) {}
mBackupData = mNewState = null;
@@ -5024,9 +5307,17 @@ public class BackupManagerService extends IBackupManager.Stub {
public void operationComplete() {
int size = (int) mBackupDataName.length();
EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE, mCurrentPackage.packageName, size);
+
// Just go back to running the restore queue
agentCleanup();
+ // If there was widget state associated with this app, get the OS to
+ // incorporate it into current bookeeping and then pass that along to
+ // the app as part of the restore operation.
+ if (mWidgetData != null) {
+ restoreWidgetData(mCurrentPackage.packageName, mWidgetData);
+ }
+
executeNextState(RestoreState.RUNNING_QUEUE);
}
@@ -5050,6 +5341,12 @@ public class BackupManagerService extends IBackupManager.Stub {
}
}
+ // Used by both incremental and full restore
+ void restoreWidgetData(String packageName, byte[] widgetData) {
+ // Apply the restored widget state and generate the ID update for the app
+ AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, UserHandle.USER_OWNER);
+ }
+
class PerformClearTask implements Runnable {
IBackupTransport mTransport;
PackageInfo mPackage;
@@ -5345,8 +5642,9 @@ public class BackupManagerService extends IBackupManager.Stub {
// Run a *full* backup pass for the given package, writing the resulting data stream
// to the supplied file descriptor. This method is synchronous and does not return
// to the caller until the backup has been completed.
+ @Override
public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
- boolean includeObbs, boolean includeShared,
+ boolean includeObbs, boolean includeShared, boolean doWidgets,
boolean doAllApps, boolean includeSystem, String[] pkgList) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
@@ -5382,7 +5680,7 @@ public class BackupManagerService extends IBackupManager.Stub {
Slog.i(TAG, "Beginning full backup...");
FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
- includeShared, doAllApps, includeSystem, pkgList);
+ includeShared, doWidgets, doAllApps, includeSystem, pkgList);
final int token = generateToken();
synchronized (mFullConfirmations) {
mFullConfirmations.put(token, params);
@@ -5839,7 +6137,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = new RestoreParams(transport, dirName, null,
- restoreSet, pkg, token, true);
+ restoreSet, pkg, token);
mBackupHandler.sendMessage(msg);
} catch (RemoteException e) {
// Binding to the transport broke; back off and proceed with the installation.
@@ -6020,7 +6318,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = new RestoreParams(mRestoreTransport, dirName,
- observer, token, true);
+ observer, token);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;
@@ -6032,6 +6330,7 @@ public class BackupManagerService extends IBackupManager.Stub {
return -1;
}
+ // Restores of more than a single package are treated as 'system' restores
public synchronized int restoreSome(long token, IRestoreObserver observer,
String[] packages) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
@@ -6090,7 +6389,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, token,
- packages, true);
+ packages, packages.length > 1);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;
@@ -6169,7 +6468,7 @@ public class BackupManagerService extends IBackupManager.Stub {
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = new RestoreParams(mRestoreTransport, dirName,
- observer, token, app, 0, false);
+ observer, token, app, 0);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;