PackageManagerBackupAgentpublic class PackageManagerBackupAgent extends android.app.backup.BackupAgent 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. |
Fields Summary |
---|
private static final String | TAG | private static final boolean | DEBUG | private static final String | GLOBAL_METADATA_KEY | private static final String | DEFAULT_HOME_KEY | private static final String | STATE_FILE_HEADER | private static final int | STATE_FILE_VERSION | private static final int | ANCESTRAL_RECORD_VERSION | private List | mAllPackages | private android.content.pm.PackageManager | mPackageManager | private HashMap | mRestoredSignatures | private HashMap | mStateVersions | private final HashSet | mExisting | private int | mStoredSdkVersion | private String | mStoredIncrementalVersion | private android.content.ComponentName | mStoredHomeComponent | private long | mStoredHomeVersion | private ArrayList | mStoredHomeSigHashes | private boolean | mHasMetadata | private android.content.ComponentName | mRestoredHome | private long | mRestoredHomeVersion | private String | mRestoredHomeInstaller | private ArrayList | mRestoredHomeSigHashes |
Methods Summary |
---|
public void | evaluateStorablePackages()
mAllPackages = getStorableApplications(mPackageManager);
| private android.content.ComponentName | getPreferredHomeComponent()
return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
| public com.android.server.backup.PackageManagerBackupAgent$Metadata | getRestoredMetadata(java.lang.String packageName)
if (mRestoredSignatures == null) {
Slog.w(TAG, "getRestoredMetadata() before metadata read!");
return null;
}
return mRestoredSignatures.get(packageName);
| public java.util.Set | getRestoredPackages()
if (mRestoredSignatures == null) {
Slog.w(TAG, "getRestoredPackages() before metadata read!");
return null;
}
// This is technically the set of packages on the originating handset
// that had backup agents at all, not limited to the set of packages
// that had actually contributed a restore dataset, but it's a
// close enough approximation for our purposes and does not require any
// additional involvement by the transport to obtain.
return mRestoredSignatures.keySet();
| public static java.util.List | getStorableApplications(android.content.pm.PackageManager pm)
List<PackageInfo> pkgs;
pkgs = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
int N = pkgs.size();
for (int a = N-1; a >= 0; a--) {
PackageInfo pkg = pkgs.get(a);
if (!BackupManagerService.appIsEligibleForBackup(pkg.applicationInfo)) {
pkgs.remove(a);
}
}
return pkgs;
| public boolean | hasMetadata()
return mHasMetadata;
| private static java.util.ArrayList | hashSignatureArray(android.content.pm.Signature[] sigs)
if (sigs == null) {
return null;
}
ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
for (Signature s : sigs) {
hashes.add(BackupManagerService.hashSignature(s));
}
return hashes;
| private void | init(android.content.pm.PackageManager packageMgr, java.util.List packages)
mPackageManager = packageMgr;
mAllPackages = packages;
mRestoredSignatures = null;
mHasMetadata = false;
mStoredSdkVersion = Build.VERSION.SDK_INT;
mStoredIncrementalVersion = Build.VERSION.INCREMENTAL;
| public void | onBackup(android.os.ParcelFileDescriptor oldState, android.app.backup.BackupDataOutput data, android.os.ParcelFileDescriptor newState)
if (DEBUG) Slog.v(TAG, "onBackup()");
ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); // we'll reuse these
DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer);
parseStateFile(oldState);
// If the stored version string differs, we need to re-backup all
// of the metadata. We force this by removing everything from the
// "already backed up" map built by parseStateFile().
if (mStoredIncrementalVersion == null
|| !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
+ Build.VERSION.INCREMENTAL + " - rewriting");
mExisting.clear();
}
long homeVersion = 0;
ArrayList<byte[]> homeSigHashes = null;
PackageInfo homeInfo = null;
String homeInstaller = null;
ComponentName home = getPreferredHomeComponent();
if (home != null) {
try {
homeInfo = mPackageManager.getPackageInfo(home.getPackageName(),
PackageManager.GET_SIGNATURES);
homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
homeVersion = homeInfo.versionCode;
homeSigHashes = hashSignatureArray(homeInfo.signatures);
} catch (NameNotFoundException e) {
Slog.w(TAG, "Can't access preferred home info");
// proceed as though there were no preferred home set
home = null;
}
}
try {
// We need to push a new preferred-home-app record if:
// 1. the version of the home app has changed since our last backup;
// 2. the home app [or absence] we now use differs from the prior state,
// OR 3. it looks like we use the same home app + version as before, but
// the signatures don't match so we treat them as different apps.
final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
|| !Objects.equals(home, mStoredHomeComponent)
|| (home != null
&& !BackupManagerService.signaturesMatch(mStoredHomeSigHashes, homeInfo));
if (needHomeBackup) {
if (DEBUG) {
Slog.i(TAG, "Home preference changed; backing up new state " + home);
}
if (home != null) {
outputBufferStream.writeUTF(home.flattenToString());
outputBufferStream.writeLong(homeVersion);
outputBufferStream.writeUTF(homeInstaller != null ? homeInstaller : "" );
writeSignatureHashArray(outputBufferStream, homeSigHashes);
writeEntity(data, DEFAULT_HOME_KEY, outputBuffer.toByteArray());
} else {
data.writeEntityHeader(DEFAULT_HOME_KEY, -1);
}
}
/*
* Global metadata:
*
* int SDKversion -- the SDK version of the OS itself on the device
* that produced this backup set. Used to reject
* backups from later OSes onto earlier ones.
* String incremental -- the incremental release name of the OS stored in
* the backup set.
*/
outputBuffer.reset();
if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
if (DEBUG) Slog.v(TAG, "Storing global metadata key");
outputBufferStream.writeInt(Build.VERSION.SDK_INT);
outputBufferStream.writeUTF(Build.VERSION.INCREMENTAL);
writeEntity(data, GLOBAL_METADATA_KEY, outputBuffer.toByteArray());
} else {
if (DEBUG) Slog.v(TAG, "Global metadata key already stored");
// don't consider it to have been skipped/deleted
mExisting.remove(GLOBAL_METADATA_KEY);
}
// 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 (PackageInfo pkg : mAllPackages) {
String packName = pkg.packageName;
if (packName.equals(GLOBAL_METADATA_KEY)) {
// We've already handled the metadata key; skip it here
continue;
} else {
PackageInfo info = null;
try {
info = mPackageManager.getPackageInfo(packName,
PackageManager.GET_SIGNATURES);
} 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.
mExisting.add(packName);
continue;
}
if (mExisting.contains(packName)) {
// We have backed up this app before. Check whether the version
// of the backup matches the version of the current app; if they
// don't match, the app has been updated and we need to store its
// metadata again. In either case, take it out of mExisting so that
// we don't consider it deleted later.
mExisting.remove(packName);
if (info.versionCode == mStateVersions.get(packName).versionCode) {
continue;
}
}
if (info.signatures == null || info.signatures.length == 0)
{
Slog.w(TAG, "Not backing up package " + packName
+ " since it appears to have no signatures.");
continue;
}
// We need to store this app's metadata
/*
* Metadata for each package:
*
* int version -- [4] the package's versionCode
* byte[] signatures -- [len] flattened signature hash array of the package
*/
// marshal the version code in a canonical form
outputBuffer.reset();
outputBufferStream.writeInt(info.versionCode);
writeSignatureHashArray(outputBufferStream,
hashSignatureArray(info.signatures));
if (DEBUG) {
Slog.v(TAG, "+ writing metadata for " + packName
+ " version=" + info.versionCode
+ " entityLen=" + outputBuffer.size());
}
// Now we can write the backup entity for this package
writeEntity(data, packName, outputBuffer.toByteArray());
}
}
// 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 : mExisting) {
if (DEBUG) Slog.v(TAG, "- removing metadata for deleted pkg " + app);
try {
data.writeEntityHeader(app, -1);
} catch (IOException e) {
Slog.e(TAG, "Unable to write package deletions!");
return;
}
}
} catch (IOException e) {
// Real error writing data
Slog.e(TAG, "Unable to write package backup data file!");
return;
}
// Finally, write the new state blob -- just the list of all apps we handled
writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
| public void | onRestore(android.app.backup.BackupDataInput data, int appVersionCode, android.os.ParcelFileDescriptor newState)
List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
if (DEBUG) Slog.v(TAG, "onRestore()");
int storedSystemVersion = -1;
while (data.readNextHeader()) {
String key = data.getKey();
int dataSize = data.getDataSize();
if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize);
// generic setup to parse any entity data
byte[] inputBytes = new byte[dataSize];
data.readEntityData(inputBytes, 0, dataSize);
ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
if (key.equals(GLOBAL_METADATA_KEY)) {
int storedSdkVersion = inputBufferStream.readInt();
if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion);
if (storedSystemVersion > Build.VERSION.SDK_INT) {
// returning before setting the sig map means we rejected the restore set
Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
return;
}
mStoredSdkVersion = storedSdkVersion;
mStoredIncrementalVersion = inputBufferStream.readUTF();
mHasMetadata = true;
if (DEBUG) {
Slog.i(TAG, "Restore set version " + storedSystemVersion
+ " is compatible with OS version " + Build.VERSION.SDK_INT
+ " (" + mStoredIncrementalVersion + " vs "
+ Build.VERSION.INCREMENTAL + ")");
}
} else if (key.equals(DEFAULT_HOME_KEY)) {
String cn = inputBufferStream.readUTF();
mRestoredHome = ComponentName.unflattenFromString(cn);
mRestoredHomeVersion = inputBufferStream.readLong();
mRestoredHomeInstaller = inputBufferStream.readUTF();
mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
Slog.i(TAG, " read preferred home app " + mRestoredHome
+ " version=" + mRestoredHomeVersion
+ " installer=" + mRestoredHomeInstaller
+ " sig=" + mRestoredHomeSigHashes);
}
} else {
// it's a file metadata record
int versionCode = inputBufferStream.readInt();
ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
Slog.i(TAG, " read metadata for " + key
+ " dataSize=" + dataSize
+ " versionCode=" + versionCode + " sigs=" + sigs);
}
if (sigs == null || sigs.size() == 0) {
Slog.w(TAG, "Not restoring package " + key
+ " since it appears to have no signatures.");
continue;
}
ApplicationInfo app = new ApplicationInfo();
app.packageName = key;
restoredApps.add(app);
sigMap.put(key, new Metadata(versionCode, sigs));
}
}
// On successful completion, cache the signature map for the Backup Manager to use
mRestoredSignatures = sigMap;
| private void | parseStateFile(android.os.ParcelFileDescriptor stateFile)
mExisting.clear();
mStateVersions.clear();
mStoredSdkVersion = 0;
mStoredIncrementalVersion = null;
mStoredHomeComponent = null;
mStoredHomeVersion = 0;
mStoredHomeSigHashes = null;
// The state file is just the list of app names we have stored signatures for
// with the exception of the metadata block, to which is also appended the
// version numbers corresponding with the last time we wrote this PM block.
// If they mismatch the current system, we'll re-store the metadata key.
FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
BufferedInputStream inbuffer = new BufferedInputStream(instream);
DataInputStream in = new DataInputStream(inbuffer);
try {
boolean ignoreExisting = false;
String pkg = in.readUTF();
// Validate the state file version is sensical to us
if (pkg.equals(STATE_FILE_HEADER)) {
int stateVersion = in.readInt();
if (stateVersion > STATE_FILE_VERSION) {
Slog.w(TAG, "Unsupported state file version " + stateVersion
+ ", redoing from start");
return;
}
pkg = in.readUTF();
} else {
// This is an older version of the state file in which the lead element
// is not a STATE_FILE_VERSION string. If that's the case, we want to
// make sure to write our full backup dataset when given an opportunity.
// We trigger that by simply not marking the restored package metadata
// as known-to-exist-in-archive.
Slog.i(TAG, "Older version of saved state - rewriting");
ignoreExisting = true;
}
// First comes the preferred home app data, if any, headed by the DEFAULT_HOME_KEY tag
if (pkg.equals(DEFAULT_HOME_KEY)) {
// flattened component name, version, signature of the home app
mStoredHomeComponent = ComponentName.unflattenFromString(in.readUTF());
mStoredHomeVersion = in.readLong();
mStoredHomeSigHashes = readSignatureHashArray(in);
pkg = in.readUTF(); // set up for the next block of state
} else {
// else no preferred home app on the ancestral device - fall through to the rest
}
// After (possible) home app data comes the global metadata block
if (pkg.equals(GLOBAL_METADATA_KEY)) {
mStoredSdkVersion = in.readInt();
mStoredIncrementalVersion = in.readUTF();
if (!ignoreExisting) {
mExisting.add(GLOBAL_METADATA_KEY);
}
} else {
Slog.e(TAG, "No global metadata in state file!");
return;
}
// The global metadata was last; now read all the apps
while (true) {
pkg = in.readUTF();
int versionCode = in.readInt();
if (!ignoreExisting) {
mExisting.add(pkg);
}
mStateVersions.put(pkg, new Metadata(versionCode, null));
}
} catch (EOFException eof) {
// safe; we're done
} catch (IOException e) {
// whoops, bad state file. abort.
Slog.e(TAG, "Unable to read Package Manager state file: " + e);
}
| private static java.util.ArrayList | readSignatureHashArray(java.io.DataInputStream in)
try {
int num;
try {
num = in.readInt();
} catch (EOFException e) {
// clean termination
Slog.w(TAG, "Read empty signature block");
return null;
}
if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
// Sensical?
if (num > 20) {
Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
throw new IllegalStateException("Bad restore state");
}
// This could be a "legacy" block of actual signatures rather than their hashes.
// If this is the case, convert them now. We judge based on the payload size:
// if the blocks are all 256 bits (32 bytes) then we take them to be SHA-256 hashes;
// otherwise we take them to be Signatures.
boolean nonHashFound = false;
ArrayList<byte[]> sigs = new ArrayList<byte[]>(num);
for (int i = 0; i < num; i++) {
int len = in.readInt();
byte[] readHash = new byte[len];
in.read(readHash);
sigs.add(readHash);
if (len != 32) {
nonHashFound = true;
}
}
if (nonHashFound) {
ArrayList<byte[]> hashes =
new ArrayList<byte[]>(sigs.size());
for (int i = 0; i < sigs.size(); i++) {
Signature s = new Signature(sigs.get(i));
hashes.add(BackupManagerService.hashSignature(s));
}
sigs = hashes;
}
return sigs;
} catch (IOException e) {
Slog.e(TAG, "Unable to read signatures");
return null;
}
| private static void | writeEntity(android.app.backup.BackupDataOutput data, java.lang.String key, byte[] bytes)
data.writeEntityHeader(key, bytes.length);
data.writeEntityData(bytes, bytes.length);
| private static void | writeSignatureHashArray(java.io.DataOutputStream out, java.util.ArrayList hashes)
// the number of entries in the array
out.writeInt(hashes.size());
// the hash arrays themselves as length + contents
for (byte[] buffer : hashes) {
out.writeInt(buffer.length);
out.write(buffer);
}
| private void | writeStateFile(java.util.List pkgs, android.content.ComponentName preferredHome, long homeVersion, java.util.ArrayList homeSigHashes, android.os.ParcelFileDescriptor stateFile)
FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
DataOutputStream out = new DataOutputStream(outbuf);
// by the time we get here we know we've done all our backing up
try {
// state file version header
out.writeUTF(STATE_FILE_HEADER);
out.writeInt(STATE_FILE_VERSION);
// If we remembered a preferred home app, record that
if (preferredHome != null) {
out.writeUTF(DEFAULT_HOME_KEY);
out.writeUTF(preferredHome.flattenToString());
out.writeLong(homeVersion);
writeSignatureHashArray(out, homeSigHashes);
}
// Conclude with the metadata block
out.writeUTF(GLOBAL_METADATA_KEY);
out.writeInt(Build.VERSION.SDK_INT);
out.writeUTF(Build.VERSION.INCREMENTAL);
// now write all the app names + versions
for (PackageInfo pkg : pkgs) {
out.writeUTF(pkg.packageName);
out.writeInt(pkg.versionCode);
}
out.flush();
} catch (IOException e) {
Slog.e(TAG, "Unable to write package manager state file!");
}
|
|