summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristopher Tate <ctate@google.com>2015-04-01 17:18:50 -0700
committerChristopher Tate <ctate@google.com>2015-04-06 17:36:25 -0700
commite012a235569fe307d165dfd0784ae847d0b13739 (patch)
treef60b37a641be0e89eeba32cdef7f825dedfbdd29
parent0f2974321b8109d7d4e888a8a9662b5f9813c114 (diff)
downloadframeworks_base-e012a235569fe307d165dfd0784ae847d0b13739.zip
frameworks_base-e012a235569fe307d165dfd0784ae847d0b13739.tar.gz
frameworks_base-e012a235569fe307d165dfd0784ae847d0b13739.tar.bz2
Back up / restore preferred app configuration
Bug 19848104 Change-Id: I84cdfcc44b48a9732984955d7eedf745b5586bdd
-rw-r--r--core/java/android/app/backup/BackupTransport.java2
-rw-r--r--core/java/android/app/backup/RecentsBackupHelper.java16
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl6
-rw-r--r--core/java/com/android/server/backup/PreferredActivityBackupHelper.java175
-rw-r--r--core/java/com/android/server/backup/SystemBackupAgent.java26
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java85
-rw-r--r--services/core/java/com/android/server/pm/Settings.java16
8 files changed, 314 insertions, 13 deletions
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index ca6dc69..1131ff9 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -464,7 +464,7 @@ public class BackupTransport {
* transport level).
*
* <p>After this method returns zero, the system will then call
- * {@link #getNextFullRestorePackage()} to begin the restore process for the next
+ * {@link #nextRestorePackage()} to begin the restore process for the next
* application, and the sequence begins again.
*
* <p>The transport should always close this socket when returning from this method.
diff --git a/core/java/android/app/backup/RecentsBackupHelper.java b/core/java/android/app/backup/RecentsBackupHelper.java
index fd69d20..1a64da6 100644
--- a/core/java/android/app/backup/RecentsBackupHelper.java
+++ b/core/java/android/app/backup/RecentsBackupHelper.java
@@ -1,3 +1,19 @@
+/*
+ * 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 android.app.backup;
import android.content.Context;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index fb7c96d..649bb47 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -269,6 +269,12 @@ interface IPackageManager {
void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
/**
+ * Backup/restore support - only the system uid may use these.
+ */
+ byte[] getPreferredActivityBackup(int userId);
+ void restorePreferredActivities(in byte[] backup, int userId);
+
+ /**
* Report the set of 'Home' activity candidates, plus (if any) which of them
* is the current "always use this one" setting.
*/
diff --git a/core/java/com/android/server/backup/PreferredActivityBackupHelper.java b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java
new file mode 100644
index 0000000..6ac0d89
--- /dev/null
+++ b/core/java/com/android/server/backup/PreferredActivityBackupHelper.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 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.backup;
+
+import android.app.AppGlobals;
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupHelper;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.org.bouncycastle.util.Arrays;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class PreferredActivityBackupHelper implements BackupHelper {
+ private static final String TAG = "PreferredBackup";
+ private static final boolean DEBUG = true;
+
+ // current schema of the backup state blob
+ private static final int STATE_VERSION = 1;
+
+ // key under which the preferred-activity state blob is committed to backup
+ private static final String KEY_PREFERRED = "preferred-activity";
+
+ final Context mContext;
+
+ public PreferredActivityBackupHelper(Context context) {
+ mContext = context;
+ }
+
+ // The fds passed here are shared among all helpers, so we mustn't close them
+ private void writeState(ParcelFileDescriptor stateFile, byte[] payload) {
+ try {
+ FileOutputStream fos = new FileOutputStream(stateFile.getFileDescriptor());
+
+ // We explicitly don't close 'out' because we must not close the backing fd.
+ // The FileOutputStream will not close it implicitly.
+ @SuppressWarnings("resource")
+ DataOutputStream out = new DataOutputStream(fos);
+
+ out.writeInt(STATE_VERSION);
+ if (payload == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(payload.length);
+ out.write(payload);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to write updated state", e);
+ }
+ }
+
+ private byte[] readState(ParcelFileDescriptor oldStateFd) {
+ FileInputStream fis = new FileInputStream(oldStateFd.getFileDescriptor());
+ BufferedInputStream bis = new BufferedInputStream(fis);
+
+ @SuppressWarnings("resource")
+ DataInputStream in = new DataInputStream(bis);
+
+ byte[] oldState = null;
+ try {
+ int version = in.readInt();
+ if (version == STATE_VERSION) {
+ int size = in.readInt();
+ if (size > 0) {
+ if (size > 200*1024) {
+ Slog.w(TAG, "Suspiciously large state blog; ignoring. N=" + size);
+ } else {
+ // size looks okay; make the return buffer and fill it
+ oldState = new byte[size];
+ in.read(oldState);
+ }
+ }
+ } else {
+ Slog.w(TAG, "Prior state from unrecognized version " + version);
+ }
+ } catch (EOFException e) {
+ // Empty file is expected on first backup, so carry on. If the state
+ // is truncated we just treat it the same way.
+ oldState = null;
+ } catch (Exception e) {
+ Slog.w(TAG, "Error examing prior backup state " + e.getMessage());
+ oldState = null;
+ }
+
+ return oldState;
+ }
+
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ byte[] payload = null;
+ try {
+ byte[] oldPayload = readState(oldState);
+
+ IPackageManager pm = AppGlobals.getPackageManager();
+ byte[] newPayload = pm.getPreferredActivityBackup(UserHandle.USER_OWNER);
+ if (!Arrays.areEqual(oldPayload, newPayload)) {
+ if (DEBUG) {
+ Slog.i(TAG, "State has changed => writing new preferred app payload");
+ }
+ data.writeEntityHeader(KEY_PREFERRED, newPayload.length);
+ data.writeEntityData(newPayload, newPayload.length);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "No change to state => not writing to wire");
+ }
+ }
+
+ // Always need to re-record the state, even if nothing changed
+ payload = newPayload;
+ } catch (Exception e) {
+ // On failures we'll wind up committing a zero-size state payload. This is
+ // a forward-safe situation because we know we commit the entire new payload
+ // on prior-state mismatch.
+ Slog.w(TAG, "Unable to record preferred activities", e);
+ } finally {
+ writeState(newState, payload);
+ }
+ }
+
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ try {
+ byte[] payload = new byte[data.size()];
+ data.read(payload);
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring preferred activities; size=" + payload.length);
+ }
+ pm.restorePreferredActivities(payload, UserHandle.USER_OWNER);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception reading restore data", e);
+ }
+ }
+
+ @Override
+ public void writeNewStateDescription(ParcelFileDescriptor newState) {
+ writeState(newState, null);
+ }
+
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 037fd66..19d9e29 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -16,7 +16,6 @@
package com.android.server.backup;
-
import android.app.ActivityManagerNative;
import android.app.IWallpaperManager;
import android.app.backup.BackupDataInput;
@@ -43,6 +42,13 @@ import java.io.IOException;
public class SystemBackupAgent extends BackupAgentHelper {
private static final String TAG = "SystemBackupAgent";
+ // Names of the helper tags within the dataset. Changing one of these names will
+ // break the ability to restore from datasets that predate the change.
+ private static final String WALLPAPER_HELPER = "wallpaper";
+ private static final String RECENTS_HELPER = "recents";
+ private static final String SYNC_SETTINGS_HELPER = "account_sync_settings";
+ private static final String PREFERRED_HELPER = "preferred_activities";
+
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
// taken to support the legacy backed-up datasets.
@@ -84,10 +90,10 @@ public class SystemBackupAgent extends BackupAgentHelper {
Slog.e(TAG, "Couldn't get wallpaper name\n" + re);
}
}
- addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys));
- addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this));
- addHelper("account_sync_settings",
- new AccountSyncSettingsBackupHelper(SystemBackupAgent.this));
+ addHelper(WALLPAPER_HELPER, new WallpaperBackupHelper(this, files, keys));
+ addHelper(RECENTS_HELPER, new RecentsBackupHelper(this));
+ addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
+ addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(this));
super.onBackup(oldState, data, newState);
}
@@ -113,15 +119,15 @@ public class SystemBackupAgent extends BackupAgentHelper {
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
// On restore, we also support a previous data schema "system_files"
- addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this,
+ addHelper(WALLPAPER_HELPER, new WallpaperBackupHelper(this,
new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO },
new String[] { WALLPAPER_IMAGE_KEY, WALLPAPER_INFO_KEY} ));
- addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this,
+ addHelper("system_files", new WallpaperBackupHelper(this,
new String[] { WALLPAPER_IMAGE },
new String[] { WALLPAPER_IMAGE_KEY} ));
- addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this));
- addHelper("account_sync_settings",
- new AccountSyncSettingsBackupHelper(SystemBackupAgent.this));
+ addHelper(RECENTS_HELPER, new RecentsBackupHelper(this));
+ addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
+ addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper(this));
try {
super.onRestore(data, appVersionCode, newState);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 5cc59e5..96840a2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -94,6 +94,7 @@ import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.SystemService;
import com.android.server.backup.PackageManagerBackupAgent.Metadata;
+import com.android.server.pm.PackageManagerService;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3c99484..2ff1718 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -87,6 +87,7 @@ import com.android.server.Watchdog;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.storage.DeviceStorageMonitorInternal;
+import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import android.app.ActivityManager;
@@ -189,11 +190,14 @@ import android.util.PrintStreamPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.Xml;
import android.view.Display;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -246,6 +250,7 @@ public class PackageManagerService extends IPackageManager.Stub {
static final boolean DEBUG_SETTINGS = false;
static final boolean DEBUG_PREFERRED = false;
static final boolean DEBUG_UPGRADE = false;
+ private static final boolean DEBUG_BACKUP = true;
private static final boolean DEBUG_INSTALL = false;
private static final boolean DEBUG_REMOVE = false;
private static final boolean DEBUG_BROADCASTS = false;
@@ -866,6 +871,9 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>();
int mNextInstallToken = 1; // nonzero; will be wrapped back to 1 when ++ overflows
+ // backup/restore of preferred activity state
+ private static final String TAG_PREFERRED_BACKUP = "pa";
+
private final String mRequiredVerifierPackage;
private final PackageUsage mPackageUsage = new PackageUsage();
@@ -12525,6 +12533,83 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ /**
+ * Non-Binder method, support for the backup/restore mechanism: write the
+ * full set of preferred activities in its canonical XML format. Returns true
+ * on success; false otherwise.
+ */
+ @Override
+ public byte[] getPreferredActivityBackup(int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call getPreferredActivityBackup()");
+ }
+
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ try {
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(dataStream, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_PREFERRED_BACKUP);
+
+ synchronized (mPackages) {
+ mSettings.writePreferredActivitiesLPr(serializer, userId, true);
+ }
+
+ serializer.endTag(null, TAG_PREFERRED_BACKUP);
+ serializer.endDocument();
+ serializer.flush();
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Unable to write preferred activities for backup", e);
+ }
+ return null;
+ }
+
+ return dataStream.toByteArray();
+ }
+
+ @Override
+ public void restorePreferredActivities(byte[] backup, int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call restorePreferredActivities()");
+ }
+
+ try {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new ByteArrayInputStream(backup), null);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ // oops didn't find a start tag?!
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Didn't find start tag during restore");
+ }
+ return;
+ }
+
+ // this is supposed to be TAG_PREFERRED_BACKUP
+ if (!TAG_PREFERRED_BACKUP.equals(parser.getName())) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Found unexpected tag " + parser.getName());
+ }
+ return;
+ }
+
+ // skip interfering stuff, then we're aligned with the backing implementation
+ while ((type = parser.next()) == XmlPullParser.TEXT) { }
+ synchronized (mPackages) {
+ mSettings.readPreferredActivitiesLPw(parser, userId);
+ }
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
+ }
+ }
+ }
+
@Override
public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
int sourceUserId, int targetUserId, int flags) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0d2ef89..6930965 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -45,15 +45,16 @@ import android.os.UserManager;
import android.util.AtomicFile;
import android.text.TextUtils;
import android.util.LogPrinter;
-
import android.util.SparseBooleanArray;
import android.util.SparseLongArray;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
+import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.PackageManagerService.DumpState;
import java.io.FileNotFoundException;
@@ -1108,7 +1109,13 @@ final class Settings {
mExternalDatabaseVersion = CURRENT_DATABASE_VERSION;
}
- private void readPreferredActivitiesLPw(XmlPullParser parser, int userId)
+ /**
+ * Applies the preferred activity state described by the given XML. This code
+ * also supports the restore-from-backup code path.
+ *
+ * @see PreferredActivityBackupHelper
+ */
+ void readPreferredActivitiesLPw(XmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
@@ -1399,6 +1406,11 @@ final class Settings {
return components;
}
+ /**
+ * Record the state of preferred activity configuration into XML. This is used both
+ * for recording packages.xml internally and for supporting backup/restore of the
+ * preferred activity configuration.
+ */
void writePreferredActivitiesLPr(XmlSerializer serializer, int userId, boolean full)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, "preferred-activities");