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

Commit 0b25273d authored by Michal Karpinski's avatar Michal Karpinski Committed by Android (Google) Code Review
Browse files

Merge "Ancestral restore versioning for PackageManagerBackupAgent"

parents 96e622cd a8a01bff
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;
        }
    }
}