diff options
author | Christopher Tate <ctate@google.com> | 2015-04-01 17:18:50 -0700 |
---|---|---|
committer | Christopher Tate <ctate@google.com> | 2015-04-06 17:36:25 -0700 |
commit | e012a235569fe307d165dfd0784ae847d0b13739 (patch) | |
tree | f60b37a641be0e89eeba32cdef7f825dedfbdd29 | |
parent | 0f2974321b8109d7d4e888a8a9662b5f9813c114 (diff) | |
download | frameworks_base-e012a235569fe307d165dfd0784ae847d0b13739.zip frameworks_base-e012a235569fe307d165dfd0784ae847d0b13739.tar.gz frameworks_base-e012a235569fe307d165dfd0784ae847d0b13739.tar.bz2 |
Back up / restore preferred app configuration
Bug 19848104
Change-Id: I84cdfcc44b48a9732984955d7eedf745b5586bdd
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"); |