FileDocCategorySizeDatePackage
PackageManagerBackupAgent.javaAPI DocAndroid 5.1 API27391Thu Mar 12 22:22:42 GMT 2015com.android.server.backup

PackageManagerBackupAgent

public 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
Constructors Summary
PackageManagerBackupAgent(android.content.pm.PackageManager packageMgr, List packages)

        init(packageMgr, packages);
    
PackageManagerBackupAgent(android.content.pm.PackageManager packageMgr)

        init(packageMgr, null);

        evaluateStorablePackages();
    
Methods Summary
public voidevaluateStorablePackages()

        mAllPackages = getStorableApplications(mPackageManager);
    
private android.content.ComponentNamegetPreferredHomeComponent()

        return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
    
public com.android.server.backup.PackageManagerBackupAgent$MetadatagetRestoredMetadata(java.lang.String packageName)

        if (mRestoredSignatures == null) {
            Slog.w(TAG, "getRestoredMetadata() before metadata read!");
            return null;
        }

        return mRestoredSignatures.get(packageName);
    
public java.util.SetgetRestoredPackages()

        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.ListgetStorableApplications(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 booleanhasMetadata()

        return mHasMetadata;
    
private static java.util.ArrayListhashSignatureArray(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 voidinit(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 voidonBackup(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 voidonRestore(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 voidparseStateFile(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.ArrayListreadSignatureHashArray(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 voidwriteEntity(android.app.backup.BackupDataOutput data, java.lang.String key, byte[] bytes)

        data.writeEntityHeader(key, bytes.length);
        data.writeEntityData(bytes, bytes.length);
    
private static voidwriteSignatureHashArray(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 voidwriteStateFile(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!");
        }