• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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