1 /* 2 * Copyright (C) 2019 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 android.content.pm.parsing; 18 19 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; 20 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; 21 import static android.content.pm.parsing.ParsingPackageUtils.validateName; 22 import static android.content.pm.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; 23 import static android.content.pm.parsing.ParsingUtils.DEFAULT_MIN_SDK_VERSION; 24 import static android.content.pm.parsing.ParsingUtils.DEFAULT_TARGET_SDK_VERSION; 25 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; 26 27 import android.annotation.NonNull; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageManager; 30 import android.content.pm.PackageParser; 31 import android.content.pm.VerifierInfo; 32 import android.content.pm.parsing.result.ParseInput; 33 import android.content.pm.parsing.result.ParseResult; 34 import android.content.res.ApkAssets; 35 import android.content.res.XmlResourceParser; 36 import android.os.Trace; 37 import android.text.TextUtils; 38 import android.util.ArrayMap; 39 import android.util.AttributeSet; 40 import android.util.Pair; 41 import android.util.Slog; 42 43 import com.android.internal.util.ArrayUtils; 44 45 import libcore.io.IoUtils; 46 47 import org.xmlpull.v1.XmlPullParser; 48 import org.xmlpull.v1.XmlPullParserException; 49 50 import java.io.File; 51 import java.io.FileDescriptor; 52 import java.io.IOException; 53 import java.security.PublicKey; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.Comparator; 57 import java.util.List; 58 import java.util.Objects; 59 60 /** @hide */ 61 public class ApkLiteParseUtils { 62 63 private static final String TAG = ParsingUtils.TAG; 64 65 private static final int PARSE_DEFAULT_INSTALL_LOCATION = 66 PackageInfo.INSTALL_LOCATION_UNSPECIFIED; 67 68 private static final Comparator<String> sSplitNameComparator = new SplitNameComparator(); 69 70 public static final String APK_FILE_EXTENSION = ".apk"; 71 72 /** 73 * Parse only lightweight details about the package at the given location. 74 * Automatically detects if the package is a monolithic style (single APK 75 * file) or cluster style (directory of APKs). 76 * <p> 77 * This performs validity checking on cluster style packages, such as 78 * requiring identical package name and version codes, a single base APK, 79 * and unique split names. 80 * 81 * @see PackageParser#parsePackage(File, int) 82 */ parsePackageLite(ParseInput input, File packageFile, int flags)83 public static ParseResult<PackageLite> parsePackageLite(ParseInput input, 84 File packageFile, int flags) { 85 if (packageFile.isDirectory()) { 86 return parseClusterPackageLite(input, packageFile, flags); 87 } else { 88 return parseMonolithicPackageLite(input, packageFile, flags); 89 } 90 } 91 92 /** 93 * Parse lightweight details about a single APK files. 94 */ parseMonolithicPackageLite(ParseInput input, File packageFile, int flags)95 public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input, 96 File packageFile, int flags) { 97 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); 98 try { 99 final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags); 100 if (result.isError()) { 101 return input.error(result); 102 } 103 104 final ApkLite baseApk = result.getResult(); 105 final String packagePath = packageFile.getAbsolutePath(); 106 return input.success( 107 new PackageLite(packagePath, baseApk.getPath(), baseApk, null, 108 null, null, null, null, null, baseApk.getTargetSdkVersion())); 109 } finally { 110 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 111 } 112 } 113 114 /** 115 * Parse lightweight details about a directory of APKs. 116 */ parseClusterPackageLite(ParseInput input, File packageDir, int flags)117 public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input, 118 File packageDir, int flags) { 119 final File[] files = packageDir.listFiles(); 120 if (ArrayUtils.isEmpty(files)) { 121 return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, 122 "No packages found in split"); 123 } 124 // Apk directory is directly nested under the current directory 125 if (files.length == 1 && files[0].isDirectory()) { 126 return parseClusterPackageLite(input, files[0], flags); 127 } 128 129 String packageName = null; 130 int versionCode = 0; 131 132 final ArrayMap<String, ApkLite> apks = new ArrayMap<>(); 133 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); 134 try { 135 for (File file : files) { 136 if (isApkFile(file)) { 137 final ParseResult<ApkLite> result = parseApkLite(input, file, flags); 138 if (result.isError()) { 139 return input.error(result); 140 } 141 142 final ApkLite lite = result.getResult(); 143 // Assert that all package names and version codes are 144 // consistent with the first one we encounter. 145 if (packageName == null) { 146 packageName = lite.getPackageName(); 147 versionCode = lite.getVersionCode(); 148 } else { 149 if (!packageName.equals(lite.getPackageName())) { 150 return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, 151 "Inconsistent package " + lite.getPackageName() + " in " + file 152 + "; expected " + packageName); 153 } 154 if (versionCode != lite.getVersionCode()) { 155 return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, 156 "Inconsistent version " + lite.getVersionCode() + " in " + file 157 + "; expected " + versionCode); 158 } 159 } 160 161 // Assert that each split is defined only oncuses-static-libe 162 if (apks.put(lite.getSplitName(), lite) != null) { 163 return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, 164 "Split name " + lite.getSplitName() 165 + " defined more than once; most recent was " + file); 166 } 167 } 168 } 169 } finally { 170 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 171 } 172 173 final ApkLite baseApk = apks.remove(null); 174 return composePackageLiteFromApks(input, packageDir, baseApk, apks); 175 } 176 177 /** 178 * Utility method that retrieves lightweight details about the package by given location, 179 * base APK, and split APKs. 180 * 181 * @param packageDir Path to the package 182 * @param baseApk Parsed base APK 183 * @param splitApks Parsed split APKs 184 * @return PackageLite 185 */ composePackageLiteFromApks(ParseInput input, File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks)186 public static ParseResult<PackageLite> composePackageLiteFromApks(ParseInput input, 187 File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks) { 188 return composePackageLiteFromApks(input, packageDir, baseApk, splitApks, false); 189 } 190 191 /** 192 * Utility method that retrieves lightweight details about the package by given location, 193 * base APK, and split APKs. 194 * 195 * @param packageDir Path to the package 196 * @param baseApk Parsed base APK 197 * @param splitApks Parsed split APKs 198 * @param apkRenamed Indicate whether the APKs are renamed after parsed. 199 * @return PackageLite 200 */ composePackageLiteFromApks( ParseInput input, File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks, boolean apkRenamed)201 public static ParseResult<PackageLite> composePackageLiteFromApks( 202 ParseInput input, File packageDir, ApkLite baseApk, 203 ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) { 204 if (baseApk == null) { 205 return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, 206 "Missing base APK in " + packageDir); 207 } 208 // Always apply deterministic ordering based on splitName 209 final int size = ArrayUtils.size(splitApks); 210 211 String[] splitNames = null; 212 boolean[] isFeatureSplits = null; 213 String[] usesSplitNames = null; 214 String[] configForSplits = null; 215 String[] splitCodePaths = null; 216 int[] splitRevisionCodes = null; 217 if (size > 0) { 218 splitNames = new String[size]; 219 isFeatureSplits = new boolean[size]; 220 usesSplitNames = new String[size]; 221 configForSplits = new String[size]; 222 splitCodePaths = new String[size]; 223 splitRevisionCodes = new int[size]; 224 225 splitNames = splitApks.keySet().toArray(splitNames); 226 Arrays.sort(splitNames, sSplitNameComparator); 227 228 for (int i = 0; i < size; i++) { 229 final ApkLite apk = splitApks.get(splitNames[i]); 230 usesSplitNames[i] = apk.getUsesSplitName(); 231 isFeatureSplits[i] = apk.isFeatureSplit(); 232 configForSplits[i] = apk.getConfigForSplit(); 233 splitCodePaths[i] = apkRenamed ? new File(packageDir, 234 splitNameToFileName(apk)).getAbsolutePath() : apk.getPath(); 235 splitRevisionCodes[i] = apk.getRevisionCode(); 236 } 237 } 238 239 final String codePath = packageDir.getAbsolutePath(); 240 final String baseCodePath = apkRenamed ? new File(packageDir, 241 splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath(); 242 return input.success( 243 new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits, 244 usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes, 245 baseApk.getTargetSdkVersion())); 246 } 247 248 /** 249 * Utility method that retrieves canonical file name by given split name from parsed APK. 250 * 251 * @param apk Parsed APK 252 * @return The canonical file name 253 */ splitNameToFileName(@onNull ApkLite apk)254 public static String splitNameToFileName(@NonNull ApkLite apk) { 255 Objects.requireNonNull(apk); 256 final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName(); 257 return fileName + APK_FILE_EXTENSION; 258 } 259 260 /** 261 * Utility method that retrieves lightweight details about a single APK 262 * file, including package name, split name, and install location. 263 * 264 * @param apkFile path to a single APK 265 * @param flags optional parse flags, such as 266 * {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES} 267 */ parseApkLite(ParseInput input, File apkFile, int flags)268 public static ParseResult<ApkLite> parseApkLite(ParseInput input, File apkFile, int flags) { 269 return parseApkLiteInner(input, apkFile, null, null, flags); 270 } 271 272 /** 273 * Utility method that retrieves lightweight details about a single APK 274 * file, including package name, split name, and install location. 275 * 276 * @param fd already open file descriptor of an apk file 277 * @param debugPathName arbitrary text name for this file, for debug output 278 * @param flags optional parse flags, such as 279 * {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES} 280 */ parseApkLite(ParseInput input, FileDescriptor fd, String debugPathName, int flags)281 public static ParseResult<ApkLite> parseApkLite(ParseInput input, 282 FileDescriptor fd, String debugPathName, int flags) { 283 return parseApkLiteInner(input, null, fd, debugPathName, flags); 284 } 285 parseApkLiteInner(ParseInput input, File apkFile, FileDescriptor fd, String debugPathName, int flags)286 private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input, 287 File apkFile, FileDescriptor fd, String debugPathName, int flags) { 288 final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); 289 290 XmlResourceParser parser = null; 291 ApkAssets apkAssets = null; 292 try { 293 try { 294 apkAssets = fd != null 295 ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */) 296 : ApkAssets.loadFromPath(apkPath); 297 } catch (IOException e) { 298 return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK, 299 "Failed to parse " + apkPath, e); 300 } 301 302 parser = apkAssets.openXml(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); 303 304 final PackageParser.SigningDetails signingDetails; 305 if ((flags & ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES) != 0) { 306 final boolean skipVerify = (flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0; 307 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); 308 try { 309 ParseResult<PackageParser.SigningDetails> result = 310 ParsingPackageUtils.getSigningDetails(input, 311 apkFile.getAbsolutePath(), skipVerify, false, 312 PackageParser.SigningDetails.UNKNOWN, 313 DEFAULT_TARGET_SDK_VERSION); 314 if (result.isError()) { 315 return input.error(result); 316 } 317 signingDetails = result.getResult(); 318 } finally { 319 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); 320 } 321 } else { 322 signingDetails = PackageParser.SigningDetails.UNKNOWN; 323 } 324 325 return parseApkLite(input, apkPath, parser, signingDetails); 326 } catch (XmlPullParserException | IOException | RuntimeException e) { 327 Slog.w(TAG, "Failed to parse " + apkPath, e); 328 return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, 329 "Failed to parse " + apkPath, e); 330 } finally { 331 IoUtils.closeQuietly(parser); 332 if (apkAssets != null) { 333 try { 334 apkAssets.close(); 335 } catch (Throwable ignored) { 336 } 337 } 338 // TODO(b/72056911): Implement AutoCloseable on ApkAssets. 339 } 340 } 341 parseApkLite(ParseInput input, String codePath, XmlResourceParser parser, PackageParser.SigningDetails signingDetails)342 private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath, 343 XmlResourceParser parser, PackageParser.SigningDetails signingDetails) 344 throws IOException, XmlPullParserException { 345 ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser); 346 if (result.isError()) { 347 return input.error(result); 348 } 349 350 Pair<String, String> packageSplit = result.getResult(); 351 352 int installLocation = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, 353 "installLocation", PARSE_DEFAULT_INSTALL_LOCATION); 354 int versionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "versionCode", 0); 355 int versionCodeMajor = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, 356 "versionCodeMajor", 357 0); 358 int revisionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "revisionCode", 0); 359 boolean coreApp = parser.getAttributeBooleanValue(null, "coreApp", false); 360 boolean isolatedSplits = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, 361 "isolatedSplits", false); 362 boolean isFeatureSplit = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, 363 "isFeatureSplit", false); 364 boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, 365 "isSplitRequired", false); 366 String configForSplit = parser.getAttributeValue(null, "configForSplit"); 367 368 int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION; 369 int minSdkVersion = DEFAULT_MIN_SDK_VERSION; 370 boolean debuggable = false; 371 boolean profilableByShell = false; 372 boolean multiArch = false; 373 boolean use32bitAbi = false; 374 boolean extractNativeLibs = true; 375 boolean useEmbeddedDex = false; 376 String usesSplitName = null; 377 String targetPackage = null; 378 boolean overlayIsStatic = false; 379 int overlayPriority = 0; 380 int rollbackDataPolicy = 0; 381 382 String requiredSystemPropertyName = null; 383 String requiredSystemPropertyValue = null; 384 385 // Only search the tree when the tag is the direct child of <manifest> tag 386 int type; 387 final int searchDepth = parser.getDepth() + 1; 388 389 final List<VerifierInfo> verifiers = new ArrayList<>(); 390 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 391 && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) { 392 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 393 continue; 394 } 395 396 if (parser.getDepth() != searchDepth) { 397 continue; 398 } 399 400 if (ParsingPackageUtils.TAG_PACKAGE_VERIFIER.equals(parser.getName())) { 401 final VerifierInfo verifier = parseVerifier(parser); 402 if (verifier != null) { 403 verifiers.add(verifier); 404 } 405 } else if (ParsingPackageUtils.TAG_APPLICATION.equals(parser.getName())) { 406 debuggable = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "debuggable", 407 false); 408 multiArch = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "multiArch", 409 false); 410 use32bitAbi = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "use32bitAbi", 411 false); 412 extractNativeLibs = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, 413 "extractNativeLibs", true); 414 useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, 415 "useEmbeddedDex", false); 416 rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, 417 "rollbackDataPolicy", 0); 418 419 final int innerDepth = parser.getDepth(); 420 int innerType; 421 while ((innerType = parser.next()) != XmlPullParser.END_DOCUMENT 422 && (innerType != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { 423 if (innerType == XmlPullParser.END_TAG || innerType == XmlPullParser.TEXT) { 424 continue; 425 } 426 427 if (parser.getDepth() != innerDepth + 1) { 428 // Search only under <application>. 429 continue; 430 } 431 432 if (ParsingPackageUtils.TAG_PROFILEABLE.equals(parser.getName())) { 433 profilableByShell = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, 434 "shell", profilableByShell); 435 } 436 } 437 } else if (ParsingPackageUtils.TAG_OVERLAY.equals(parser.getName())) { 438 requiredSystemPropertyName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, 439 "requiredSystemPropertyName"); 440 requiredSystemPropertyValue = parser.getAttributeValue(ANDROID_RES_NAMESPACE, 441 "requiredSystemPropertyValue"); 442 targetPackage = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "targetPackage"); 443 overlayIsStatic = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isStatic", 444 false); 445 overlayPriority = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "priority", 0); 446 } else if (ParsingPackageUtils.TAG_USES_SPLIT.equals(parser.getName())) { 447 if (usesSplitName != null) { 448 Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others."); 449 continue; 450 } 451 452 usesSplitName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name"); 453 if (usesSplitName == null) { 454 return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, 455 "<uses-split> tag requires 'android:name' attribute"); 456 } 457 } else if (ParsingPackageUtils.TAG_USES_SDK.equals(parser.getName())) { 458 // Mirrors ParsingPackageUtils#parseUsesSdk until lite and full parsing is combined 459 String minSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE, 460 "minSdkVersion"); 461 String targetSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE, 462 "targetSdkVersion"); 463 464 int minVer = DEFAULT_MIN_SDK_VERSION; 465 String minCode = null; 466 int targetVer = DEFAULT_TARGET_SDK_VERSION; 467 String targetCode = null; 468 469 if (!TextUtils.isEmpty(minSdkVersionString)) { 470 try { 471 minVer = Integer.parseInt(minSdkVersionString); 472 } catch (NumberFormatException ignored) { 473 minCode = minSdkVersionString; 474 } 475 } 476 477 if (!TextUtils.isEmpty(targetSdkVersionString)) { 478 try { 479 targetVer = Integer.parseInt(targetSdkVersionString); 480 } catch (NumberFormatException ignored) { 481 targetCode = targetSdkVersionString; 482 if (minCode == null) { 483 minCode = targetCode; 484 } 485 } 486 } else { 487 targetVer = minVer; 488 targetCode = minCode; 489 } 490 491 ParseResult<Integer> targetResult = ParsingPackageUtils.computeTargetSdkVersion( 492 targetVer, targetCode, ParsingPackageUtils.SDK_CODENAMES, input); 493 if (targetResult.isError()) { 494 return input.error(targetResult); 495 } 496 targetSdkVersion = targetResult.getResult(); 497 498 ParseResult<Integer> minResult = ParsingPackageUtils.computeMinSdkVersion( 499 minVer, minCode, ParsingPackageUtils.SDK_VERSION, 500 ParsingPackageUtils.SDK_CODENAMES, input); 501 if (minResult.isError()) { 502 return input.error(minResult); 503 } 504 minSdkVersion = minResult.getResult(); 505 } 506 } 507 508 // Check to see if overlay should be excluded based on system property condition 509 if (!PackageParser.checkRequiredSystemProperties(requiredSystemPropertyName, 510 requiredSystemPropertyValue)) { 511 Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and " 512 + codePath + ": overlay ignored due to required system property: " 513 + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue); 514 targetPackage = null; 515 overlayIsStatic = false; 516 overlayPriority = 0; 517 } 518 519 return input.success( 520 new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit, 521 configForSplit, usesSplitName, isSplitRequired, versionCode, 522 versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails, 523 coreApp, debuggable, profilableByShell, multiArch, use32bitAbi, 524 useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage, 525 overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion, 526 rollbackDataPolicy)); 527 } 528 parsePackageSplitNames(ParseInput input, XmlResourceParser parser)529 public static ParseResult<Pair<String, String>> parsePackageSplitNames(ParseInput input, 530 XmlResourceParser parser) throws IOException, XmlPullParserException { 531 int type; 532 while ((type = parser.next()) != XmlPullParser.START_TAG 533 && type != XmlPullParser.END_DOCUMENT) { 534 } 535 536 if (type != XmlPullParser.START_TAG) { 537 return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, 538 "No start tag found"); 539 } 540 if (!parser.getName().equals(ParsingPackageUtils.TAG_MANIFEST)) { 541 return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, 542 "No <manifest> tag"); 543 } 544 545 final String packageName = parser.getAttributeValue(null, "package"); 546 if (!"android".equals(packageName)) { 547 final ParseResult<?> nameResult = validateName(input, packageName, true, true); 548 if (nameResult.isError()) { 549 return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, 550 "Invalid manifest package: " + nameResult.getErrorMessage()); 551 } 552 } 553 554 String splitName = parser.getAttributeValue(null, "split"); 555 if (splitName != null) { 556 if (splitName.length() == 0) { 557 splitName = null; 558 } else { 559 final ParseResult<?> nameResult = validateName(input, splitName, false, false); 560 if (nameResult.isError()) { 561 return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, 562 "Invalid manifest split: " + nameResult.getErrorMessage()); 563 } 564 } 565 } 566 567 return input.success(Pair.create(packageName.intern(), 568 (splitName != null) ? splitName.intern() : splitName)); 569 } 570 parseVerifier(AttributeSet attrs)571 public static VerifierInfo parseVerifier(AttributeSet attrs) { 572 String packageName = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "name"); 573 String encodedPublicKey = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "publicKey"); 574 575 if (packageName == null || packageName.length() == 0) { 576 Slog.i(TAG, "verifier package name was null; skipping"); 577 return null; 578 } 579 580 final PublicKey publicKey = PackageParser.parsePublicKey(encodedPublicKey); 581 if (publicKey == null) { 582 Slog.i(TAG, "Unable to parse verifier public key for " + packageName); 583 return null; 584 } 585 586 return new VerifierInfo(packageName, publicKey); 587 } 588 589 /** 590 * Used to sort a set of APKs based on their split names, always placing the 591 * base APK (with {@code null} split name) first. 592 */ 593 private static class SplitNameComparator implements Comparator<String> { 594 @Override compare(String lhs, String rhs)595 public int compare(String lhs, String rhs) { 596 if (lhs == null) { 597 return -1; 598 } else if (rhs == null) { 599 return 1; 600 } else { 601 return lhs.compareTo(rhs); 602 } 603 } 604 } 605 606 /** 607 * Check if the given file is an APK file. 608 * 609 * @param file the file to check. 610 * @return {@code true} if the given file is an APK file. 611 */ isApkFile(File file)612 public static boolean isApkFile(File file) { 613 return isApkPath(file.getName()); 614 } 615 616 /** 617 * Check if the given path ends with APK file extension. 618 * 619 * @param path the path to check. 620 * @return {@code true} if the given path ends with APK file extension. 621 */ isApkPath(String path)622 public static boolean isApkPath(String path) { 623 return path.endsWith(APK_FILE_EXTENSION); 624 } 625 } 626