content: /*
   * Copyright (C) 2013 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.pm;
  
  import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
  
  import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
  
  import com.android.internal.util.Preconditions;
  import android.content.pm.PackageParser;
  import android.util.ArrayMap;
  import android.util.ArraySet;
  import android.util.Base64;
  import android.util.Slog;
  import android.util.LongSparseArray;
  
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.security.PublicKey;
  import java.util.Set;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  import org.xmlpull.v1.XmlSerializer;
  
  /*
   * Manages system-wide KeySet state.
   */
  public class KeySetManagerService {
  
      static final String TAG = "KeySetManagerService";
  
      /* original keysets implementation had no versioning info, so this is the first */
      public static final int FIRST_VERSION = 1;
  
      public static final int CURRENT_VERSION = FIRST_VERSION;
  
      /** Sentinel value returned when a {@code KeySet} is not found. */
      public static final long KEYSET_NOT_FOUND = -1;
  
      /** Sentinel value returned when public key is not found. */
      protected static final long PUBLIC_KEY_NOT_FOUND = -1;
  
      private final LongSparseArray<KeySetHandle> mKeySets;
  
      private final LongSparseArray<PublicKeyHandle> mPublicKeys;
  
      protected final LongSparseArray<ArraySet<Long>> mKeySetMapping;
  
      private final ArrayMap<String, PackageSetting> mPackages;
  
      private long lastIssuedKeySetId = 0;
  
      private long lastIssuedKeyId = 0;
  
      class PublicKeyHandle {
          private final PublicKey mKey;
          private final long mId;
          private int mRefCount;
  
          public PublicKeyHandle(long id, PublicKey key) {
              mId = id;
              mRefCount = 1;
              mKey = key;
          }
  
          /*
           * Only used when reading state from packages.xml
           */
          private PublicKeyHandle(long id, int refCount, PublicKey key) {
              mId = id;
              mRefCount = refCount;
              mKey = key;
          }
  
          public long getId() {
              return mId;
          }
  
          public PublicKey getKey() {
              return mKey;
          }
  
          public int getRefCountLPr() {
              return mRefCount;
          }
  
          public void incrRefCountLPw() {
              mRefCount++;
              return;
          }
  
          public long decrRefCountLPw() {
              mRefCount--;
              return mRefCount;
          }
      }
  
      public KeySetManagerService(ArrayMap<String, PackageSetting> packages) {
          mKeySets = new LongSparseArray<KeySetHandle>();
          mPublicKeys = new LongSparseArray<PublicKeyHandle>();
          mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
          mPackages = packages;
      }
  
      /**
       * Determine if a package is signed by the given KeySet.
       *
       * Returns false if the package was not signed by all the
       * keys in the KeySet.
       *
       * Returns true if the package was signed by at least the
       * keys in the given KeySet.
       *
       * Note that this can return true for multiple KeySets.
       */
      public boolean packageIsSignedByLPr(String packageName, KeySetHandle ks) {
          PackageSetting pkg = mPackages.get(packageName);
          if (pkg == null) {
              throw new NullPointerException("Invalid package name");
          }
          if (pkg.keySetData == null) {
              throw new NullPointerException("Package has no KeySet data");
          }
          long id = getIdByKeySetLPr(ks);
          if (id == KEYSET_NOT_FOUND) {
                  return false;
          }
          ArraySet<Long> pkgKeys = mKeySetMapping.get(pkg.keySetData.getProperSigningKeySet());
          ArraySet<Long> testKeys = mKeySetMapping.get(id);
          return pkgKeys.containsAll(testKeys);
      }
  
      /**
       * Determine if a package is signed by the given KeySet.
       *
       * Returns false if the package was not signed by all the
       * keys in the KeySet, or if the package was signed by keys
       * not in the KeySet.
       *
       * Note that this can return only for one KeySet.
       */
      public boolean packageIsSignedByExactlyLPr(String packageName, KeySetHandle ks) {
          PackageSetting pkg = mPackages.get(packageName);
          if (pkg == null) {
              throw new NullPointerException("Invalid package name");
          }
          if (pkg.keySetData == null
              || pkg.keySetData.getProperSigningKeySet()
              == PackageKeySetData.KEYSET_UNASSIGNED) {
              throw new NullPointerException("Package has no KeySet data");
           }
          long id = getIdByKeySetLPr(ks);
          if (id == KEYSET_NOT_FOUND) {
                  return false;
          }
          ArraySet<Long> pkgKeys = mKeySetMapping.get(pkg.keySetData.getProperSigningKeySet());
          ArraySet<Long> testKeys = mKeySetMapping.get(id);
          return pkgKeys.equals(testKeys);
      }
  
      /**
       * addScannedPackageLPw directly modifies the package metadata in  pm.Settings
       * at a point of no-return.  We need to make sure that the scanned package does
       * not contain bad keyset meta-data that could generate an incorrect
       * PackageSetting. Verify that there is a signing keyset, there are no issues
       * with null objects, and the upgrade and defined keysets match.
       *
       * Returns true if the package can safely be added to the keyset metadata.
       */
      public void assertScannedPackageValid(PackageParser.Package pkg)
              throws PackageManagerException {
          if (pkg == null || pkg.packageName == null) {
              throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                      "Passed invalid package to keyset validation.");
          }
          ArraySet<PublicKey> signingKeys = pkg.mSigningDetails.publicKeys;
          if (signingKeys == null || !(signingKeys.size() > 0) || signingKeys.contains(null)) {
              throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                      "Package has invalid signing-key-set.");
          }
          ArrayMap<String, ArraySet<PublicKey>> definedMapping = pkg.mKeySetMapping;
          if (definedMapping != null) {
              if (definedMapping.containsKey(null) || definedMapping.containsValue(null)) {
                  throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                          "Package has null defined key set.");
              }
              int defMapSize = definedMapping.size();
              for (int i = 0; i < defMapSize; i++) {
                  if (!(definedMapping.valueAt(i).size() > 0)
                          || definedMapping.valueAt(i).contains(null)) {
                      throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                              "Package has null/no public keys for defined key-sets.");
                  }
              }
          }
          ArraySet<String> upgradeAliases = pkg.mUpgradeKeySets;
          if (upgradeAliases != null) {
              if (definedMapping == null || !(definedMapping.keySet().containsAll(upgradeAliases))) {
                  throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                          "Package has upgrade-key-sets without corresponding definitions.");
              }
          }
      }
  
      public void addScannedPackageLPw(PackageParser.Package pkg) {
          Preconditions.checkNotNull(pkg, "Attempted to add null pkg to ksms.");
          Preconditions.checkNotNull(pkg.packageName, "Attempted to add null pkg to ksms.");
          PackageSetting ps = mPackages.get(pkg.packageName);
          Preconditions.checkNotNull(ps, "pkg: " + pkg.packageName
                      + "does not have a corresponding entry in mPackages.");
          addSigningKeySetToPackageLPw(ps, pkg.mSigningDetails.publicKeys);
          if (pkg.mKeySetMapping != null) {
              addDefinedKeySetsToPackageLPw(ps, pkg.mKeySetMapping);
              if (pkg.mUpgradeKeySets != null) {
                  addUpgradeKeySetsToPackageLPw(ps, pkg.mUpgradeKeySets);
              }
          }
      }
  
      /**
       * Informs the system that the given package was signed by the provided KeySet.
       */
      void addSigningKeySetToPackageLPw(PackageSetting pkg,
              ArraySet<PublicKey> signingKeys) {
  
          /* check existing keyset for reuse or removal */
          long signingKeySetId = pkg.keySetData.getProperSigningKeySet();
  
          if (signingKeySetId != PackageKeySetData.KEYSET_UNASSIGNED) {
              ArraySet<PublicKey> existingKeys = getPublicKeysFromKeySetLPr(signingKeySetId);
              if (existingKeys != null && existingKeys.equals(signingKeys)) {
  
                  /* no change in signing keys, leave PackageSetting alone */
                  return;
              } else {
  
                  /* old keyset no longer valid, remove ref */
                  decrementKeySetLPw(signingKeySetId);
              }
          }
  
          /* create and add a new keyset */
          KeySetHandle ks = addKeySetLPw(signingKeys);
          long id = ks.getId();
          pkg.keySetData.setProperSigningKeySet(id);
          return;
      }
  
      /**
       * Fetches the stable identifier associated with the given KeySet. Returns
       * {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found.
       */
      private long getIdByKeySetLPr(KeySetHandle ks) {
          for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
              KeySetHandle value = mKeySets.valueAt(keySetIndex);
              if (ks.equals(value)) {
                  return mKeySets.keyAt(keySetIndex);
              }
          }
          return KEYSET_NOT_FOUND;
      }
  
      /**
       * Inform the system that the given package defines the given KeySets.
       * Remove any KeySets the package no longer defines.
       */
      void addDefinedKeySetsToPackageLPw(PackageSetting pkg,
              ArrayMap<String, ArraySet<PublicKey>> definedMapping) {
          ArrayMap<String, Long> prevDefinedKeySets = pkg.keySetData.getAliases();
  
          /* add all of the newly defined KeySets */
          ArrayMap<String, Long> newKeySetAliases = new ArrayMap<String, Long>();
          final int defMapSize = definedMapping.size();
          for (int i = 0; i < defMapSize; i++) {
              String alias = definedMapping.keyAt(i);
              ArraySet<PublicKey> pubKeys = definedMapping.valueAt(i);
              if (alias != null && pubKeys != null && pubKeys.size() > 0) {
                  KeySetHandle ks = addKeySetLPw(pubKeys);
                  newKeySetAliases.put(alias, ks.getId());
              }
          }
  
          /* remove each of the old references */
          final int prevDefSize = prevDefinedKeySets.size();
          for (int i = 0; i < prevDefSize; i++) {
              decrementKeySetLPw(prevDefinedKeySets.valueAt(i));
          }
          pkg.keySetData.removeAllUpgradeKeySets();
  
          /* switch to the just-added */
          pkg.keySetData.setAliases(newKeySetAliases);
          return;
      }
  
      /**
       * This informs the system that the given package has defined a KeySet
       * alias in its manifest to be an upgradeKeySet.  This must be called
       * after all of the defined KeySets have been added.
       */
      void addUpgradeKeySetsToPackageLPw(PackageSetting pkg,
              ArraySet<String> upgradeAliases) {
          final int uaSize = upgradeAliases.size();
          for (int i = 0; i < uaSize; i++) {
              pkg.keySetData.addUpgradeKeySet(upgradeAliases.valueAt(i));
          }
          return;
      }
  
      /**
       * Fetched the {@link KeySetHandle} that a given package refers to by the
       * provided alias.  Returns null if the package is unknown or does not have a
       * KeySet corresponding to that alias.
       */
      public KeySetHandle getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
          PackageSetting p = mPackages.get(packageName);
          if (p == null || p.keySetData == null) {
              return null;
          }
          Long keySetId = p.keySetData.getAliases().get(alias);
          if (keySetId == null) {
              throw new IllegalArgumentException("Unknown KeySet alias: " + alias);
          }
          return mKeySets.get(keySetId);
      }
  
      /* Checks if an identifier refers to a known keyset */
      public boolean isIdValidKeySetId(long id) {
          return mKeySets.get(id) != null;
      }
  
      public boolean shouldCheckUpgradeKeySetLocked(PackageSettingBase oldPs, int scanFlags) {
          // Can't rotate keys during boot or if sharedUser.
          if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
                  || !oldPs.keySetData.isUsingUpgradeKeySets()) {
              return false;
          }
          // app is using upgradeKeySets; make sure all are valid
          long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
          for (int i = 0; i < upgradeKeySets.length; i++) {
              if (!isIdValidKeySetId(upgradeKeySets[i])) {
                  Slog.wtf(TAG, "Package "
                           + (oldPs.name != null ? oldPs.name : "<null>")
                           + " contains upgrade-key-set reference to unknown key-set: "
                           + upgradeKeySets[i]
                           + " reverting to signatures check.");
                  return false;
              }
          }
          return true;
      }
  
      public boolean checkUpgradeKeySetLocked(PackageSettingBase oldPS,
              PackageParser.Package newPkg) {
          // Upgrade keysets are being used.  Determine if new package has a superset of the
          // required keys.
          long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
          for (int i = 0; i < upgradeKeySets.length; i++) {
              Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
              if (upgradeSet != null && newPkg.mSigningDetails.publicKeys.containsAll(upgradeSet)) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Fetches the {@link PublicKey public keys} which belong to the specified
       * KeySet id.
       *
       * Returns {@code null} if the identifier doesn't
       * identify a {@link KeySetHandle}.
       */
      public ArraySet<PublicKey> getPublicKeysFromKeySetLPr(long id) {
          ArraySet<Long> pkIds = mKeySetMapping.get(id);
          if (pkIds == null) {
              return null;
          }
          ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
          final int pkSize = pkIds.size();
          for (int i = 0; i < pkSize; i++) {
              mPubKeys.add(mPublicKeys.get(pkIds.valueAt(i)).getKey());
          }
          return mPubKeys;
      }
  
      /**
       * Fetches the proper {@link KeySetHandle KeySet} that signed the given
       * package.
       *
       * @throws IllegalArgumentException if the package has no keyset data.
       * @throws NullPointerException if the packgae is unknown.
       */
      public KeySetHandle  getSigningKeySetByPackageNameLPr(String packageName) {
          PackageSetting p = mPackages.get(packageName);
          if (p == null
              || p.keySetData == null
              || p.keySetData.getProperSigningKeySet()
              == PackageKeySetData.KEYSET_UNASSIGNED) {
              return null;
          }
          return mKeySets.get(p.keySetData.getProperSigningKeySet());
      }
  
      /**
       * Creates a new KeySet corresponding to the given keys.
       *
       * If the {@link PublicKey PublicKeys} aren't known to the system, this
       * adds them. Otherwise, they're deduped and the reference count
       * incremented.
       *
       * If the KeySet isn't known to the system, this adds that and creates the
       * mapping to the PublicKeys. If it is known, then it's deduped and the
       * reference count is incremented.
       *
       * Throws if the provided set is {@code null}.
       */
      private KeySetHandle addKeySetLPw(ArraySet<PublicKey> keys) {
          if (keys == null || keys.size() == 0) {
              throw new IllegalArgumentException("Cannot add an empty set of keys!");
          }
  
          /* add each of the keys in the provided set */
          ArraySet<Long> addedKeyIds = new ArraySet<Long>(keys.size());
          final int kSize = keys.size();
          for (int i = 0; i < kSize; i++) {
              long id = addPublicKeyLPw(keys.valueAt(i));
              addedKeyIds.add(id);
          }
  
          /* check to see if the resulting keyset is new */
          long existingKeySetId = getIdFromKeyIdsLPr(addedKeyIds);
          if (existingKeySetId != KEYSET_NOT_FOUND) {
  
              /* public keys were incremented, but we aren't adding a new keyset: undo */
              for (int i = 0; i < kSize; i++) {
                  decrementPublicKeyLPw(addedKeyIds.valueAt(i));
              }
              KeySetHandle ks = mKeySets.get(existingKeySetId);
              ks.incrRefCountLPw();
              return ks;
          }
  
          // get the next keyset id
          long id = getFreeKeySetIDLPw();
  
          // create the KeySet object and add to mKeySets and mapping
          KeySetHandle ks = new KeySetHandle(id);
          mKeySets.put(id, ks);
          mKeySetMapping.put(id, addedKeyIds);
          return ks;
      }
  
      /*
       * Decrements the reference to KeySet represented by the given id.  If this
       * drops to zero, then also decrement the reference to each public key it
       * contains and remove the KeySet.
       */
      private void decrementKeySetLPw(long id) {
          KeySetHandle ks = mKeySets.get(id);
          if (ks == null) {
              /* nothing to do */
              return;
          }
          if (ks.decrRefCountLPw() <= 0) {
              ArraySet<Long> pubKeys = mKeySetMapping.get(id);
              final int pkSize = pubKeys.size();
              for (int i = 0; i < pkSize; i++) {
                  decrementPublicKeyLPw(pubKeys.valueAt(i));
              }
              mKeySets.delete(id);
              mKeySetMapping.delete(id);
          }
      }
  
      /*
       * Decrements the reference to PublicKey represented by the given id.  If
       * this drops to zero, then remove it.
       */
      private void decrementPublicKeyLPw(long id) {
          PublicKeyHandle pk = mPublicKeys.get(id);
          if (pk == null) {
              /* nothing to do */
              return;
          }
          if (pk.decrRefCountLPw() <= 0) {
              mPublicKeys.delete(id);
          }
      }
  
      /**
       * Adds the given PublicKey to the system, deduping as it goes.
       */
      private long addPublicKeyLPw(PublicKey key) {
          Preconditions.checkNotNull(key, "Cannot add null public key!");
          long id = getIdForPublicKeyLPr(key);
          if (id != PUBLIC_KEY_NOT_FOUND) {
  
              /* We already know about this key, increment its ref count and ret */
              mPublicKeys.get(id).incrRefCountLPw();
              return id;
          }
  
          /* if it's new find the first unoccupied slot in the public keys */
          id = getFreePublicKeyIdLPw();
          mPublicKeys.put(id, new PublicKeyHandle(id, key));
          return id;
      }
  
      /**
       * Finds the stable identifier for a KeySet based on a set of PublicKey stable IDs.
       *
       * Returns KEYSET_NOT_FOUND if there isn't one.
       */
      private long getIdFromKeyIdsLPr(Set<Long> publicKeyIds) {
          for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) {
              ArraySet<Long> value = mKeySetMapping.valueAt(keyMapIndex);
              if (value.equals(publicKeyIds)) {
                  return mKeySetMapping.keyAt(keyMapIndex);
              }
          }
          return KEYSET_NOT_FOUND;
      }
  
      /**
       * Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
       */
      private long getIdForPublicKeyLPr(PublicKey k) {
          String encodedPublicKey = new String(k.getEncoded());
          for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) {
              PublicKey value = mPublicKeys.valueAt(publicKeyIndex).getKey();
              String encodedExistingKey = new String(value.getEncoded());
              if (encodedPublicKey.equals(encodedExistingKey)) {
                  return mPublicKeys.keyAt(publicKeyIndex);
              }
          }
          return PUBLIC_KEY_NOT_FOUND;
      }
  
      /**
       * Gets an unused stable identifier for a KeySet.
       */
      private long getFreeKeySetIDLPw() {
          lastIssuedKeySetId += 1;
          return lastIssuedKeySetId;
      }
  
      /**
       * Same as above, but for public keys.
       */
      private long getFreePublicKeyIdLPw() {
          lastIssuedKeyId += 1;
          return lastIssuedKeyId;
      }
  
      /*
       * This package is being removed from the system, so we need to
       * remove its keyset and public key references, then remove its
       * keyset data.
       */
      public void removeAppKeySetDataLPw(String packageName) {
  
          /* remove refs from common keysets and public keys */
          PackageSetting pkg = mPackages.get(packageName);
          Preconditions.checkNotNull(pkg, "pkg name: " + packageName
                  + "does not have a corresponding entry in mPackages.");
          long signingKeySetId = pkg.keySetData.getProperSigningKeySet();
          decrementKeySetLPw(signingKeySetId);
          ArrayMap<String, Long> definedKeySets = pkg.keySetData.getAliases();
          for (int i = 0; i < definedKeySets.size(); i++) {
              decrementKeySetLPw(definedKeySets.valueAt(i));
          }
  
          /* remove from package */
          clearPackageKeySetDataLPw(pkg);
          return;
      }
  
      private void clearPackageKeySetDataLPw(PackageSetting pkg) {
          pkg.keySetData.setProperSigningKeySet(PackageKeySetData.KEYSET_UNASSIGNED);
          pkg.keySetData.removeAllDefinedKeySets();
          pkg.keySetData.removeAllUpgradeKeySets();
          return;
      }
  
      public String encodePublicKey(PublicKey k) throws IOException {
          return new String(Base64.encode(k.getEncoded(), Base64.NO_WRAP));
      }
  
      public void dumpLPr(PrintWriter pw, String packageName,
                          DumpState dumpState) {
          boolean printedHeader = false;
          for (ArrayMap.Entry<String, PackageSetting> e : mPackages.entrySet()) {
              String keySetPackage = e.getKey();
              if (packageName != null && !packageName.equals(keySetPackage)) {
                  continue;
              }
              if (!printedHeader) {
                  if (dumpState.onTitlePrinted())
                      pw.println();
                  pw.println("Key Set Manager:");
                  printedHeader = true;
              }
              PackageSetting pkg = e.getValue();
              pw.print("  ["); pw.print(keySetPackage); pw.println("]");
              if (pkg.keySetData != null) {
                  boolean printedLabel = false;
                  for (ArrayMap.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
                      if (!printedLabel) {
                          pw.print("      KeySets Aliases: ");
                          printedLabel = true;
                      } else {
                          pw.print(", ");
                      }
                      pw.print(entry.getKey());
                      pw.print('=');
                      pw.print(Long.toString(entry.getValue()));
                  }
                  if (printedLabel) {
                      pw.println("");
                  }
                  printedLabel = false;
                  if (pkg.keySetData.isUsingDefinedKeySets()) {
                      ArrayMap<String, Long> definedKeySets = pkg.keySetData.getAliases();
                      final int dksSize = definedKeySets.size();
                      for (int i = 0; i < dksSize; i++) {
                          if (!printedLabel) {
                              pw.print("      Defined KeySets: ");
                              printedLabel = true;
                          } else {
                              pw.print(", ");
                          }
                          pw.print(Long.toString(definedKeySets.valueAt(i)));
                      }
                  }
                  if (printedLabel) {
                      pw.println("");
                  }
                  printedLabel = false;
                  final long signingKeySet = pkg.keySetData.getProperSigningKeySet();
                  pw.print("      Signing KeySets: ");
                  pw.print(Long.toString(signingKeySet));
                  pw.println("");
                  if (pkg.keySetData.isUsingUpgradeKeySets()) {
                      for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
                          if (!printedLabel) {
                              pw.print("      Upgrade KeySets: ");
                              printedLabel = true;
                          } else {
                              pw.print(", ");
                          }
                          pw.print(Long.toString(keySetId));
                      }
                  }
                  if (printedLabel) {
                      pw.println("");
                  }
              }
          }
      }
  
      void writeKeySetManagerServiceLPr(XmlSerializer serializer) throws IOException {
          serializer.startTag(null, "keyset-settings");
          serializer.attribute(null, "version", Integer.toString(CURRENT_VERSION));
          writePublicKeysLPr(serializer);
          writeKeySetsLPr(serializer);
          serializer.startTag(null, "lastIssuedKeyId");
          serializer.attribute(null, "value", Long.toString(lastIssuedKeyId));
          serializer.endTag(null, "lastIssuedKeyId");
          serializer.startTag(null, "lastIssuedKeySetId");
          serializer.attribute(null, "value", Long.toString(lastIssuedKeySetId));
          serializer.endTag(null, "lastIssuedKeySetId");
          serializer.endTag(null, "keyset-settings");
      }
  
      void writePublicKeysLPr(XmlSerializer serializer) throws IOException {
          serializer.startTag(null, "keys");
          for (int pKeyIndex = 0; pKeyIndex < mPublicKeys.size(); pKeyIndex++) {
              long id = mPublicKeys.keyAt(pKeyIndex);
              PublicKeyHandle pkh = mPublicKeys.valueAt(pKeyIndex);
              String encodedKey = encodePublicKey(pkh.getKey());
              serializer.startTag(null, "public-key");
              serializer.attribute(null, "identifier", Long.toString(id));
              serializer.attribute(null, "value", encodedKey);
              serializer.endTag(null, "public-key");
          }
          serializer.endTag(null, "keys");
      }
  
      void writeKeySetsLPr(XmlSerializer serializer) throws IOException {
          serializer.startTag(null, "keysets");
          for (int keySetIndex = 0; keySetIndex < mKeySetMapping.size(); keySetIndex++) {
              long id = mKeySetMapping.keyAt(keySetIndex);
              ArraySet<Long> keys = mKeySetMapping.valueAt(keySetIndex);
              serializer.startTag(null, "keyset");
              serializer.attribute(null, "identifier", Long.toString(id));
              for (long keyId : keys) {
                  serializer.startTag(null, "key-id");
                  serializer.attribute(null, "identifier", Long.toString(keyId));
                  serializer.endTag(null, "key-id");
              }
              serializer.endTag(null, "keyset");
          }
          serializer.endTag(null, "keysets");
      }
  
      void readKeySetsLPw(XmlPullParser parser, ArrayMap<Long, Integer> keySetRefCounts)
              throws XmlPullParserException, IOException {
          int type;
          long currentKeySetId = 0;
          int outerDepth = parser.getDepth();
          String recordedVersionStr = parser.getAttributeValue(null, "version");
          if (recordedVersionStr == null) {
              // The keyset information comes from pre-versioned devices, and
              // is inaccurate, don't collect any of it.
              while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                      && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                  continue;
              }
              // The KeySet information read previously from packages.xml is invalid.
              // Destroy it all.
              for (PackageSetting p : mPackages.values()) {
                  clearPackageKeySetDataLPw(p);
              }
              return;
          }
          int recordedVersion = Integer.parseInt(recordedVersionStr);
          while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
              if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                  continue;
              }
              final String tagName = parser.getName();
              if (tagName.equals("keys")) {
                  readKeysLPw(parser);
              } else if (tagName.equals("keysets")) {
                  readKeySetListLPw(parser);
              } else if (tagName.equals("lastIssuedKeyId")) {
                  lastIssuedKeyId = Long.parseLong(parser.getAttributeValue(null, "value"));
              } else if (tagName.equals("lastIssuedKeySetId")) {
                  lastIssuedKeySetId = Long.parseLong(parser.getAttributeValue(null, "value"));
              }
          }
  
          addRefCountsFromSavedPackagesLPw(keySetRefCounts);
      }
  
      void readKeysLPw(XmlPullParser parser)
              throws XmlPullParserException, IOException {
          int outerDepth = parser.getDepth();
          int type;
          while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                  && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
              if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                  continue;
              }
              final String tagName = parser.getName();
              if (tagName.equals("public-key")) {
                  readPublicKeyLPw(parser);
              }
          }
      }
  
      void readKeySetListLPw(XmlPullParser parser)
              throws XmlPullParserException, IOException {
          int outerDepth = parser.getDepth();
          int type;
          long currentKeySetId = 0;
          while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                  && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
              if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                  continue;
              }
              final String tagName = parser.getName();
              if (tagName.equals("keyset")) {
                  String encodedID = parser.getAttributeValue(null, "identifier");
                  currentKeySetId = Long.parseLong(encodedID);
                  int refCount = 0;
                  mKeySets.put(currentKeySetId, new KeySetHandle(currentKeySetId, refCount));
                  mKeySetMapping.put(currentKeySetId, new ArraySet<Long>());
              } else if (tagName.equals("key-id")) {
                  String encodedID = parser.getAttributeValue(null, "identifier");
                  long id = Long.parseLong(encodedID);
                  mKeySetMapping.get(currentKeySetId).add(id);
              }
          }
      }
  
      void readPublicKeyLPw(XmlPullParser parser)
              throws XmlPullParserException {
          String encodedID = parser.getAttributeValue(null, "identifier");
          long identifier = Long.parseLong(encodedID);
          int refCount = 0;
          String encodedPublicKey = parser.getAttributeValue(null, "value");
          PublicKey pub = PackageParser.parsePublicKey(encodedPublicKey);
          if (pub != null) {
              PublicKeyHandle pkh = new PublicKeyHandle(identifier, refCount, pub);
              mPublicKeys.put(identifier, pkh);
          }
      }
  
      /*
       * Set each KeySet ref count.  Also increment all public keys in each keyset.
       */
      private void addRefCountsFromSavedPackagesLPw(ArrayMap<Long, Integer> keySetRefCounts) {
          final int numRefCounts = keySetRefCounts.size();
          for (int i = 0; i < numRefCounts; i++) {
              KeySetHandle ks = mKeySets.get(keySetRefCounts.keyAt(i));
              if (ks == null) {
                  /* something went terribly wrong and we have references to a non-existent key-set */
                  Slog.wtf(TAG, "Encountered non-existent key-set reference when reading settings");
                  continue;
              }
              ks.setRefCountLPw(keySetRefCounts.valueAt(i));
          }
  
          /*
           * In case something went terribly wrong and we have keysets with no associated packges
           * that refer to them, record the orphaned keyset ids, and remove them using
           * decrementKeySetLPw() after all keyset references have been set so that the associtaed
           * public keys have the appropriate references from all keysets.
           */
          ArraySet<Long> orphanedKeySets = new ArraySet<Long>();
          final int numKeySets = mKeySets.size();
          for (int i = 0; i < numKeySets; i++) {
              if (mKeySets.valueAt(i).getRefCountLPr() == 0) {
                  Slog.wtf(TAG, "Encountered key-set w/out package references when reading settings");
                  orphanedKeySets.add(mKeySets.keyAt(i));
              }
              ArraySet<Long> pubKeys = mKeySetMapping.valueAt(i);
              final int pkSize = pubKeys.size();
              for (int j = 0; j < pkSize; j++) {
                  mPublicKeys.get(pubKeys.valueAt(j)).incrRefCountLPw();
              }
          }
          final int numOrphans = orphanedKeySets.size();
          for (int i = 0; i < numOrphans; i++) {
              decrementKeySetLPw(orphanedKeySets.valueAt(i));
          }
      }
  }
  