Merge "Ancestral restore versioning for PackageManagerBackupAgent"
This commit is contained in:
committed by
Android (Google) Code Review
commit
0b25273da2
@@ -70,12 +70,28 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
private static final String DEFAULT_HOME_KEY = "@home@";
|
||||
|
||||
// Sentinel: start of state file, followed by a version number
|
||||
// Note that STATE_FILE_VERSION=2 is tied to UNDEFINED_ANCESTRAL_RECORD_VERSION=-1 *as well as*
|
||||
// ANCESTRAL_RECORD_VERSION=1 (introduced Android P).
|
||||
// Should the ANCESTRAL_RECORD_VERSION be bumped up in the future, STATE_FILE_VERSION will also
|
||||
// need bumping up, assuming more data needs saving to the state file.
|
||||
private static final String STATE_FILE_HEADER = "=state=";
|
||||
private static final int STATE_FILE_VERSION = 2;
|
||||
|
||||
// Current version of the saved ancestral-dataset file format
|
||||
// key under which we store the saved ancestral-dataset format (starting from Android P)
|
||||
// IMPORTANT: this key needs to come first in the restore data stream (to find out
|
||||
// whether this version of Android knows how to restore the incoming data set), so it needs
|
||||
// to be always the first one in alphabetical order of all the keys
|
||||
private static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@";
|
||||
|
||||
// Current version of the saved ancestral-dataset format
|
||||
// Note that this constant was not used until Android P, and started being used
|
||||
// to version @pm@ data for forwards-compatibility.
|
||||
private static final int ANCESTRAL_RECORD_VERSION = 1;
|
||||
|
||||
// Undefined version of the saved ancestral-dataset file format means that the restore data
|
||||
// is coming from pre-Android P device.
|
||||
private static final int UNDEFINED_ANCESTRAL_RECORD_VERSION = -1;
|
||||
|
||||
private List<PackageInfo> mAllPackages;
|
||||
private PackageManager mPackageManager;
|
||||
// version & signature info of each app in a restore set
|
||||
@@ -175,9 +191,8 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
// additional involvement by the transport to obtain.
|
||||
return mRestoredSignatures.keySet();
|
||||
}
|
||||
|
||||
// The backed up data is the signature block for each app, keyed by
|
||||
// the package name.
|
||||
|
||||
// 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) {
|
||||
if (DEBUG) Slog.v(TAG, "onBackup()");
|
||||
@@ -196,6 +211,22 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
mExisting.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
* Ancestral record version:
|
||||
*
|
||||
* int ancestralRecordVersion -- the version of the format in which this backup set is
|
||||
* produced
|
||||
*/
|
||||
try {
|
||||
if (DEBUG) Slog.v(TAG, "Storing ancestral record version key");
|
||||
outputBufferStream.writeInt(ANCESTRAL_RECORD_VERSION);
|
||||
writeEntity(data, ANCESTRAL_RECORD_KEY, outputBuffer.toByteArray());
|
||||
} catch (IOException e) {
|
||||
// Real error writing data
|
||||
Slog.e(TAG, "Unable to write package backup data file!");
|
||||
return;
|
||||
}
|
||||
|
||||
long homeVersion = 0;
|
||||
ArrayList<byte[]> homeSigHashes = null;
|
||||
PackageInfo homeInfo = null;
|
||||
@@ -230,6 +261,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
Slog.i(TAG, "Home preference changed; backing up new state " + home);
|
||||
}
|
||||
if (home != null) {
|
||||
outputBuffer.reset();
|
||||
outputBufferStream.writeUTF(home.flattenToString());
|
||||
outputBufferStream.writeLong(homeVersion);
|
||||
outputBufferStream.writeUTF(homeInstaller != null ? homeInstaller : "" );
|
||||
@@ -244,8 +276,8 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
* 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.
|
||||
* that produced this backup set. Before Android P it was used to
|
||||
* reject backups from later OSes onto earlier ones.
|
||||
* String incremental -- the incremental release name of the OS stored in
|
||||
* the backup set.
|
||||
*/
|
||||
@@ -354,7 +386,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
// Finally, write the new state blob -- just the list of all apps we handled
|
||||
writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
|
||||
}
|
||||
|
||||
|
||||
private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
|
||||
throws IOException {
|
||||
data.writeEntityHeader(key, bytes.length);
|
||||
@@ -366,83 +398,57 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
// image. We'll use those later to determine what we can legitimately restore.
|
||||
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
|
||||
throws IOException {
|
||||
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()) {
|
||||
// we expect the ANCESTRAL_RECORD_KEY ("@ancestral_record@") to always come first in the
|
||||
// restore set - based on that value we use different mechanisms to consume the data;
|
||||
// if the ANCESTRAL_RECORD_KEY is missing in the restore set, it means that the data is
|
||||
// is coming from a pre-Android P device, and we consume the header data in the legacy way
|
||||
// TODO: add a CTS test to verify that backups of PMBA generated on Android P+ always
|
||||
// contain the ANCESTRAL_RECORD_KEY, and it's always the first key
|
||||
int ancestralRecordVersion = getAncestralRecordVersionValue(data);
|
||||
|
||||
RestoreDataConsumer consumer = getRestoreDataConsumer(ancestralRecordVersion);
|
||||
if (consumer == null) {
|
||||
Slog.w(TAG, "Ancestral restore set version is unknown"
|
||||
+ " to this Android version; not restoring");
|
||||
return;
|
||||
} else {
|
||||
consumer.consumeRestoreData(data);
|
||||
}
|
||||
}
|
||||
|
||||
private int getAncestralRecordVersionValue(BackupDataInput data) throws IOException {
|
||||
int ancestralRecordVersionValue = UNDEFINED_ANCESTRAL_RECORD_VERSION;
|
||||
if (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 (ANCESTRAL_RECORD_KEY.equals(key)) {
|
||||
// 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 versionCodeInt = inputBufferStream.readInt();
|
||||
long versionCode;
|
||||
if (versionCodeInt == Integer.MIN_VALUE) {
|
||||
versionCode = inputBufferStream.readLong();
|
||||
} else {
|
||||
versionCode = versionCodeInt;
|
||||
}
|
||||
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));
|
||||
ancestralRecordVersionValue = inputBufferStream.readInt();
|
||||
}
|
||||
}
|
||||
return ancestralRecordVersionValue;
|
||||
}
|
||||
|
||||
// On successful completion, cache the signature map for the Backup Manager to use
|
||||
mRestoredSignatures = sigMap;
|
||||
private RestoreDataConsumer getRestoreDataConsumer(int ancestralRecordVersion) {
|
||||
switch (ancestralRecordVersion) {
|
||||
case UNDEFINED_ANCESTRAL_RECORD_VERSION:
|
||||
return new LegacyRestoreDataConsumer();
|
||||
case 1:
|
||||
return new AncestralVersion1RestoreDataConsumer();
|
||||
default:
|
||||
Slog.e(TAG, "Unrecognized ANCESTRAL_RECORD_VERSION: " + ancestralRecordVersion);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
|
||||
@@ -639,4 +645,173 @@ public class PackageManagerBackupAgent extends BackupAgent {
|
||||
Slog.e(TAG, "Unable to write package manager state file!");
|
||||
}
|
||||
}
|
||||
|
||||
interface RestoreDataConsumer {
|
||||
void consumeRestoreData(BackupDataInput data) throws IOException;
|
||||
}
|
||||
|
||||
private class LegacyRestoreDataConsumer implements RestoreDataConsumer {
|
||||
|
||||
public void consumeRestoreData(BackupDataInput data) throws IOException {
|
||||
List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
|
||||
HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
|
||||
int storedSystemVersion = -1;
|
||||
|
||||
if (DEBUG) Slog.i(TAG, "Using LegacyRestoreDataConsumer");
|
||||
// we already have the first header read and "cached", since ANCESTRAL_RECORD_KEY
|
||||
// was missing
|
||||
while (true) {
|
||||
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);
|
||||
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 versionCodeInt = inputBufferStream.readInt();
|
||||
long versionCode;
|
||||
if (versionCodeInt == Integer.MIN_VALUE) {
|
||||
versionCode = inputBufferStream.readLong();
|
||||
} else {
|
||||
versionCode = versionCodeInt;
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
boolean readNextHeader = data.readNextHeader();
|
||||
if (!readNextHeader) {
|
||||
if (DEBUG) Slog.v(TAG, "LegacyRestoreDataConsumer:"
|
||||
+ " we're done reading all the headers");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// On successful completion, cache the signature map for the Backup Manager to use
|
||||
mRestoredSignatures = sigMap;
|
||||
}
|
||||
}
|
||||
|
||||
private class AncestralVersion1RestoreDataConsumer implements RestoreDataConsumer {
|
||||
|
||||
public void consumeRestoreData(BackupDataInput data) throws IOException {
|
||||
List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
|
||||
HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
|
||||
int storedSystemVersion = -1;
|
||||
|
||||
if (DEBUG) Slog.i(TAG, "Using AncestralVersion1RestoreDataConsumer");
|
||||
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);
|
||||
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 versionCodeInt = inputBufferStream.readInt();
|
||||
long versionCode;
|
||||
if (versionCodeInt == Integer.MIN_VALUE) {
|
||||
versionCode = inputBufferStream.readLong();
|
||||
} else {
|
||||
versionCode = versionCodeInt;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user