2 * Copyright (C) 2009 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.server.backup;
19 import android.app.backup.BackupAgent;
20 import android.app.backup.BackupDataInput;
21 import android.app.backup.BackupDataOutput;
22 import android.content.ComponentName;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.Signature;
29 import android.os.Build;
30 import android.os.ParcelFileDescriptor;
31 import android.util.Slog;
33 import java.io.BufferedInputStream;
34 import java.io.BufferedOutputStream;
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
37 import java.io.DataInputStream;
38 import java.io.DataOutputStream;
39 import java.io.EOFException;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.List;
49 import java.util.Objects;
52 * We back up the signatures of each package so that during a system restore,
53 * we can verify that the app whose data we think we have matches the app
54 * actually resident on the device.
56 * Since the Package Manager isn't a proper "application" we just provide a
57 * direct IBackupAgent implementation and hand-construct it at need.
59 public class PackageManagerBackupAgent extends BackupAgent {
60 private static final String TAG = "PMBA";
61 private static final boolean DEBUG = false;
63 // key under which we store global metadata (individual app metadata
64 // is stored using the package name as a key)
65 private static final String GLOBAL_METADATA_KEY = "@meta@";
67 // key under which we store the identity of the user's chosen default home app
68 private static final String DEFAULT_HOME_KEY = "@home@";
70 // Sentinel: start of state file, followed by a version number
71 private static final String STATE_FILE_HEADER = "=state=";
72 private static final int STATE_FILE_VERSION = 2;
74 // Current version of the saved ancestral-dataset file format
75 private static final int ANCESTRAL_RECORD_VERSION = 1;
77 private List<PackageInfo> mAllPackages;
78 private PackageManager mPackageManager;
79 // version & signature info of each app in a restore set
80 private HashMap<String, Metadata> mRestoredSignatures;
81 // The version info of each backed-up app as read from the state file
82 private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();
84 private final HashSet<String> mExisting = new HashSet<String>();
85 private int mStoredSdkVersion;
86 private String mStoredIncrementalVersion;
87 private ComponentName mStoredHomeComponent;
88 private long mStoredHomeVersion;
89 private ArrayList<byte[]> mStoredHomeSigHashes;
91 private boolean mHasMetadata;
92 private ComponentName mRestoredHome;
93 private long mRestoredHomeVersion;
94 private String mRestoredHomeInstaller;
95 private ArrayList<byte[]> mRestoredHomeSigHashes;
97 // For compactness we store the SHA-256 hash of each app's Signatures
98 // rather than the Signature blocks themselves.
99 public class Metadata {
100 public int versionCode;
101 public ArrayList<byte[]> sigHashes;
103 Metadata(int version, ArrayList<byte[]> hashes) {
104 versionCode = version;
109 // We're constructed with the set of applications that are participating
110 // in backup. This set changes as apps are installed & removed.
111 PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
112 init(packageMgr, packages);
115 PackageManagerBackupAgent(PackageManager packageMgr) {
116 init(packageMgr, null);
118 evaluateStorablePackages();
121 private void init(PackageManager packageMgr, List<PackageInfo> packages) {
122 mPackageManager = packageMgr;
123 mAllPackages = packages;
124 mRestoredSignatures = null;
125 mHasMetadata = false;
127 mStoredSdkVersion = Build.VERSION.SDK_INT;
128 mStoredIncrementalVersion = Build.VERSION.INCREMENTAL;
131 // We will need to refresh our understanding of what is eligible for
132 // backup periodically; this entry point serves that purpose.
133 public void evaluateStorablePackages() {
134 mAllPackages = getStorableApplications(mPackageManager);
137 public static List<PackageInfo> getStorableApplications(PackageManager pm) {
138 List<PackageInfo> pkgs;
139 pkgs = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
141 for (int a = N-1; a >= 0; a--) {
142 PackageInfo pkg = pkgs.get(a);
143 if (!BackupManagerService.appIsEligibleForBackup(pkg.applicationInfo)) {
150 public boolean hasMetadata() {
154 public Metadata getRestoredMetadata(String packageName) {
155 if (mRestoredSignatures == null) {
156 Slog.w(TAG, "getRestoredMetadata() before metadata read!");
160 return mRestoredSignatures.get(packageName);
163 public Set<String> getRestoredPackages() {
164 if (mRestoredSignatures == null) {
165 Slog.w(TAG, "getRestoredPackages() before metadata read!");
169 // This is technically the set of packages on the originating handset
170 // that had backup agents at all, not limited to the set of packages
171 // that had actually contributed a restore dataset, but it's a
172 // close enough approximation for our purposes and does not require any
173 // additional involvement by the transport to obtain.
174 return mRestoredSignatures.keySet();
177 // The backed up data is the signature block for each app, keyed by
179 public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
180 ParcelFileDescriptor newState) {
181 if (DEBUG) Slog.v(TAG, "onBackup()");
183 ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); // we'll reuse these
184 DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer);
185 parseStateFile(oldState);
187 // If the stored version string differs, we need to re-backup all
188 // of the metadata. We force this by removing everything from the
189 // "already backed up" map built by parseStateFile().
190 if (mStoredIncrementalVersion == null
191 || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
192 Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
193 + Build.VERSION.INCREMENTAL + " - rewriting");
197 long homeVersion = 0;
198 ArrayList<byte[]> homeSigHashes = null;
199 PackageInfo homeInfo = null;
200 String homeInstaller = null;
201 ComponentName home = getPreferredHomeComponent();
204 homeInfo = mPackageManager.getPackageInfo(home.getPackageName(),
205 PackageManager.GET_SIGNATURES);
206 homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
207 homeVersion = homeInfo.versionCode;
208 homeSigHashes = hashSignatureArray(homeInfo.signatures);
209 } catch (NameNotFoundException e) {
210 Slog.w(TAG, "Can't access preferred home info");
211 // proceed as though there were no preferred home set
217 // We need to push a new preferred-home-app record if:
218 // 1. the version of the home app has changed since our last backup;
219 // 2. the home app [or absence] we now use differs from the prior state,
220 // OR 3. it looks like we use the same home app + version as before, but
221 // the signatures don't match so we treat them as different apps.
222 final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
223 || !Objects.equals(home, mStoredHomeComponent)
225 && !BackupManagerService.signaturesMatch(mStoredHomeSigHashes, homeInfo));
226 if (needHomeBackup) {
228 Slog.i(TAG, "Home preference changed; backing up new state " + home);
231 outputBufferStream.writeUTF(home.flattenToString());
232 outputBufferStream.writeLong(homeVersion);
233 outputBufferStream.writeUTF(homeInstaller != null ? homeInstaller : "" );
234 writeSignatureHashArray(outputBufferStream, homeSigHashes);
235 writeEntity(data, DEFAULT_HOME_KEY, outputBuffer.toByteArray());
237 data.writeEntityHeader(DEFAULT_HOME_KEY, -1);
244 * int SDKversion -- the SDK version of the OS itself on the device
245 * that produced this backup set. Used to reject
246 * backups from later OSes onto earlier ones.
247 * String incremental -- the incremental release name of the OS stored in
250 outputBuffer.reset();
251 if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
252 if (DEBUG) Slog.v(TAG, "Storing global metadata key");
253 outputBufferStream.writeInt(Build.VERSION.SDK_INT);
254 outputBufferStream.writeUTF(Build.VERSION.INCREMENTAL);
255 writeEntity(data, GLOBAL_METADATA_KEY, outputBuffer.toByteArray());
257 if (DEBUG) Slog.v(TAG, "Global metadata key already stored");
258 // don't consider it to have been skipped/deleted
259 mExisting.remove(GLOBAL_METADATA_KEY);
262 // For each app we have on device, see if we've backed it up yet. If not,
263 // write its signature block to the output, keyed on the package name.
264 for (PackageInfo pkg : mAllPackages) {
265 String packName = pkg.packageName;
266 if (packName.equals(GLOBAL_METADATA_KEY)) {
267 // We've already handled the metadata key; skip it here
270 PackageInfo info = null;
272 info = mPackageManager.getPackageInfo(packName,
273 PackageManager.GET_SIGNATURES);
274 } catch (NameNotFoundException e) {
275 // Weird; we just found it, and now are told it doesn't exist.
276 // Treat it as having been removed from the device.
277 mExisting.add(packName);
281 if (mExisting.contains(packName)) {
282 // We have backed up this app before. Check whether the version
283 // of the backup matches the version of the current app; if they
284 // don't match, the app has been updated and we need to store its
285 // metadata again. In either case, take it out of mExisting so that
286 // we don't consider it deleted later.
287 mExisting.remove(packName);
288 if (info.versionCode == mStateVersions.get(packName).versionCode) {
293 if (info.signatures == null || info.signatures.length == 0)
295 Slog.w(TAG, "Not backing up package " + packName
296 + " since it appears to have no signatures.");
300 // We need to store this app's metadata
302 * Metadata for each package:
304 * int version -- [4] the package's versionCode
305 * byte[] signatures -- [len] flattened signature hash array of the package
308 // marshal the version code in a canonical form
309 outputBuffer.reset();
310 outputBufferStream.writeInt(info.versionCode);
311 writeSignatureHashArray(outputBufferStream,
312 hashSignatureArray(info.signatures));
315 Slog.v(TAG, "+ writing metadata for " + packName
316 + " version=" + info.versionCode
317 + " entityLen=" + outputBuffer.size());
320 // Now we can write the backup entity for this package
321 writeEntity(data, packName, outputBuffer.toByteArray());
325 // At this point, the only entries in 'existing' are apps that were
326 // mentioned in the saved state file, but appear to no longer be present
327 // on the device. Write a deletion entity for them.
328 for (String app : mExisting) {
329 if (DEBUG) Slog.v(TAG, "- removing metadata for deleted pkg " + app);
331 data.writeEntityHeader(app, -1);
332 } catch (IOException e) {
333 Slog.e(TAG, "Unable to write package deletions!");
337 } catch (IOException e) {
338 // Real error writing data
339 Slog.e(TAG, "Unable to write package backup data file!");
343 // Finally, write the new state blob -- just the list of all apps we handled
344 writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
347 private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
349 data.writeEntityHeader(key, bytes.length);
350 data.writeEntityData(bytes, bytes.length);
353 // "Restore" here is a misnomer. What we're really doing is reading back the
354 // set of app signatures associated with each backed-up app in this restore
355 // image. We'll use those later to determine what we can legitimately restore.
356 public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
358 List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
359 HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
360 if (DEBUG) Slog.v(TAG, "onRestore()");
361 int storedSystemVersion = -1;
363 while (data.readNextHeader()) {
364 String key = data.getKey();
365 int dataSize = data.getDataSize();
367 if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
369 // generic setup to parse any entity data
370 byte[] inputBytes = new byte[dataSize];
371 data.readEntityData(inputBytes, 0, dataSize);
372 ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
373 DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
375 if (key.equals(GLOBAL_METADATA_KEY)) {
376 int storedSdkVersion = inputBufferStream.readInt();
377 if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
378 if (storedSystemVersion > Build.VERSION.SDK_INT) {
379 // returning before setting the sig map means we rejected the restore set
380 Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
383 mStoredSdkVersion = storedSdkVersion;
384 mStoredIncrementalVersion = inputBufferStream.readUTF();
387 Slog.i(TAG, "Restore set version " + storedSystemVersion
388 + " is compatible with OS version " + Build.VERSION.SDK_INT
389 + " (" + mStoredIncrementalVersion + " vs "
390 + Build.VERSION.INCREMENTAL + ")");
392 } else if (key.equals(DEFAULT_HOME_KEY)) {
393 String cn = inputBufferStream.readUTF();
394 mRestoredHome = ComponentName.unflattenFromString(cn);
395 mRestoredHomeVersion = inputBufferStream.readLong();
396 mRestoredHomeInstaller = inputBufferStream.readUTF();
397 mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
399 Slog.i(TAG, " read preferred home app " + mRestoredHome
400 + " version=" + mRestoredHomeVersion
401 + " installer=" + mRestoredHomeInstaller
402 + " sig=" + mRestoredHomeSigHashes);
405 // it's a file metadata record
406 int versionCode = inputBufferStream.readInt();
407 ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
409 Slog.i(TAG, " read metadata for " + key
410 + " dataSize=" + dataSize
411 + " versionCode=" + versionCode + " sigs=" + sigs);
414 if (sigs == null || sigs.size() == 0) {
415 Slog.w(TAG, "Not restoring package " + key
416 + " since it appears to have no signatures.");
420 ApplicationInfo app = new ApplicationInfo();
421 app.packageName = key;
422 restoredApps.add(app);
423 sigMap.put(key, new Metadata(versionCode, sigs));
427 // On successful completion, cache the signature map for the Backup Manager to use
428 mRestoredSignatures = sigMap;
431 private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
436 ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
437 for (Signature s : sigs) {
438 hashes.add(BackupManagerService.hashSignature(s));
443 private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
445 // the number of entries in the array
446 out.writeInt(hashes.size());
448 // the hash arrays themselves as length + contents
449 for (byte[] buffer : hashes) {
450 out.writeInt(buffer.length);
455 private static ArrayList<byte[]> readSignatureHashArray(DataInputStream in) {
460 } catch (EOFException e) {
462 Slog.w(TAG, "Read empty signature block");
466 if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
470 Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
471 throw new IllegalStateException("Bad restore state");
474 // This could be a "legacy" block of actual signatures rather than their hashes.
475 // If this is the case, convert them now. We judge based on the payload size:
476 // if the blocks are all 256 bits (32 bytes) then we take them to be SHA-256 hashes;
477 // otherwise we take them to be Signatures.
478 boolean nonHashFound = false;
479 ArrayList<byte[]> sigs = new ArrayList<byte[]>(num);
480 for (int i = 0; i < num; i++) {
481 int len = in.readInt();
482 byte[] readHash = new byte[len];
491 ArrayList<byte[]> hashes =
492 new ArrayList<byte[]>(sigs.size());
493 for (int i = 0; i < sigs.size(); i++) {
494 Signature s = new Signature(sigs.get(i));
495 hashes.add(BackupManagerService.hashSignature(s));
501 } catch (IOException e) {
502 Slog.e(TAG, "Unable to read signatures");
507 // Util: parse out an existing state file into a usable structure
508 private void parseStateFile(ParcelFileDescriptor stateFile) {
510 mStateVersions.clear();
511 mStoredSdkVersion = 0;
512 mStoredIncrementalVersion = null;
513 mStoredHomeComponent = null;
514 mStoredHomeVersion = 0;
515 mStoredHomeSigHashes = null;
517 // The state file is just the list of app names we have stored signatures for
518 // with the exception of the metadata block, to which is also appended the
519 // version numbers corresponding with the last time we wrote this PM block.
520 // If they mismatch the current system, we'll re-store the metadata key.
521 FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
522 BufferedInputStream inbuffer = new BufferedInputStream(instream);
523 DataInputStream in = new DataInputStream(inbuffer);
526 boolean ignoreExisting = false;
527 String pkg = in.readUTF();
529 // Validate the state file version is sensical to us
530 if (pkg.equals(STATE_FILE_HEADER)) {
531 int stateVersion = in.readInt();
532 if (stateVersion > STATE_FILE_VERSION) {
533 Slog.w(TAG, "Unsupported state file version " + stateVersion
534 + ", redoing from start");
539 // This is an older version of the state file in which the lead element
540 // is not a STATE_FILE_VERSION string. If that's the case, we want to
541 // make sure to write our full backup dataset when given an opportunity.
542 // We trigger that by simply not marking the restored package metadata
543 // as known-to-exist-in-archive.
544 Slog.i(TAG, "Older version of saved state - rewriting");
545 ignoreExisting = true;
548 // First comes the preferred home app data, if any, headed by the DEFAULT_HOME_KEY tag
549 if (pkg.equals(DEFAULT_HOME_KEY)) {
550 // flattened component name, version, signature of the home app
551 mStoredHomeComponent = ComponentName.unflattenFromString(in.readUTF());
552 mStoredHomeVersion = in.readLong();
553 mStoredHomeSigHashes = readSignatureHashArray(in);
555 pkg = in.readUTF(); // set up for the next block of state
557 // else no preferred home app on the ancestral device - fall through to the rest
560 // After (possible) home app data comes the global metadata block
561 if (pkg.equals(GLOBAL_METADATA_KEY)) {
562 mStoredSdkVersion = in.readInt();
563 mStoredIncrementalVersion = in.readUTF();
564 if (!ignoreExisting) {
565 mExisting.add(GLOBAL_METADATA_KEY);
568 Slog.e(TAG, "No global metadata in state file!");
572 // The global metadata was last; now read all the apps
575 int versionCode = in.readInt();
577 if (!ignoreExisting) {
580 mStateVersions.put(pkg, new Metadata(versionCode, null));
582 } catch (EOFException eof) {
584 } catch (IOException e) {
585 // whoops, bad state file. abort.
586 Slog.e(TAG, "Unable to read Package Manager state file: " + e);
590 private ComponentName getPreferredHomeComponent() {
591 return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
594 // Util: write out our new backup state file
595 private void writeStateFile(List<PackageInfo> pkgs, ComponentName preferredHome,
596 long homeVersion, ArrayList<byte[]> homeSigHashes, ParcelFileDescriptor stateFile) {
597 FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
598 BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
599 DataOutputStream out = new DataOutputStream(outbuf);
601 // by the time we get here we know we've done all our backing up
603 // state file version header
604 out.writeUTF(STATE_FILE_HEADER);
605 out.writeInt(STATE_FILE_VERSION);
607 // If we remembered a preferred home app, record that
608 if (preferredHome != null) {
609 out.writeUTF(DEFAULT_HOME_KEY);
610 out.writeUTF(preferredHome.flattenToString());
611 out.writeLong(homeVersion);
612 writeSignatureHashArray(out, homeSigHashes);
615 // Conclude with the metadata block
616 out.writeUTF(GLOBAL_METADATA_KEY);
617 out.writeInt(Build.VERSION.SDK_INT);
618 out.writeUTF(Build.VERSION.INCREMENTAL);
620 // now write all the app names + versions
621 for (PackageInfo pkg : pkgs) {
622 out.writeUTF(pkg.packageName);
623 out.writeInt(pkg.versionCode);
627 } catch (IOException e) {
628 Slog.e(TAG, "Unable to write package manager state file!");