Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8a01bff authored by Michal Karpinski's avatar Michal Karpinski
Browse files

Ancestral restore versioning for PackageManagerBackupAgent

Introduced usage of ANCESTRAL_RECORD_KEY, based on which value
the restore set can be parsed in multiple ways by different
RestoreDataConsumers.

Test: manual - D2D and cloud for different scenarios (O->P, P->P, Q->P)
Test: O->P and P->P will be covered by our automatic e2e tests
Test: a CTS test will be added to verify that backups of PMBA
      generated on Android P+ always contain the ANCESTRAL_RECORD_KEY,
      and it's always the first key
Bug: 64988620
Change-Id: I4634f403da4aefdeb09dc5ca621c4ac349583251
parent 7efb442a
Loading
Loading
Loading
Loading
+248 −73
Original line number Diff line number Diff line
@@ -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
@@ -176,8 +192,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
        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.
             */
@@ -366,85 +398,59 @@ 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);

            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 + ")");
                ancestralRecordVersionValue = inputBufferStream.readInt();
            }
            } 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;
        return ancestralRecordVersionValue;
    }

                ApplicationInfo app = new ApplicationInfo();
                app.packageName = key;
                restoredApps.add(app);
                sigMap.put(key, new Metadata(versionCode, sigs));
    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;
        }
    }

        // On successful completion, cache the signature map for the Backup Manager to use
        mRestoredSignatures = sigMap;
    }

    private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
            throws IOException {
        // the number of entries in the array
@@ -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;
        }
    }
}