diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java index a6d5becd3e820..05e667ed4764f 100644 --- a/core/java/android/backup/BackupDataOutput.java +++ b/core/java/android/backup/BackupDataOutput.java @@ -36,6 +36,7 @@ public class BackupDataOutput { } } + // A dataSize of -1 indicates that the record under this key should be deleted public int writeEntityHeader(String key, int dataSize) throws IOException { int result = writeEntityHeader_native(mBackupWriter, key, dataSize); if (result >= 0) { diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 231582104cc81..f9a6f5ba244a3 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -52,6 +52,8 @@ import android.backup.RestoreSet; import com.android.internal.backup.LocalTransport; import com.android.internal.backup.IBackupTransport; +import com.android.server.PackageManagerBackupAgent; + import java.io.EOFException; import java.io.File; import java.io.FileDescriptor; @@ -70,6 +72,7 @@ class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "BackupManagerService"; private static final boolean DEBUG = true; + // Default time to wait after data changes before we back up the data private static final long COLLECTION_INTERVAL = 1000; //private static final long COLLECTION_INTERVAL = 3 * 60 * 1000; @@ -104,6 +107,9 @@ class BackupManagerService extends IBackupManager.Stub { // Backups that we haven't started yet. private HashMap mPendingBackups = new HashMap(); + // Do we need to back up the package manager metadata on the next pass? + private boolean mDoPackageManager; + private static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; // Backups that we have started. These are separate to prevent starvation // if an app keeps re-enqueuing itself. private ArrayList mBackupQueue; @@ -363,12 +369,13 @@ class BackupManagerService extends IBackupManager.Stub { mBackupParticipants.put(uid, set); } set.add(app); + backUpPackageManagerData(); } } } - // Remove the given package's backup services from our known active set. If - // 'packageName' is null, *all* backup services will be removed. + // Remove the given package's entry from our known active set. If + // 'packageName' is null, *all* participating apps will be removed. void removePackageParticipantsLocked(String packageName) { if (DEBUG) Log.v(TAG, "removePackageParticipantsLocked: " + packageName); List allApps = null; @@ -406,6 +413,7 @@ class BackupManagerService extends IBackupManager.Stub { for (ApplicationInfo entry: set) { if (entry.packageName.equals(app.packageName)) { set.remove(entry); + backUpPackageManagerData(); break; } } @@ -418,6 +426,7 @@ class BackupManagerService extends IBackupManager.Stub { // Returns the set of all applications that define an android:backupAgent attribute private List allAgentApps() { + // !!! TODO: cache this and regenerate only when necessary List allApps = mPackageManager.getInstalledApplications(0); int N = allApps.size(); if (N > 0) { @@ -447,13 +456,29 @@ class BackupManagerService extends IBackupManager.Stub { addPackageParticipantsLockedInner(packageName, allApps); } + private void backUpPackageManagerData() { + // No need to schedule a backup just for the metadata; just piggyback on + // the next actual data backup. + synchronized(this) { + mDoPackageManager = true; + } + } + + // The queue lock should be held when scheduling a backup pass + private void scheduleBackupPassLocked(long timeFromNowMillis) { + mBackupHandler.removeMessages(MSG_RUN_BACKUP); + mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, timeFromNowMillis); + } + // Return the given transport private IBackupTransport getTransport(int transportID) { switch (transportID) { case BackupManager.TRANSPORT_LOCAL: + Log.v(TAG, "Supplying local transport"); return mLocalTransport; case BackupManager.TRANSPORT_GOOGLE: + Log.v(TAG, "Supplying Google transport"); return mGoogleTransport; default: @@ -558,7 +583,29 @@ class BackupManagerService extends IBackupManager.Stub { return; } - // The transport is up and running; now run all the backups in our queue + // The transport is up and running. First, back up the package manager + // metadata if necessary + boolean doPackageManager; + synchronized (BackupManagerService.this) { + doPackageManager = mDoPackageManager; + mDoPackageManager = false; + } + if (doPackageManager) { + // The package manager doesn't have a proper etc, but since + // it's running here in the system process we can just set up its agent + // directly and use a synthetic BackupRequest. + if (DEBUG) Log.i(TAG, "Running PM backup pass as well"); + + PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( + mPackageManager, allAgentApps()); + BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); + pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; + processOneBackup(pmRequest, + IBackupAgent.Stub.asInterface(pmAgent.onBind()), + mTransport); + } + + // Now run all the backups in our queue doQueuedBackups(mTransport); // Finally, tear down the transport @@ -735,6 +782,8 @@ class BackupManagerService extends IBackupManager.Stub { } if (err == 0) { + // !!! TODO: do the package manager signatures restore first + // build the set of apps to restore try { RestoreSet[] images = mTransport.getAvailableRestoreSets(); @@ -920,8 +969,7 @@ class BackupManagerService extends IBackupManager.Stub { // Schedule a backup pass in a few minutes. As backup-eligible data // keeps changing, continue to defer the backup pass until things // settle down, to avoid extra overhead. - mBackupHandler.removeMessages(MSG_RUN_BACKUP); - mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL); + scheduleBackupPassLocked(COLLECTION_INTERVAL); } } else { Log.w(TAG, "dataChanged but no participant pkg " + packageName); @@ -947,8 +995,7 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { - mBackupHandler.removeMessages(MSG_RUN_BACKUP); - mBackupHandler.sendEmptyMessage(MSG_RUN_BACKUP); + scheduleBackupPassLocked(0); } } diff --git a/services/java/com/android/server/PackageManagerBackupAgent.java b/services/java/com/android/server/PackageManagerBackupAgent.java new file mode 100644 index 0000000000000..6bd32a02f464a --- /dev/null +++ b/services/java/com/android/server/PackageManagerBackupAgent.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2009 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 android.app.BackupAgent; +import android.backup.BackupDataInput; +import android.backup.BackupDataOutput; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +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; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +// !!!TODO: take this out +import java.util.zip.CRC32; + +/** + * We back up the signatures of each package so that during a system restore, + * we can verify that the app whose data we think we have matches the app + * actually resident on the device. + * + * Since the Package Manager isn't a proper "application" we just provide a + * direct IBackupAgent implementation and hand-construct it at need. + */ +public class PackageManagerBackupAgent extends BackupAgent { + private static final String TAG = "PMBA"; + private static final boolean DEBUG = true; + + private List mAllApps; + private PackageManager mPackageManager; + private HashMap mRestoredSignatures; + + // We're constructed with the set of applications that are participating + // in backup. This set changes as apps are installed & removed. + PackageManagerBackupAgent(PackageManager packageMgr, List apps) { + mPackageManager = packageMgr; + mAllApps = apps; + mRestoredSignatures = null; + } + + public Signature[] getRestoredSignatures(String packageName) { + if (mRestoredSignatures == null) { + return null; + } + + return mRestoredSignatures.get(packageName); + } + + // The backed up data is the signature block for each app, keyed by + // the package name. + public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) { + HashSet existing = parseStateFile(oldState); + + // For each app we have on device, see if we've backed it up yet. If not, + // write its signature block to the output, keyed on the package name. + for (ApplicationInfo app : mAllApps) { + String packName = app.packageName; + if (!existing.contains(packName)) { + // We haven't stored this app's signatures yet, so we do that now + try { + PackageInfo info = mPackageManager.getPackageInfo(packName, + PackageManager.GET_SIGNATURES); + // build a byte array out of the signature list + byte[] sigs = flattenSignatureArray(info.signatures); +// !!! TODO: take out this debugging + if (DEBUG) { + CRC32 crc = new CRC32(); + crc.update(sigs); + Log.i(TAG, "+ flat sig array for " + packName + " : " + + crc.getValue()); + } + data.writeEntityHeader(packName, sigs.length); + data.writeEntityData(sigs, sigs.length); + } catch (NameNotFoundException e) { + // Weird; we just found it, and now are told it doesn't exist. + // Treat it as having been removed from the device. + existing.add(packName); + } catch (IOException e) { + // Real error writing data + Log.e(TAG, "Unable to write package backup data file!"); + return; + } + } else { + // We've already backed up this app. Remove it from the set so + // we can tell at the end what has disappeared from the device. + if (!existing.remove(packName)) { + Log.d(TAG, "*** failed to remove " + packName + " from package set!"); + } + } + } + + // At this point, the only entries in 'existing' are apps that were + // mentioned in the saved state file, but appear to no longer be present + // on the device. Write a deletion entity for them. + for (String app : existing) { + try { + data.writeEntityHeader(app, -1); + } catch (IOException e) { + Log.e(TAG, "Unable to write package deletions!"); + return; + } + } + + // Finally, write the new state blob -- just the list of all apps we handled + writeStateFile(mAllApps, newState); + } + + // "Restore" here is a misnomer. What we're really doing is reading back the + // set of app signatures associated with each backed-up app in this restore + // image. We'll use those later to determine what we can legitimately restore. + public void onRestore(BackupDataInput data, ParcelFileDescriptor newState) + throws IOException { + List restoredApps = new ArrayList(); + HashMap sigMap = new HashMap(); + + while (data.readNextHeader()) { + int dataSize = data.getDataSize(); + byte[] buf = new byte[dataSize]; + data.readEntityData(buf, 0, dataSize); + + Signature[] sigs = unflattenSignatureArray(buf); + String pkg = data.getKey(); +// !!! TODO: take out this debugging + if (DEBUG) { + CRC32 crc = new CRC32(); + crc.update(buf); + Log.i(TAG, "- unflat sig array for " + pkg + " : " + + crc.getValue()); + } + + ApplicationInfo app = new ApplicationInfo(); + app.packageName = pkg; + restoredApps.add(app); + sigMap.put(pkg, sigs); + } + + mRestoredSignatures = sigMap; + } + + + // Util: convert an array of Signatures into a flattened byte buffer. The + // flattened format contains enough info to reconstruct the signature array. + private byte[] flattenSignatureArray(Signature[] allSigs) { + ByteArrayOutputStream outBuf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(outBuf); + + // build the set of subsidiary buffers + try { + // first the # of signatures in the array + out.writeInt(allSigs.length); + + // then the signatures themselves, length + flattened buffer + for (Signature sig : allSigs) { + byte[] flat = sig.toByteArray(); + out.writeInt(flat.length); + out.write(flat); + } + } catch (IOException e) { + // very strange; we're writing to memory here. abort. + return null; + } + + return outBuf.toByteArray(); + } + + private Signature[] unflattenSignatureArray(byte[] buffer) { + ByteArrayInputStream inBufStream = new ByteArrayInputStream(buffer); + DataInputStream in = new DataInputStream(inBufStream); + Signature[] sigs = null; + + try { + int num = in.readInt(); + sigs = new Signature[num]; + for (int i = 0; i < num; i++) { + int len = in.readInt(); + byte[] flatSig = new byte[len]; + in.read(flatSig); + sigs[i] = new Signature(flatSig); + } + } catch (EOFException e) { + // clean termination + if (sigs == null) { + Log.w(TAG, "Empty signature block found"); + } + } catch (IOException e) { + Log.d(TAG, "Unable to unflatten sigs"); + return null; + } + + return sigs; + } + + // Util: parse out an existing state file into a usable structure + private HashSet parseStateFile(ParcelFileDescriptor stateFile) { + HashSet set = new HashSet(); + // The state file is just the list of app names we have stored signatures for + FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor()); + DataInputStream in = new DataInputStream(instream); + + int bufSize = 256; + byte[] buf = new byte[bufSize]; + try { + int nameSize = in.readInt(); + if (bufSize < nameSize) { + bufSize = nameSize + 32; + buf = new byte[bufSize]; + } + in.read(buf, 0, nameSize); + String pkg = new String(buf, 0, nameSize); + set.add(pkg); + } catch (EOFException eof) { + // safe; we're done + } catch (IOException e) { + // whoops, bad state file. abort. + Log.e(TAG, "Unable to read Package Manager state file"); + return null; + } + return set; + } + + // Util: write a set of names into a new state file + private void writeStateFile(List apps, ParcelFileDescriptor stateFile) { + FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor()); + DataOutputStream out = new DataOutputStream(outstream); + + for (ApplicationInfo app : apps) { + try { + byte[] pkgNameBuf = app.packageName.getBytes(); + out.writeInt(pkgNameBuf.length); + out.write(pkgNameBuf); + } catch (IOException e) { + Log.e(TAG, "Unable to write package manager state file!"); + return; + } + } + } +}