content: /*
   *  Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
   *  Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
   *
   *  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
   *
   *       https://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 brut.androlib.res.decoder;
  
  import android.util.TypedValue;
  import brut.androlib.AndrolibException;
  import brut.androlib.res.data.*;
  import brut.androlib.res.data.value.*;
  import brut.util.Duo;
  import brut.util.ExtDataInput;
  import com.google.common.io.LittleEndianDataInputStream;
  import org.apache.commons.io.input.CountingInputStream;
  
  import java.io.DataInput;
  import java.io.EOFException;
  import java.io.IOException;
  import java.io.InputStream;
  import java.math.BigInteger;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.logging.Logger;
  
  public class ARSCDecoder {
      public static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets, boolean keepBroken)
              throws AndrolibException {
          return decode(arscStream, findFlagsOffsets, keepBroken, new ResTable());
      }
  
      public static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets, boolean keepBroken,
                                    ResTable resTable)
              throws AndrolibException {
          try {
              ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
              ResPackage[] pkgs = decoder.readTableHeader();
              return new ARSCData(pkgs, decoder.mFlagsOffsets == null
                      ? null
                      : decoder.mFlagsOffsets.toArray(new FlagsOffset[0]), resTable);
          } catch (IOException ex) {
              throw new AndrolibException("Could not decode arsc file", ex);
          }
      }
  
      private ARSCDecoder(InputStream arscStream, ResTable resTable, boolean storeFlagsOffsets, boolean keepBroken) {
          arscStream = mCountIn = new CountingInputStream(arscStream);
          if (storeFlagsOffsets) {
              mFlagsOffsets = new ArrayList<>();
          } else {
              mFlagsOffsets = null;
          }
          // We need to explicitly cast to DataInput as otherwise the constructor is ambiguous.
          // We choose DataInput instead of InputStream as ExtDataInput wraps an InputStream in
          // a DataInputStream which is big-endian and ignores the little-endian behavior.
          mIn = new ExtDataInput((DataInput) new LittleEndianDataInputStream(arscStream));
          mResTable = resTable;
          mKeepBroken = keepBroken;
      }
  
      private ResPackage[] readTableHeader() throws IOException, AndrolibException {
          nextChunkCheckType(Header.TYPE_TABLE);
          int packageCount = mIn.readInt();
  
          mTableStrings = StringBlock.read(mIn);
          ResPackage[] packages = new ResPackage[packageCount];
  
          nextChunk();
          for (int i = 0; i < packageCount; i++) {
              mTypeIdOffset = 0;
              packages[i] = readTablePackage();
          }
          return packages;
      }
  
      private ResPackage readTablePackage() throws IOException, AndrolibException {
          checkChunkType(Header.XML_TYPE_PACKAGE);
          int id = mIn.readInt();
  
          if (id == 0) {
              // This means we are dealing with a Library Package, we should just temporarily
              // set the packageId to the next available id . This will be set at runtime regardless, but
              // for Apktool's use we need a non-zero packageId.
              // AOSP indicates 0x02 is next, as 0x01 is system and 0x7F is private.
              id = 2;
              if (mResTable.getPackageOriginal() == null && mResTable.getPackageRenamed() == null) {
                  mResTable.setSharedLibrary(true);
              }
          }
  
          String name = mIn.readNullEndedString(128, true);
          /* typeStrings */mIn.skipInt();
          /* lastPublicType */mIn.skipInt();
          /* keyStrings */mIn.skipInt();
          /* lastPublicKey */mIn.skipInt();
  
          // TypeIdOffset was added platform_frameworks_base/@f90f2f8dc36e7243b85e0b6a7fd5a590893c827e
          // which is only in split/new applications.
          int splitHeaderSize = (2 + 2 + 4 + 4 + (2 * 128) + (4 * 5)); // short, short, int, int, char[128], int * 4
          if (mHeader.headerSize == splitHeaderSize) {
              mTypeIdOffset = mIn.readInt();
          }
  
          if (mTypeIdOffset > 0) {
              LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/1728");
          }
  
          mTypeNames = StringBlock.read(mIn);
          mSpecNames = StringBlock.read(mIn);
  
          mResId = id << 24;
          mPkg = new ResPackage(mResTable, id, name);
  
          nextChunk();
          boolean flag = true;
          while (flag) {
              switch (mHeader.type) {
                  case Header.XML_TYPE_SPEC_TYPE:
                      readTableTypeSpec();
                      break;
                  case Header.XML_TYPE_LIBRARY:
                      readLibraryType();
                      break;
                  case Header.XML_TYPE_OVERLAY:
                      readOverlaySpec();
                      break;
                  case Header.XML_TYPE_STAGED_ALIAS:
                      readStagedAliasSpec();
                      break;
                  default:
                      flag = false;
                      break;
              }
          }
  
          return mPkg;
      }
  
      private void readLibraryType() throws AndrolibException, IOException {
          checkChunkType(Header.XML_TYPE_LIBRARY);
          int libraryCount = mIn.readInt();
  
          int packageId;
          String packageName;
  
          for (int i = 0; i < libraryCount; i++) {
              packageId = mIn.readInt();
              packageName = mIn.readNullEndedString(128, true);
              LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
          }
  
          while(nextChunk().type == Header.XML_TYPE_TYPE) {
              readTableTypeSpec();
          }
      }
  
      private void readStagedAliasSpec() throws IOException {
          int count = mIn.readInt();
  
          for (int i = 0; i < count; i++) {
              LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt()));
          }
  
          nextChunk();
      }
  
      private void readOverlaySpec() throws IOException {
          /* policyFlags */mIn.skipInt();
          int count = mIn.readInt();
  
          for (int i = 0; i < count; i++) {
              LOGGER.fine(String.format("Skipping overlay (%h)", mIn.readInt()));
          }
  
          nextChunk();
      }
  
      private void readTableTypeSpec() throws AndrolibException, IOException {
          mTypeSpec = readSingleTableTypeSpec();
          addTypeSpec(mTypeSpec);
  
          int type = nextChunk().type;
          ResTypeSpec resTypeSpec;
  
          while (type == Header.XML_TYPE_SPEC_TYPE) {
              resTypeSpec = readSingleTableTypeSpec();
              addTypeSpec(resTypeSpec);
              type = nextChunk().type;
  
              // We've detected sparse resources, lets record this so we can rebuild in that same format (sparse/not)
              // with aapt2. aapt1 will ignore this.
              if (! mResTable.getSparseResources()) {
                  mResTable.setSparseResources(true);
              }
          }
  
          while (type == Header.XML_TYPE_TYPE) {
              readTableType();
  
              // skip "TYPE 8 chunks" and/or padding data at the end of this chunk
              if (mCountIn.getCount() < mHeader.endPosition) {
                  LOGGER.warning("Unknown data detected. Skipping: " + (mHeader.endPosition - mCountIn.getCount()) + " byte(s)");
                  mCountIn.skip(mHeader.endPosition - mCountIn.getCount());
              }
  
              type = nextChunk().type;
  
              addMissingResSpecs();
          }
      }
  
      private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOException {
          checkChunkType(Header.XML_TYPE_SPEC_TYPE);
          int id = mIn.readUnsignedByte();
          mIn.skipBytes(3);
          int entryCount = mIn.readInt();
  
          if (mFlagsOffsets != null) {
              mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount));
          }
  
  		/* flags */mIn.skipBytes(entryCount * 4);
          mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
          mPkg.addType(mTypeSpec);
          return mTypeSpec;
      }
  
      private ResType readTableType() throws IOException, AndrolibException {
          checkChunkType(Header.XML_TYPE_TYPE);
          int typeId = mIn.readUnsignedByte() - mTypeIdOffset;
          if (mResTypeSpecs.containsKey(typeId)) {
              mResId = (0xff000000 & mResId) | mResTypeSpecs.get(typeId).getId() << 16;
              mTypeSpec = mResTypeSpecs.get(typeId);
          }
  
          int typeFlags = mIn.readByte();
          /* reserved */mIn.skipBytes(2);
          int entryCount = mIn.readInt();
          int entriesStart = mIn.readInt();
          mMissingResSpecs = new boolean[entryCount];
          Arrays.fill(mMissingResSpecs, true);
  
          ResConfigFlags flags = readConfigFlags();
          int position = (mHeader.startPosition + entriesStart) - (entryCount * 4);
  
          // For some APKs there is a disconnect between the reported size of Configs
          // If we find a mismatch skip those bytes.
          if (position != mCountIn.getCount()) {
              LOGGER.warning("Invalid data detected. Skipping: " + (position - mCountIn.getCount()) + " byte(s)");
              mIn.skipBytes(position - mCountIn.getCount());
          }
  
          if (typeFlags == 1) {
              LOGGER.info("Sparse type flags detected: " + mTypeSpec.getName());
          }
          int[] entryOffsets = mIn.readIntArray(entryCount);
  
          if (flags.isInvalid) {
              String resName = mTypeSpec.getName() + flags.getQualifiers();
              if (mKeepBroken) {
                  LOGGER.warning("Invalid config flags detected: " + resName);
              } else {
                  LOGGER.warning("Invalid config flags detected. Dropping resources: " + resName);
              }
          }
  
          mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags);
          HashMap<Integer, EntryData> offsetsToEntryData = new HashMap<>();
  
          for (int offset : entryOffsets) {
              if (offset == -1 || offsetsToEntryData.containsKey(offset)) {
                  continue;
              }
  
              offsetsToEntryData.put(offset, readEntryData());
          }
  
          for (int i = 0; i < entryOffsets.length; i++) {
              if (entryOffsets[i] != -1) {
                  mMissingResSpecs[i] = false;
                  mResId = (mResId & 0xffff0000) | i;
                  EntryData entryData = offsetsToEntryData.get(entryOffsets[i]);
                  readEntry(entryData);
              }
          }
  
          return mType;
      }
  
  
      private EntryData readEntryData() throws IOException, AndrolibException {
          short size = mIn.readShort();
          if (size < 0) {
              throw new AndrolibException("Entry size is under 0 bytes.");
          }
  
          short flags = mIn.readShort();
          int specNamesId = mIn.readInt();
          ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ? readValue() : readComplexEntry();
          EntryData entryData = new EntryData();
          entryData.mFlags = flags;
          entryData.mSpecNamesId = specNamesId;
          entryData.mValue = value;
          return entryData;
      }
  
      private void readEntry(EntryData entryData) throws AndrolibException {
          int specNamesId = entryData.mSpecNamesId;
          ResValue value = entryData.mValue;
  
          if (mTypeSpec.isString() && value instanceof ResFileValue) {
              value = new ResStringValue(value.toString(), ((ResFileValue) value).getRawIntValue());
          }
          if (mType == null) {
              return;
          }
  
          ResID resId = new ResID(mResId);
          ResResSpec spec;
          if (mPkg.hasResSpec(resId)) {
              spec = mPkg.getResSpec(resId);
  
              if (spec.isDummyResSpec()) {
                  removeResSpec(spec);
  
                  spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), mPkg, mTypeSpec);
                  mPkg.addResSpec(spec);
                  mTypeSpec.addResSpec(spec);
              }
          } else {
              spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), mPkg, mTypeSpec);
              mPkg.addResSpec(spec);
              mTypeSpec.addResSpec(spec);
          }
          ResResource res = new ResResource(mType, spec, value);
  
          try {
              mType.addResource(res);
              spec.addResource(res);
          } catch (AndrolibException ex) {
              if (mKeepBroken) {
                  mType.addResource(res, true);
                  spec.addResource(res, true);
                  LOGGER.warning(String.format("Duplicate Resource Detected. Ignoring duplicate: %s", res));
              } else {
                  throw ex;
              }
          }
      }
  
      private ResBagValue readComplexEntry() throws IOException, AndrolibException {
          int parent = mIn.readInt();
          int count = mIn.readInt();
  
          ResValueFactory factory = mPkg.getValueFactory();
          Duo<Integer, ResScalarValue>[] items = new Duo[count];
          ResIntBasedValue resValue;
          int resId;
  
          for (int i = 0; i < count; i++) {
              resId = mIn.readInt();
              resValue = readValue();
  
              if (!(resValue instanceof ResScalarValue)) {
                  resValue = new ResStringValue(resValue.toString(), resValue.getRawIntValue());
              }
              items[i] = new Duo<>(resId, (ResScalarValue) resValue);
          }
  
          return factory.bagFactory(parent, items, mTypeSpec);
      }
  
      private ResIntBasedValue readValue() throws IOException, AndrolibException {
  		/* size */mIn.skipCheckShort((short) 8);
  		/* zero */mIn.skipCheckByte((byte) 0);
          byte type = mIn.readByte();
          int data = mIn.readInt();
  
          return type == TypedValue.TYPE_STRING
                  ? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
                  : mPkg.getValueFactory().factory(type, data, null);
      }
  
      private ResConfigFlags readConfigFlags() throws IOException, AndrolibException {
          int size = mIn.readInt();
          int read = 28;
  
          if (size < 28) {
              throw new AndrolibException("Config size < 28");
          }
  
          boolean isInvalid = false;
  
          short mcc = mIn.readShort();
          short mnc = mIn.readShort();
  
          char[] language = this.unpackLanguageOrRegion(mIn.readByte(), mIn.readByte(), 'a');
          char[] country = this.unpackLanguageOrRegion(mIn.readByte(), mIn.readByte(), '0');
  
          byte orientation = mIn.readByte();
          byte touchscreen = mIn.readByte();
  
          int density = mIn.readUnsignedShort();
  
          byte keyboard = mIn.readByte();
          byte navigation = mIn.readByte();
          byte inputFlags = mIn.readByte();
  		/* inputPad0 */mIn.skipBytes(1);
  
          short screenWidth = mIn.readShort();
          short screenHeight = mIn.readShort();
  
          short sdkVersion = mIn.readShort();
  		/* minorVersion, now must always be 0 */mIn.skipBytes(2);
  
          byte screenLayout = 0;
          byte uiMode = 0;
          short smallestScreenWidthDp = 0;
          if (size >= 32) {
              screenLayout = mIn.readByte();
              uiMode = mIn.readByte();
              smallestScreenWidthDp = mIn.readShort();
              read = 32;
          }
  
          short screenWidthDp = 0;
          short screenHeightDp = 0;
          if (size >= 36) {
              screenWidthDp = mIn.readShort();
              screenHeightDp = mIn.readShort();
              read = 36;
          }
  
          char[] localeScript = null;
          char[] localeVariant = null;
          if (size >= 48) {
              localeScript = readScriptOrVariantChar(4).toCharArray();
              localeVariant = readScriptOrVariantChar(8).toCharArray();
              read = 48;
          }
  
          byte screenLayout2 = 0;
          byte colorMode = 0;
          if (size >= 52) {
              screenLayout2 = mIn.readByte();
              colorMode = mIn.readByte();
              mIn.skipBytes(2); // reserved padding
              read = 52;
          }
  
          if (size >= 56) {
              mIn.skipBytes(4);
              read = 56;
          }
  
          int exceedingSize = size - KNOWN_CONFIG_BYTES;
          if (exceedingSize > 0) {
              byte[] buf = new byte[exceedingSize];
              read += exceedingSize;
              mIn.readFully(buf);
              BigInteger exceedingBI = new BigInteger(1, buf);
  
              if (exceedingBI.equals(BigInteger.ZERO)) {
                  LOGGER.fine(String
                          .format("Config flags size > %d, but exceeding bytes are all zero, so it should be ok.",
                                  KNOWN_CONFIG_BYTES));
              } else {
                  LOGGER.warning(String.format("Config flags size > %d. Size = %d. Exceeding bytes: 0x%X.",
                          KNOWN_CONFIG_BYTES, size, exceedingBI));
                  isInvalid = true;
              }
          }
  
          int remainingSize = size - read;
          if (remainingSize > 0) {
              mIn.skipBytes(remainingSize);
          }
  
          return new ResConfigFlags(mcc, mnc, language, country,
                  orientation, touchscreen, density, keyboard, navigation,
                  inputFlags, screenWidth, screenHeight, sdkVersion,
                  screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
                  screenHeightDp, localeScript, localeVariant, screenLayout2,
                  colorMode, isInvalid, size);
      }
  
      private char[] unpackLanguageOrRegion(byte in0, byte in1, char base) {
          // check high bit, if so we have a packed 3 letter code
          if (((in0 >> 7) & 1) == 1) {
              int first = in1 & 0x1F;
              int second = ((in1 & 0xE0) >> 5) + ((in0 & 0x03) << 3);
              int third = (in0 & 0x7C) >> 2;
  
              // since this function handles languages & regions, we add the value(s) to the base char
              // which is usually 'a' or '0' depending on language or region.
              return new char[] { (char) (first + base), (char) (second + base), (char) (third + base) };
          }
          return new char[] { (char) in0, (char) in1 };
      }
  
      private String readScriptOrVariantChar(int length) throws IOException {
          StringBuilder string = new StringBuilder(16);
  
          while(length-- != 0) {
              short ch = mIn.readByte();
              if (ch == 0) {
                  break;
              }
              string.append((char) ch);
          }
          mIn.skipBytes(length);
  
          return string.toString();
      }
  
      private void addTypeSpec(ResTypeSpec resTypeSpec) {
          mResTypeSpecs.put(resTypeSpec.getId(), resTypeSpec);
      }
  
      private void addMissingResSpecs() throws AndrolibException {
          int resId = mResId & 0xffff0000;
  
          for (int i = 0; i < mMissingResSpecs.length; i++) {
              if (!mMissingResSpecs[i]) {
                  continue;
              }
  
              ResResSpec spec = new ResResSpec(new ResID(resId | i), "APKTOOL_DUMMY_" + Integer.toHexString(i), mPkg, mTypeSpec);
  
              // If we already have this resID dont add it again.
              if (! mPkg.hasResSpec(new ResID(resId | i))) {
                  mPkg.addResSpec(spec);
                  mTypeSpec.addResSpec(spec);
  
                  if (mType == null) {
                      mType = mPkg.getOrCreateConfig(new ResConfigFlags());
                  }
  
                  // We are going to make dummy attributes a null reference (@null) now instead of a boolean false.
                  // This is because aapt2 is much more strict when it comes to what we can put in an application.
                  ResValue value = new ResReferenceValue(mPkg, 0, "");
  
                  ResResource res = new ResResource(mType, spec, value);
                  mType.addResource(res);
                  spec.addResource(res);
              }
          }
      }
  
      private void removeResSpec(ResResSpec spec) throws AndrolibException {
          if (mPkg.hasResSpec(spec.getId())) {
              mPkg.removeResSpec(spec);
              mTypeSpec.removeResSpec(spec);
          }
      }
  
      private Header nextChunk() throws IOException {
          return mHeader = Header.read(mIn, mCountIn);
      }
  
      private void checkChunkType(int expectedType) throws AndrolibException {
          if (mHeader.type != expectedType) {
              throw new AndrolibException(String.format("Invalid chunk type: expected=0x%08x, got=0x%08x",
                      expectedType, mHeader.type));
          }
      }
  
      private void nextChunkCheckType(int expectedType) throws IOException, AndrolibException {
          nextChunk();
          checkChunkType(expectedType);
      }
  
      private final ExtDataInput mIn;
      private final ResTable mResTable;
      private final CountingInputStream mCountIn;
      private final List<FlagsOffset> mFlagsOffsets;
      private final boolean mKeepBroken;
  
      private Header mHeader;
      private StringBlock mTableStrings;
      private StringBlock mTypeNames;
      private StringBlock mSpecNames;
      private ResPackage mPkg;
      private ResTypeSpec mTypeSpec;
      private ResType mType;
      private int mResId;
      private int mTypeIdOffset = 0;
      private boolean[] mMissingResSpecs;
      private final HashMap<Integer, ResTypeSpec> mResTypeSpecs = new HashMap<>();
  
      private final static short ENTRY_FLAG_COMPLEX = 0x0001;
      private final static short ENTRY_FLAG_PUBLIC = 0x0002;
      private final static short ENTRY_FLAG_WEAK = 0x0004;
  
      public static class Header {
          public final short type;
          public final int headerSize;
          public final int chunkSize;
          public final int startPosition;
          public final int endPosition;
  
          public Header(short type, int headerSize, int chunkSize, int headerStart) {
              this.type = type;
              this.headerSize = headerSize;
              this.chunkSize = chunkSize;
              this.startPosition = headerStart;
              this.endPosition = headerStart + chunkSize;
          }
  
          public static Header read(ExtDataInput in, CountingInputStream countIn) throws IOException {
              short type;
              int start = countIn.getCount();
              try {
                  type = in.readShort();
              } catch (EOFException ex) {
                  return new Header(TYPE_NONE, 0, 0, countIn.getCount());
              }
              return new Header(type, in.readShort(), in.readInt(), start);
          }
  
          public final static short TYPE_NONE = -1;
          public final static short TYPE_STRING_POOL = 0x0001;
          public final static short TYPE_TABLE = 0x0002;
          public final static short TYPE_XML = 0x0003;
  
          public final static short XML_TYPE_PACKAGE = 0x0200;
          public final static short XML_TYPE_TYPE = 0x0201;
          public final static short XML_TYPE_SPEC_TYPE = 0x0202;
          public final static short XML_TYPE_LIBRARY = 0x0203;
          public final static short XML_TYPE_OVERLAY = 0x0204;
          public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
          public final static short XML_TYPE_STAGED_ALIAS = 0x0206;
      }
  
      public static class FlagsOffset {
          public final int offset;
          public final int count;
  
          public FlagsOffset(int offset, int count) {
              this.offset = offset;
              this.count = count;
          }
      }
  
      private class EntryData {
          public short mFlags;
          public int mSpecNamesId;
          public ResValue mValue;
      }
  
      private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
      private static final int KNOWN_CONFIG_BYTES = 56;
  
      public static class ARSCData {
  
          public ARSCData(ResPackage[] packages, FlagsOffset[] flagsOffsets, ResTable resTable) {
              mPackages = packages;
              mFlagsOffsets = flagsOffsets;
              mResTable = resTable;
          }
  
          public FlagsOffset[] getFlagsOffsets() {
              return mFlagsOffsets;
          }
  
          public ResPackage[] getPackages() {
              return mPackages;
          }
  
          public ResPackage getOnePackage() throws AndrolibException {
              if (mPackages.length <= 0) {
                  throw new AndrolibException("Arsc file contains zero packages");
              } else if (mPackages.length != 1) {
                  int id = findPackageWithMostResSpecs();
                  LOGGER.info("Arsc file contains multiple packages. Using package "
                          + mPackages[id].getName() + " as default.");
  
                  return mPackages[id];
              }
              return mPackages[0];
          }
  
          public int findPackageWithMostResSpecs() {
              int count = mPackages[0].getResSpecCount();
              int id = 0;
  
              for (int i = 0; i < mPackages.length; i++) {
                  if (mPackages[i].getResSpecCount() >= count) {
                      count = mPackages[i].getResSpecCount();
                      id = i;
                  }
              }
              return id;
          }
  
          public ResTable getResTable() {
              return mResTable;
          }
  
          private final ResPackage[] mPackages;
          private final FlagsOffset[] mFlagsOffsets;
          private final ResTable mResTable;
      }
  }
  