1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.apksig.apk; 18 19 import com.android.apksig.internal.apk.AndroidBinXmlParser; 20 import com.android.apksig.internal.apk.v1.V1SchemeVerifier; 21 import com.android.apksig.internal.util.Pair; 22 import com.android.apksig.internal.zip.CentralDirectoryRecord; 23 import com.android.apksig.internal.zip.LocalFileRecord; 24 import com.android.apksig.internal.zip.ZipUtils; 25 import com.android.apksig.util.DataSource; 26 import com.android.apksig.zip.ZipFormatException; 27 import java.io.IOException; 28 import java.nio.ByteBuffer; 29 import java.nio.ByteOrder; 30 import java.util.Arrays; 31 import java.util.Comparator; 32 import java.util.List; 33 34 /** 35 * APK utilities. 36 */ 37 public abstract class ApkUtils { 38 39 /** 40 * Name of the Android manifest ZIP entry in APKs. 41 */ 42 public static final String ANDROID_MANIFEST_ZIP_ENTRY_NAME = "AndroidManifest.xml"; 43 ApkUtils()44 private ApkUtils() {} 45 46 /** 47 * Finds the main ZIP sections of the provided APK. 48 * 49 * @throws IOException if an I/O error occurred while reading the APK 50 * @throws ZipFormatException if the APK is malformed 51 */ findZipSections(DataSource apk)52 public static ZipSections findZipSections(DataSource apk) 53 throws IOException, ZipFormatException { 54 Pair<ByteBuffer, Long> eocdAndOffsetInFile = 55 ZipUtils.findZipEndOfCentralDirectoryRecord(apk); 56 if (eocdAndOffsetInFile == null) { 57 throw new ZipFormatException("ZIP End of Central Directory record not found"); 58 } 59 60 ByteBuffer eocdBuf = eocdAndOffsetInFile.getFirst(); 61 long eocdOffset = eocdAndOffsetInFile.getSecond(); 62 eocdBuf.order(ByteOrder.LITTLE_ENDIAN); 63 long cdStartOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocdBuf); 64 if (cdStartOffset > eocdOffset) { 65 throw new ZipFormatException( 66 "ZIP Central Directory start offset out of range: " + cdStartOffset 67 + ". ZIP End of Central Directory offset: " + eocdOffset); 68 } 69 70 long cdSizeBytes = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocdBuf); 71 long cdEndOffset = cdStartOffset + cdSizeBytes; 72 if (cdEndOffset > eocdOffset) { 73 throw new ZipFormatException( 74 "ZIP Central Directory overlaps with End of Central Directory" 75 + ". CD end: " + cdEndOffset 76 + ", EoCD start: " + eocdOffset); 77 } 78 79 int cdRecordCount = ZipUtils.getZipEocdCentralDirectoryTotalRecordCount(eocdBuf); 80 81 return new ZipSections( 82 cdStartOffset, 83 cdSizeBytes, 84 cdRecordCount, 85 eocdOffset, 86 eocdBuf); 87 } 88 89 /** 90 * Information about the ZIP sections of an APK. 91 */ 92 public static class ZipSections { 93 private final long mCentralDirectoryOffset; 94 private final long mCentralDirectorySizeBytes; 95 private final int mCentralDirectoryRecordCount; 96 private final long mEocdOffset; 97 private final ByteBuffer mEocd; 98 ZipSections( long centralDirectoryOffset, long centralDirectorySizeBytes, int centralDirectoryRecordCount, long eocdOffset, ByteBuffer eocd)99 public ZipSections( 100 long centralDirectoryOffset, 101 long centralDirectorySizeBytes, 102 int centralDirectoryRecordCount, 103 long eocdOffset, 104 ByteBuffer eocd) { 105 mCentralDirectoryOffset = centralDirectoryOffset; 106 mCentralDirectorySizeBytes = centralDirectorySizeBytes; 107 mCentralDirectoryRecordCount = centralDirectoryRecordCount; 108 mEocdOffset = eocdOffset; 109 mEocd = eocd; 110 } 111 112 /** 113 * Returns the start offset of the ZIP Central Directory. This value is taken from the 114 * ZIP End of Central Directory record. 115 */ getZipCentralDirectoryOffset()116 public long getZipCentralDirectoryOffset() { 117 return mCentralDirectoryOffset; 118 } 119 120 /** 121 * Returns the size (in bytes) of the ZIP Central Directory. This value is taken from the 122 * ZIP End of Central Directory record. 123 */ getZipCentralDirectorySizeBytes()124 public long getZipCentralDirectorySizeBytes() { 125 return mCentralDirectorySizeBytes; 126 } 127 128 /** 129 * Returns the number of records in the ZIP Central Directory. This value is taken from the 130 * ZIP End of Central Directory record. 131 */ getZipCentralDirectoryRecordCount()132 public int getZipCentralDirectoryRecordCount() { 133 return mCentralDirectoryRecordCount; 134 } 135 136 /** 137 * Returns the start offset of the ZIP End of Central Directory record. The record extends 138 * until the very end of the APK. 139 */ getZipEndOfCentralDirectoryOffset()140 public long getZipEndOfCentralDirectoryOffset() { 141 return mEocdOffset; 142 } 143 144 /** 145 * Returns the contents of the ZIP End of Central Directory. 146 */ getZipEndOfCentralDirectory()147 public ByteBuffer getZipEndOfCentralDirectory() { 148 return mEocd; 149 } 150 } 151 152 /** 153 * Sets the offset of the start of the ZIP Central Directory in the APK's ZIP End of Central 154 * Directory record. 155 * 156 * @param zipEndOfCentralDirectory APK's ZIP End of Central Directory record 157 * @param offset offset of the ZIP Central Directory relative to the start of the archive. Must 158 * be between {@code 0} and {@code 2^32 - 1} inclusive. 159 */ setZipEocdCentralDirectoryOffset( ByteBuffer zipEndOfCentralDirectory, long offset)160 public static void setZipEocdCentralDirectoryOffset( 161 ByteBuffer zipEndOfCentralDirectory, long offset) { 162 ByteBuffer eocd = zipEndOfCentralDirectory.slice(); 163 eocd.order(ByteOrder.LITTLE_ENDIAN); 164 ZipUtils.setZipEocdCentralDirectoryOffset(eocd, offset); 165 } 166 167 // See https://source.android.com/security/apksigning/v2.html 168 private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; 169 private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; 170 private static final int APK_SIG_BLOCK_MIN_SIZE = 32; 171 172 /** 173 * Returns the APK Signing Block of the provided APK. 174 * 175 * @throws IOException if an I/O error occurs 176 * @throws ApkSigningBlockNotFoundException if there is no APK Signing Block in the APK 177 * 178 * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a> 179 */ findApkSigningBlock(DataSource apk, ZipSections zipSections)180 public static ApkSigningBlock findApkSigningBlock(DataSource apk, ZipSections zipSections) 181 throws IOException, ApkSigningBlockNotFoundException { 182 // FORMAT (see https://source.android.com/security/apksigning/v2.html): 183 // OFFSET DATA TYPE DESCRIPTION 184 // * @+0 bytes uint64: size in bytes (excluding this field) 185 // * @+8 bytes payload 186 // * @-24 bytes uint64: size in bytes (same as the one above) 187 // * @-16 bytes uint128: magic 188 189 long centralDirStartOffset = zipSections.getZipCentralDirectoryOffset(); 190 long centralDirEndOffset = 191 centralDirStartOffset + zipSections.getZipCentralDirectorySizeBytes(); 192 long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset(); 193 if (centralDirEndOffset != eocdStartOffset) { 194 throw new ApkSigningBlockNotFoundException( 195 "ZIP Central Directory is not immediately followed by End of Central Directory" 196 + ". CD end: " + centralDirEndOffset 197 + ", EoCD start: " + eocdStartOffset); 198 } 199 200 if (centralDirStartOffset < APK_SIG_BLOCK_MIN_SIZE) { 201 throw new ApkSigningBlockNotFoundException( 202 "APK too small for APK Signing Block. ZIP Central Directory offset: " 203 + centralDirStartOffset); 204 } 205 // Read the magic and offset in file from the footer section of the block: 206 // * uint64: size of block 207 // * 16 bytes: magic 208 ByteBuffer footer = apk.getByteBuffer(centralDirStartOffset - 24, 24); 209 footer.order(ByteOrder.LITTLE_ENDIAN); 210 if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) 211 || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { 212 throw new ApkSigningBlockNotFoundException( 213 "No APK Signing Block before ZIP Central Directory"); 214 } 215 // Read and compare size fields 216 long apkSigBlockSizeInFooter = footer.getLong(0); 217 if ((apkSigBlockSizeInFooter < footer.capacity()) 218 || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { 219 throw new ApkSigningBlockNotFoundException( 220 "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); 221 } 222 int totalSize = (int) (apkSigBlockSizeInFooter + 8); 223 long apkSigBlockOffset = centralDirStartOffset - totalSize; 224 if (apkSigBlockOffset < 0) { 225 throw new ApkSigningBlockNotFoundException( 226 "APK Signing Block offset out of range: " + apkSigBlockOffset); 227 } 228 ByteBuffer apkSigBlock = apk.getByteBuffer(apkSigBlockOffset, 8); 229 apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); 230 long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); 231 if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { 232 throw new ApkSigningBlockNotFoundException( 233 "APK Signing Block sizes in header and footer do not match: " 234 + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); 235 } 236 return new ApkSigningBlock(apkSigBlockOffset, apk.slice(apkSigBlockOffset, totalSize)); 237 } 238 239 /** 240 * Information about the location of the APK Signing Block inside an APK. 241 */ 242 public static class ApkSigningBlock { 243 private final long mStartOffsetInApk; 244 private final DataSource mContents; 245 246 /** 247 * Constructs a new {@code ApkSigningBlock}. 248 * 249 * @param startOffsetInApk start offset (in bytes, relative to start of file) of the APK 250 * Signing Block inside the APK file 251 * @param contents contents of the APK Signing Block 252 */ ApkSigningBlock(long startOffsetInApk, DataSource contents)253 public ApkSigningBlock(long startOffsetInApk, DataSource contents) { 254 mStartOffsetInApk = startOffsetInApk; 255 mContents = contents; 256 } 257 258 /** 259 * Returns the start offset (in bytes, relative to start of file) of the APK Signing Block. 260 */ getStartOffset()261 public long getStartOffset() { 262 return mStartOffsetInApk; 263 } 264 265 /** 266 * Returns the data source which provides the full contents of the APK Signing Block, 267 * including its footer. 268 */ getContents()269 public DataSource getContents() { 270 return mContents; 271 } 272 } 273 274 /** 275 * Returns the contents of the APK's {@code AndroidManifest.xml}. 276 * 277 * @throws IOException if an I/O error occurs while reading the APK 278 * @throws ApkFormatException if the APK is malformed 279 */ getAndroidManifest(DataSource apk)280 public static ByteBuffer getAndroidManifest(DataSource apk) 281 throws IOException, ApkFormatException { 282 ZipSections zipSections; 283 try { 284 zipSections = findZipSections(apk); 285 } catch (ZipFormatException e) { 286 throw new ApkFormatException("Not a valid ZIP archive", e); 287 } 288 List<CentralDirectoryRecord> cdRecords = 289 V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections); 290 CentralDirectoryRecord androidManifestCdRecord = null; 291 for (CentralDirectoryRecord cdRecord : cdRecords) { 292 if (ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(cdRecord.getName())) { 293 androidManifestCdRecord = cdRecord; 294 break; 295 } 296 } 297 if (androidManifestCdRecord == null) { 298 throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME); 299 } 300 DataSource lfhSection = apk.slice(0, zipSections.getZipCentralDirectoryOffset()); 301 302 try { 303 return ByteBuffer.wrap( 304 LocalFileRecord.getUncompressedData( 305 lfhSection, androidManifestCdRecord, lfhSection.size())); 306 } catch (ZipFormatException e) { 307 throw new ApkFormatException("Failed to read " + ANDROID_MANIFEST_ZIP_ENTRY_NAME, e); 308 } 309 } 310 311 /** 312 * Android resource ID of the {@code android:minSdkVersion} attribute in AndroidManifest.xml. 313 */ 314 private static final int MIN_SDK_VERSION_ATTR_ID = 0x0101020c; 315 316 /** 317 * Android resource ID of the {@code android:debuggable} attribute in AndroidManifest.xml. 318 */ 319 private static final int DEBUGGABLE_ATTR_ID = 0x0101000f; 320 321 /** 322 * Returns the lowest Android platform version (API Level) supported by an APK with the 323 * provided {@code AndroidManifest.xml}. 324 * 325 * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android 326 * resource format 327 * 328 * @throws MinSdkVersionException if an error occurred while determining the API Level 329 */ getMinSdkVersionFromBinaryAndroidManifest( ByteBuffer androidManifestContents)330 public static int getMinSdkVersionFromBinaryAndroidManifest( 331 ByteBuffer androidManifestContents) throws MinSdkVersionException { 332 // IMPLEMENTATION NOTE: Minimum supported Android platform version number is declared using 333 // uses-sdk elements which are children of the top-level manifest element. uses-sdk element 334 // declares the minimum supported platform version using the android:minSdkVersion attribute 335 // whose default value is 1. 336 // For each encountered uses-sdk element, the Android runtime checks that its minSdkVersion 337 // is not higher than the runtime's API Level and rejects APKs if it is higher. Thus, the 338 // effective minSdkVersion value is the maximum over the encountered minSdkVersion values. 339 340 try { 341 // If no uses-sdk elements are encountered, Android accepts the APK. We treat this 342 // scenario as though the minimum supported API Level is 1. 343 int result = 1; 344 345 AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); 346 int eventType = parser.getEventType(); 347 while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { 348 if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) 349 && (parser.getDepth() == 2) 350 && ("uses-sdk".equals(parser.getName())) 351 && (parser.getNamespace().isEmpty())) { 352 // In each uses-sdk element, minSdkVersion defaults to 1 353 int minSdkVersion = 1; 354 for (int i = 0; i < parser.getAttributeCount(); i++) { 355 if (parser.getAttributeNameResourceId(i) == MIN_SDK_VERSION_ATTR_ID) { 356 int valueType = parser.getAttributeValueType(i); 357 switch (valueType) { 358 case AndroidBinXmlParser.VALUE_TYPE_INT: 359 minSdkVersion = parser.getAttributeIntValue(i); 360 break; 361 case AndroidBinXmlParser.VALUE_TYPE_STRING: 362 minSdkVersion = 363 getMinSdkVersionForCodename( 364 parser.getAttributeStringValue(i)); 365 break; 366 default: 367 throw new MinSdkVersionException( 368 "Unable to determine APK's minimum supported Android" 369 + ": unsupported value type in " 370 + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s" 371 + " minSdkVersion" 372 + ". Only integer values supported."); 373 } 374 break; 375 } 376 } 377 result = Math.max(result, minSdkVersion); 378 } 379 eventType = parser.next(); 380 } 381 382 return result; 383 } catch (AndroidBinXmlParser.XmlParserException e) { 384 throw new MinSdkVersionException( 385 "Unable to determine APK's minimum supported Android platform version" 386 + ": malformed binary resource: " + ANDROID_MANIFEST_ZIP_ENTRY_NAME, 387 e); 388 } 389 } 390 391 private static class CodenamesLazyInitializer { 392 393 /** 394 * List of platform codename (first letter of) to API Level mappings. The list must be 395 * sorted by the first letter. For codenames not in the list, the assumption is that the API 396 * Level is incremented by one for every increase in the codename's first letter. 397 */ 398 @SuppressWarnings({"rawtypes", "unchecked"}) 399 private static final Pair<Character, Integer>[] SORTED_CODENAMES_FIRST_CHAR_TO_API_LEVEL = 400 new Pair[] { 401 Pair.of('C', 2), 402 Pair.of('D', 3), 403 Pair.of('E', 4), 404 Pair.of('F', 7), 405 Pair.of('G', 8), 406 Pair.of('H', 10), 407 Pair.of('I', 13), 408 Pair.of('J', 15), 409 Pair.of('K', 18), 410 Pair.of('L', 20), 411 Pair.of('M', 22), 412 Pair.of('N', 23), 413 Pair.of('O', 25), 414 }; 415 416 private static final Comparator<Pair<Character, Integer>> CODENAME_FIRST_CHAR_COMPARATOR = 417 new ByFirstComparator(); 418 419 private static class ByFirstComparator implements Comparator<Pair<Character, Integer>> { 420 @Override compare(Pair<Character, Integer> o1, Pair<Character, Integer> o2)421 public int compare(Pair<Character, Integer> o1, Pair<Character, Integer> o2) { 422 char c1 = o1.getFirst(); 423 char c2 = o2.getFirst(); 424 return c1 - c2; 425 } 426 } 427 } 428 429 /** 430 * Returns the API Level corresponding to the provided platform codename. 431 * 432 * <p>This method is pessimistic. It returns a value one lower than the API Level with which the 433 * platform is actually released (e.g., 23 for N which was released as API Level 24). This is 434 * because new features which first appear in an API Level are not available in the early days 435 * of that platform version's existence, when the platform only has a codename. Moreover, this 436 * method currently doesn't differentiate between initial and MR releases, meaning API Level 437 * returned for MR releases may be more than one lower than the API Level with which the 438 * platform version is actually released. 439 * 440 * @throws CodenameMinSdkVersionException if the {@code codename} is not supported 441 */ getMinSdkVersionForCodename(String codename)442 static int getMinSdkVersionForCodename(String codename) throws CodenameMinSdkVersionException { 443 char firstChar = codename.isEmpty() ? ' ' : codename.charAt(0); 444 // Codenames are case-sensitive. Only codenames starting with A-Z are supported for now. 445 // We only look at the first letter of the codename as this is the most important letter. 446 if ((firstChar >= 'A') && (firstChar <= 'Z')) { 447 Pair<Character, Integer>[] sortedCodenamesFirstCharToApiLevel = 448 CodenamesLazyInitializer.SORTED_CODENAMES_FIRST_CHAR_TO_API_LEVEL; 449 int searchResult = 450 Arrays.binarySearch( 451 sortedCodenamesFirstCharToApiLevel, 452 Pair.of(firstChar, null), // second element of the pair is ignored here 453 CodenamesLazyInitializer.CODENAME_FIRST_CHAR_COMPARATOR); 454 if (searchResult >= 0) { 455 // Exact match -- searchResult is the index of the matching element 456 return sortedCodenamesFirstCharToApiLevel[searchResult].getSecond(); 457 } 458 // Not an exact match -- searchResult is negative and is -(insertion index) - 1. 459 // The element at insertionIndex - 1 (if present) is smaller than firstChar and the 460 // element at insertionIndex (if present) is greater than firstChar. 461 int insertionIndex = -1 - searchResult; // insertionIndex is in [0; array length] 462 if (insertionIndex == 0) { 463 // 'A' or 'B' -- never released to public 464 return 1; 465 } else { 466 // The element at insertionIndex - 1 is the newest older codename. 467 // API Level bumped by at least 1 for every change in the first letter of codename 468 Pair<Character, Integer> newestOlderCodenameMapping = 469 sortedCodenamesFirstCharToApiLevel[insertionIndex - 1]; 470 char newestOlderCodenameFirstChar = newestOlderCodenameMapping.getFirst(); 471 int newestOlderCodenameApiLevel = newestOlderCodenameMapping.getSecond(); 472 return newestOlderCodenameApiLevel + (firstChar - newestOlderCodenameFirstChar); 473 } 474 } 475 476 throw new CodenameMinSdkVersionException( 477 "Unable to determine APK's minimum supported Android platform version" 478 + " : Unsupported codename in " + ANDROID_MANIFEST_ZIP_ENTRY_NAME 479 + "'s minSdkVersion: \"" + codename + "\"", 480 codename); 481 } 482 483 /** 484 * Returns {@code true} if the APK is debuggable according to its {@code AndroidManifest.xml}. 485 * See the {@code android:debuggable} attribute of the {@code application} element. 486 * 487 * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android 488 * resource format 489 * 490 * @throws ApkFormatException if the manifest is malformed 491 */ getDebuggableFromBinaryAndroidManifest( ByteBuffer androidManifestContents)492 public static boolean getDebuggableFromBinaryAndroidManifest( 493 ByteBuffer androidManifestContents) throws ApkFormatException { 494 // IMPLEMENTATION NOTE: Whether the package is debuggable is declared using the first 495 // "application" element which is a child of the top-level manifest element. The debuggable 496 // attribute of this application element is coerced to a boolean value. If there is no 497 // application element or if it doesn't declare the debuggable attribute, the package is 498 // considered not debuggable. 499 500 try { 501 AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); 502 int eventType = parser.getEventType(); 503 while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { 504 if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) 505 && (parser.getDepth() == 2) 506 && ("application".equals(parser.getName())) 507 && (parser.getNamespace().isEmpty())) { 508 for (int i = 0; i < parser.getAttributeCount(); i++) { 509 if (parser.getAttributeNameResourceId(i) == DEBUGGABLE_ATTR_ID) { 510 int valueType = parser.getAttributeValueType(i); 511 switch (valueType) { 512 case AndroidBinXmlParser.VALUE_TYPE_BOOLEAN: 513 case AndroidBinXmlParser.VALUE_TYPE_STRING: 514 case AndroidBinXmlParser.VALUE_TYPE_INT: 515 String value = parser.getAttributeStringValue(i); 516 return ("true".equals(value)) 517 || ("TRUE".equals(value)) 518 || ("1".equals(value)); 519 case AndroidBinXmlParser.VALUE_TYPE_REFERENCE: 520 // References to resources are not supported on purpose. The 521 // reason is that the resolved value depends on the resource 522 // configuration (e.g, MNC/MCC, locale, screen density) used 523 // at resolution time. As a result, the same APK may appear as 524 // debuggable in one situation and as non-debuggable in another 525 // situation. Such APKs may put users at risk. 526 throw new ApkFormatException( 527 "Unable to determine whether APK is debuggable" 528 + ": " + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s" 529 + " android:debuggable attribute references a" 530 + " resource. References are not supported for" 531 + " security reasons. Only constant boolean," 532 + " string and int values are supported."); 533 default: 534 throw new ApkFormatException( 535 "Unable to determine whether APK is debuggable" 536 + ": " + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s" 537 + " android:debuggable attribute uses" 538 + " unsupported value type. Only boolean," 539 + " string and int values are supported."); 540 } 541 } 542 } 543 // This application element does not declare the debuggable attribute 544 return false; 545 } 546 eventType = parser.next(); 547 } 548 549 // No application element found 550 return false; 551 } catch (AndroidBinXmlParser.XmlParserException e) { 552 throw new ApkFormatException( 553 "Unable to determine whether APK is debuggable: malformed binary resource: " 554 + ANDROID_MANIFEST_ZIP_ENTRY_NAME, 555 e); 556 } 557 } 558 559 /** 560 * Returns the package name of the APK according to its {@code AndroidManifest.xml} or 561 * {@code null} if package name is not declared. See the {@code package} attribute of the 562 * {@code manifest} element. 563 * 564 * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android 565 * resource format 566 * 567 * @throws ApkFormatException if the manifest is malformed 568 */ getPackageNameFromBinaryAndroidManifest( ByteBuffer androidManifestContents)569 public static String getPackageNameFromBinaryAndroidManifest( 570 ByteBuffer androidManifestContents) throws ApkFormatException { 571 // IMPLEMENTATION NOTE: Package name is declared as the "package" attribute of the top-level 572 // manifest element. Interestingly, as opposed to most other attributes, Android Package 573 // Manager looks up this attribute by its name rather than by its resource ID. 574 575 try { 576 AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); 577 int eventType = parser.getEventType(); 578 while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { 579 if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) 580 && (parser.getDepth() == 1) 581 && ("manifest".equals(parser.getName())) 582 && (parser.getNamespace().isEmpty())) { 583 for (int i = 0; i < parser.getAttributeCount(); i++) { 584 if ("package".equals(parser.getAttributeName(i)) 585 && (parser.getNamespace().isEmpty())) { 586 return parser.getAttributeStringValue(i); 587 } 588 } 589 // No "package" attribute found 590 return null; 591 } 592 eventType = parser.next(); 593 } 594 595 // No manifest element found 596 return null; 597 } catch (AndroidBinXmlParser.XmlParserException e) { 598 throw new ApkFormatException( 599 "Unable to determine APK package name: malformed binary resource: " 600 + ANDROID_MANIFEST_ZIP_ENTRY_NAME, 601 e); 602 } 603 } 604 } 605