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

Commit 72d19aa5 authored by Christopher Tate's avatar Christopher Tate
Browse files

Tighten up the metadata backup logic

We now store the app version codes and and global OS incremental version name in
the PM backup state and the actual backup record.  We then use that information
to trigger a re-backup of the metadata if the OS revision changes in any way, or
to back up single apps' metadata if we notice that they've been upgraded.
parent 91c91b74
Loading
Loading
Loading
Loading
+94 −42
Original line number Original line Diff line number Diff line
@@ -59,7 +59,14 @@ public class PackageManagerBackupAgent extends BackupAgent {


    private List<PackageInfo> mAllPackages;
    private List<PackageInfo> mAllPackages;
    private PackageManager mPackageManager;
    private PackageManager mPackageManager;
    // version & signature info of each app in a restore set
    private HashMap<String, Metadata> mRestoredSignatures;
    private HashMap<String, Metadata> mRestoredSignatures;
    // The version info of each backed-up app as read from the state file
    private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();

    private final HashSet<String> mExisting = new HashSet<String>();
    private int mStoredSdkVersion;
    private String mStoredIncrementalVersion;


    public class Metadata {
    public class Metadata {
        public int versionCode;
        public int versionCode;
@@ -96,24 +103,39 @@ public class PackageManagerBackupAgent extends BackupAgent {


        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();  // we'll reuse these
        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();  // we'll reuse these
        DataOutputStream outWriter = new DataOutputStream(bufStream);
        DataOutputStream outWriter = new DataOutputStream(bufStream);
        HashSet<String> existing = parseStateFile(oldState);
        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)) {
            Log.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
                    + Build.VERSION.INCREMENTAL + " - rewriting");
            mExisting.clear();
        }


        try {
        try {
            /*
            /*
             * Global metadata:
             * Global metadata:
             *
             *
             * int version -- the SDK version of the OS itself on the device
             * int SDKversion -- the SDK version of the OS itself on the device
             *                   that produced this backup set.  Used to reject
             *                   that produced this backup set.  Used to reject
             *                   backups from later OSes onto earlier ones.
             *                   backups from later OSes onto earlier ones.
             * String incremental -- the incremental release name of the OS stored in
             *                       the backup set.
             */
             */
            if (!existing.contains(GLOBAL_METADATA_KEY)) {
            if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
                if (DEBUG) Log.v(TAG, "Storing global metadata key");
                if (DEBUG) Log.v(TAG, "Storing global metadata key");
                outWriter.writeInt(Build.VERSION.SDK_INT);
                outWriter.writeInt(Build.VERSION.SDK_INT);
                outWriter.writeUTF(Build.VERSION.INCREMENTAL);
                byte[] metadata = bufStream.toByteArray();
                byte[] metadata = bufStream.toByteArray();
                data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length);
                data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length);
                data.writeEntityData(metadata, metadata.length);
                data.writeEntityData(metadata, metadata.length);
            } else {
            } else {
                if (DEBUG) Log.v(TAG, "Global metadata key already stored");
                if (DEBUG) Log.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,
            // For each app we have on device, see if we've backed it up yet.  If not,
@@ -123,11 +145,36 @@ public class PackageManagerBackupAgent extends BackupAgent {
                if (packName.equals(GLOBAL_METADATA_KEY)) {
                if (packName.equals(GLOBAL_METADATA_KEY)) {
                    // We've already handled the metadata key; skip it here
                    // We've already handled the metadata key; skip it here
                    continue;
                    continue;
                } else if (!existing.contains(packName)) {
                } else {
                    // We haven't stored this app's signatures yet, so we do that now
                    PackageInfo info = null;
                    try {
                    try {
                        PackageInfo info = mPackageManager.getPackageInfo(packName,
                        info = mPackageManager.getPackageInfo(packName,
                                PackageManager.GET_SIGNATURES);
                                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;
                    }

                    boolean doBackup = false;
                    if (!mExisting.contains(packName)) {
                        // We haven't backed up this app before
                        doBackup = true;
                    } else {
                        // We *have* backed this one up 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.
                        if (info.versionCode != mStateVersions.get(packName).versionCode) {
                            doBackup = true;
                        }
                        mExisting.remove(packName);
                    }

                    if (doBackup) {
                        // We need to store this app's metadata
                        /*
                        /*
                         * Metadata for each package:
                         * Metadata for each package:
                         *
                         *
@@ -135,7 +182,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
                         * byte[] signatures -- [len] flattened Signature[] of the package
                         * byte[] signatures -- [len] flattened Signature[] of the package
                         */
                         */


                        // marshall the version code in a canonical form
                        // marshal the version code in a canonical form
                        bufStream.reset();
                        bufStream.reset();
                        outWriter.writeInt(info.versionCode);
                        outWriter.writeInt(info.versionCode);
                        byte[] versionBuf = bufStream.toByteArray();
                        byte[] versionBuf = bufStream.toByteArray();
@@ -153,18 +200,6 @@ public class PackageManagerBackupAgent extends BackupAgent {
                        data.writeEntityHeader(packName, versionBuf.length + sigs.length);
                        data.writeEntityHeader(packName, versionBuf.length + sigs.length);
                        data.writeEntityData(versionBuf, versionBuf.length);
                        data.writeEntityData(versionBuf, versionBuf.length);
                        data.writeEntityData(sigs, sigs.length);
                        data.writeEntityData(sigs, sigs.length);
                    } 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.
                        existing.add(packName);
                    }
                } else {
                    // We've already backed up this app.  Remove it from the set so
                    // we can tell at the end what has disappeared from the device.
                    // !!! TODO: take out the debugging message
                    if (DEBUG) Log.v(TAG, "= already backed up metadata for " + packName);
                    if (!existing.remove(packName)) {
                        Log.d(TAG, "*** failed to remove " + packName + " from package set!");
                    }
                    }
                }
                }
            }
            }
@@ -172,7 +207,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
            // At this point, the only entries in 'existing' are apps that were
            // 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
            // mentioned in the saved state file, but appear to no longer be present
            // on the device.  Write a deletion entity for them.
            // on the device.  Write a deletion entity for them.
            for (String app : existing) {
            for (String app : mExisting) {
                // !!! TODO: take out this msg
                // !!! TODO: take out this msg
                if (DEBUG) Log.v(TAG, "- removing metadata for deleted pkg " + app);
                if (DEBUG) Log.v(TAG, "- removing metadata for deleted pkg " + app);
                try {
                try {
@@ -215,17 +250,21 @@ public class PackageManagerBackupAgent extends BackupAgent {
            DataInputStream in = new DataInputStream(baStream);
            DataInputStream in = new DataInputStream(baStream);


            if (key.equals(GLOBAL_METADATA_KEY)) {
            if (key.equals(GLOBAL_METADATA_KEY)) {
                storedSystemVersion = in.readInt();
                int storedSdkVersion = in.readInt();
                if (DEBUG) Log.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
                if (DEBUG) Log.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
                if (storedSystemVersion > Build.VERSION.SDK_INT) {
                if (storedSystemVersion > Build.VERSION.SDK_INT) {
                    // returning before setting the sig map means we rejected the restore set
                    // returning before setting the sig map means we rejected the restore set
                    Log.w(TAG, "Restore set was from a later version of Android; not restoring");
                    Log.w(TAG, "Restore set was from a later version of Android; not restoring");
                    return;
                    return;
                }
                }
                mStoredSdkVersion = storedSdkVersion;
                mStoredIncrementalVersion = in.readUTF();
                // !!! TODO: remove this debugging output
                // !!! TODO: remove this debugging output
                if (DEBUG) {
                if (DEBUG) {
                    Log.i(TAG, "Restore set version " + storedSystemVersion
                    Log.i(TAG, "Restore set version " + storedSystemVersion
                            + " is compatible with OS version " + Build.VERSION.SDK_INT);
                            + " is compatible with OS version " + Build.VERSION.SDK_INT
                            + " (" + mStoredIncrementalVersion + " vs "
                            + Build.VERSION.INCREMENTAL + ")");
                }
                }
            } else {
            } else {
                // it's a file metadata record
                // it's a file metadata record
@@ -302,31 +341,45 @@ public class PackageManagerBackupAgent extends BackupAgent {
    }
    }


    // Util: parse out an existing state file into a usable structure
    // Util: parse out an existing state file into a usable structure
    private HashSet<String> parseStateFile(ParcelFileDescriptor stateFile) {
    private void parseStateFile(ParcelFileDescriptor stateFile) {
        HashSet<String> set = new HashSet<String>();
        mExisting.clear();
        mStateVersions.clear();
        mStoredSdkVersion = 0;
        mStoredIncrementalVersion = null;

        // The state file is just the list of app names we have stored signatures for
        // 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());
        FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
        DataInputStream in = new DataInputStream(instream);
        DataInputStream in = new DataInputStream(instream);


        int bufSize = 256;
        int bufSize = 256;
        byte[] buf = new byte[bufSize];
        byte[] buf = new byte[bufSize];
        try {
        try {
            int nameSize = in.readInt();
            String pkg = in.readUTF();
            if (bufSize < nameSize) {
            if (pkg.equals(GLOBAL_METADATA_KEY)) {
                bufSize = nameSize + 32;
                mStoredSdkVersion = in.readInt();
                buf = new byte[bufSize];
                mStoredIncrementalVersion = in.readUTF();
            }
                mExisting.add(GLOBAL_METADATA_KEY);
            in.read(buf, 0, nameSize);
            } else {
            String pkg = new String(buf, 0, nameSize);
                Log.e(TAG, "No global metadata in state file!");
            set.add(pkg);
                return;
            }

            // The global metadata was first; now read all the apps
            while (true) {
                pkg = in.readUTF();
                int versionCode = in.readInt();
                mExisting.add(pkg);
                mStateVersions.put(pkg, new Metadata(versionCode, null));
            }
        } catch (EOFException eof) {
        } catch (EOFException eof) {
            // safe; we're done
            // safe; we're done
        } catch (IOException e) {
        } catch (IOException e) {
            // whoops, bad state file.  abort.
            // whoops, bad state file.  abort.
            Log.e(TAG, "Unable to read Package Manager state file");
            Log.e(TAG, "Unable to read Package Manager state file: " + e);
            return null;
        }
        }
        return set;
    }
    }


    // Util: write out our new backup state file
    // Util: write out our new backup state file
@@ -336,15 +389,14 @@ public class PackageManagerBackupAgent extends BackupAgent {


        try {
        try {
            // by the time we get here we know we've stored the global metadata record
            // by the time we get here we know we've stored the global metadata record
            byte[] metaNameBuf = GLOBAL_METADATA_KEY.getBytes();
            out.writeUTF(GLOBAL_METADATA_KEY);
            out.writeInt(metaNameBuf.length);
            out.writeInt(Build.VERSION.SDK_INT);
            out.write(metaNameBuf);
            out.writeUTF(Build.VERSION.INCREMENTAL);


            // now write all the app names too
            // now write all the app names too
            for (PackageInfo pkg : pkgs) {
            for (PackageInfo pkg : pkgs) {
                byte[] pkgNameBuf = pkg.packageName.getBytes();
                out.writeUTF(pkg.packageName);
                out.writeInt(pkgNameBuf.length);
                out.writeInt(pkg.versionCode);
                out.write(pkgNameBuf);
            }
            }
        } catch (IOException e) {
        } catch (IOException e) {
            Log.e(TAG, "Unable to write package manager state file!");
            Log.e(TAG, "Unable to write package manager state file!");