content: /*
   * Copyright (C) 2009 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.backup;
  
  import android.app.backup.BackupAgent;
  import android.app.backup.BackupDataInput;
  import android.app.backup.BackupDataOutput;
  import android.content.ComponentName;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.pm.PackageManagerInternal;
  import android.content.pm.ResolveInfo;
  import android.content.pm.Signature;
  import android.content.pm.SigningInfo;
  import android.os.Build;
  import android.os.ParcelFileDescriptor;
  import android.util.Slog;
  
  import com.android.server.LocalServices;
  import com.android.server.backup.utils.AppBackupUtils;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedOutputStream;
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.DataInputStream;
  import java.io.DataOutputStream;
  import java.io.EOFException;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Objects;
  import java.util.Set;
  
  /**
   * 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.
   */
  public class PackageManagerBackupAgent extends BackupAgent {
      private static final String TAG = "PMBA";
      private static final boolean DEBUG = false;
  
      // key under which we store global metadata (individual app metadata
      // is stored using the package name as a key)
      private static final String GLOBAL_METADATA_KEY = "@meta@";
  
      // key under which we store the identity of the user's chosen default home app
      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;
  
      // 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 int mUserId;
      private List<PackageInfo> mAllPackages;
      private PackageManager mPackageManager;
      // version & signature info of each app in a restore set
      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;
      private ComponentName mStoredHomeComponent;
      private long mStoredHomeVersion;
      private ArrayList<byte[]> mStoredHomeSigHashes;
  
      private boolean mHasMetadata;
      private ComponentName mRestoredHome;
      private long mRestoredHomeVersion;
      private String mRestoredHomeInstaller;
      private ArrayList<byte[]> mRestoredHomeSigHashes;
  
      // For compactness we store the SHA-256 hash of each app's Signatures
      // rather than the Signature blocks themselves.
      public class Metadata {
          public long versionCode;
          public ArrayList<byte[]> sigHashes;
  
          Metadata(long version, ArrayList<byte[]> hashes) {
              versionCode = version;
              sigHashes = hashes;
          }
      }
  
      // We're constructed with the set of applications that are participating
      // in backup.  This set changes as apps are installed & removed.
      public PackageManagerBackupAgent(
              PackageManager packageMgr, List<PackageInfo> packages, int userId) {
          init(packageMgr, packages, userId);
      }
  
      public PackageManagerBackupAgent(PackageManager packageMgr, int userId) {
          init(packageMgr, null, userId);
  
          evaluateStorablePackages();
      }
  
      private void init(PackageManager packageMgr, List<PackageInfo> packages, int userId) {
          mPackageManager = packageMgr;
          mAllPackages = packages;
          mRestoredSignatures = null;
          mHasMetadata = false;
  
          mStoredSdkVersion = Build.VERSION.SDK_INT;
          mStoredIncrementalVersion = Build.VERSION.INCREMENTAL;
          mUserId = userId;
      }
  
      // We will need to refresh our understanding of what is eligible for
      // backup periodically; this entry point serves that purpose.
      public void evaluateStorablePackages() {
          mAllPackages = getStorableApplications(mPackageManager, mUserId);
      }
  
      /** Gets all packages installed on user {@code userId} eligible for backup. */
      public static List<PackageInfo> getStorableApplications(PackageManager pm, int userId) {
          List<PackageInfo> pkgs =
                  pm.getInstalledPackagesAsUser(PackageManager.GET_SIGNING_CERTIFICATES, userId);
          int N = pkgs.size();
          for (int a = N-1; a >= 0; a--) {
              PackageInfo pkg = pkgs.get(a);
              if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, userId)) {
                  pkgs.remove(a);
              }
          }
          return pkgs;
      }
  
      public boolean hasMetadata() {
          return mHasMetadata;
      }
  
      public Metadata getRestoredMetadata(String packageName) {
          if (mRestoredSignatures == null) {
              Slog.w(TAG, "getRestoredMetadata() before metadata read!");
              return null;
          }
  
          return mRestoredSignatures.get(packageName);
      }
  
      public Set<String> getRestoredPackages() {
          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();
      }
  
      // 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()");
  
          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();
          }
  
          /*
           * 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;
          String homeInstaller = null;
          ComponentName home = getPreferredHomeComponent();
          if (home != null) {
              try {
                  homeInfo = mPackageManager.getPackageInfoAsUser(home.getPackageName(),
                          PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
                  homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
                  homeVersion = homeInfo.getLongVersionCode();
                  SigningInfo signingInfo = homeInfo.signingInfo;
                  if (signingInfo == null) {
                      Slog.e(TAG, "Home app has no signing information");
                  } else {
                      // retrieve the newest sigs to back up
                      // TODO (b/73988180) use entire signing history in case of rollbacks
                      Signature[] homeInfoSignatures = signingInfo.getApkContentsSigners();
                      homeSigHashes = BackupUtils.hashSignatureArray(homeInfoSignatures);
                  }
              } 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.
              PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
              final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
                      || !Objects.equals(home, mStoredHomeComponent)
                      || (home != null
                          && !BackupUtils.signaturesMatch(mStoredHomeSigHashes, homeInfo, pmi));
              if (needHomeBackup) {
                  if (DEBUG) {
                      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 : "" );
                      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. 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.
               */
              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.getPackageInfoAsUser(packName,
                                  PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
                      } 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.getLongVersionCode() == mStateVersions.get(packName).versionCode) {
                              continue;
                          }
                      }
  
                      SigningInfo signingInfo = info.signingInfo;
                      if (signingInfo == null) {
                          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();
                      if (info.versionCodeMajor != 0) {
                          outputBufferStream.writeInt(Integer.MIN_VALUE);
                          outputBufferStream.writeLong(info.getLongVersionCode());
                      } else {
                          outputBufferStream.writeInt(info.versionCode);
                      }
                      // retrieve the newest sigs to back up
                      Signature[] infoSignatures = signingInfo.getApkContentsSigners();
                      writeSignatureHashArray(outputBufferStream,
                              BackupUtils.hashSignatureArray(infoSignatures));
  
                      if (DEBUG) {
                          Slog.v(TAG, "+ writing metadata for " + packName
                                  + " version=" + info.getLongVersionCode()
                                  + " 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.  We want to preserve the entry for them, however,
              // because we want the right thing to happen if the user goes through
              // a backup / uninstall / backup / reinstall sequence.
              if (DEBUG) {
                  if (mExisting.size() > 0) {
                      StringBuilder sb = new StringBuilder(64);
                      sb.append("Preserving metadata for deleted packages:");
                      for (String app : mExisting) {
                          sb.append(' ');
                          sb.append(app);
                      }
                      Slog.v(TAG, sb.toString());
                  }
              }
          } 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);
      }
  
      private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
              throws IOException {
          data.writeEntityHeader(key, bytes.length);
          data.writeEntityData(bytes, bytes.length);
      }
  
      // "Restore" here is a misnomer.  What we're really doing is reading back the
      // set of app signatures associated with each backed-up app in this restore
      // image.  We'll use those later to determine what we can legitimately restore.
      public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
              throws IOException {
          if (DEBUG) Slog.v(TAG, "onRestore()");
  
          // 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);
  
                  ancestralRecordVersionValue = inputBufferStream.readInt();
              }
          }
          return ancestralRecordVersionValue;
      }
  
      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)
              throws IOException {
          // 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 static ArrayList<byte[]> readSignatureHashArray(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) {
                  // Replace with the hashes.
                  sigs = BackupUtils.hashSignatureArray(sigs);
              }
  
              return sigs;
          } catch (IOException e) {
              Slog.e(TAG, "Unable to read signatures");
              return null;
          }
      }
  
      // Util: parse out an existing state file into a usable structure
      private void parseStateFile(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 versionCodeInt = in.readInt();
                  long versionCode;
                  if (versionCodeInt == Integer.MIN_VALUE) {
                      versionCode = in.readLong();
                  } else {
                      versionCode = versionCodeInt;
                  }
  
                  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 ComponentName getPreferredHomeComponent() {
          return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
      }
  
      // Util: write out our new backup state file
      private void writeStateFile(List<PackageInfo> pkgs, ComponentName preferredHome,
              long homeVersion, ArrayList<byte[]> homeSigHashes, 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);
                  if (pkg.versionCodeMajor != 0) {
                      out.writeInt(Integer.MIN_VALUE);
                      out.writeLong(pkg.getLongVersionCode());
                  } else {
                      out.writeInt(pkg.versionCode);
                  }
              }
  
              out.flush();
          } catch (IOException e) {
              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;
          }
      }
  }
  