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.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
22 
23 import android.annotation.NonNull;
24 import android.app.admin.DeviceAdminReceiver;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.SigningDetails;
28 import android.content.pm.VerifierInfo;
29 import android.content.pm.parsing.result.ParseInput;
30 import android.content.pm.parsing.result.ParseResult;
31 import android.content.res.ApkAssets;
32 import android.content.res.XmlResourceParser;
33 import android.os.Build;
34 import android.os.Trace;
35 import android.text.TextUtils;
36 import android.util.ArrayMap;
37 import android.util.ArraySet;
38 import android.util.AttributeSet;
39 import android.util.Pair;
40 import android.util.Slog;
41 
42 import com.android.internal.util.ArrayUtils;
43 
44 import libcore.io.IoUtils;
45 
46 import org.xmlpull.v1.XmlPullParser;
47 import org.xmlpull.v1.XmlPullParserException;
48 
49 import java.io.File;
50 import java.io.FileDescriptor;
51 import java.io.IOException;
52 import java.security.PublicKey;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Comparator;
56 import java.util.List;
57 import java.util.Objects;
58 import java.util.Set;
59 
60 /** @hide */
61 public class ApkLiteParseUtils {
62 
63     private static final String TAG = "ApkLiteParseUtils";
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     // Constants copied from services.jar side since they're not accessible
74     private static final String ANDROID_RES_NAMESPACE =
75             "http://schemas.android.com/apk/res/android";
76     private static final int DEFAULT_MIN_SDK_VERSION = 1;
77     private static final int DEFAULT_TARGET_SDK_VERSION = 0;
78     public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
79     private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
80     private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
81     private static final String TAG_APPLICATION = "application";
82     private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
83     private static final String TAG_PROFILEABLE = "profileable";
84     private static final String TAG_RECEIVER = "receiver";
85     private static final String TAG_OVERLAY = "overlay";
86     private static final String TAG_USES_SDK = "uses-sdk";
87     private static final String TAG_USES_SPLIT = "uses-split";
88     private static final String TAG_MANIFEST = "manifest";
89     private static final String TAG_SDK_LIBRARY = "sdk-library";
90     private static final int SDK_VERSION = Build.VERSION.SDK_INT;
91     private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
92 
93     /**
94      * Parse only lightweight details about the package at the given location.
95      * Automatically detects if the package is a monolithic style (single APK
96      * file) or cluster style (directory of APKs).
97      * <p>
98      * This performs validity checking on cluster style packages, such as
99      * requiring identical package name and version codes, a single base APK,
100      * and unique split names.
101      */
parsePackageLite(ParseInput input, File packageFile, int flags)102     public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
103             File packageFile, int flags) {
104         if (packageFile.isDirectory()) {
105             return parseClusterPackageLite(input, packageFile, flags);
106         } else {
107             return parseMonolithicPackageLite(input, packageFile, flags);
108         }
109     }
110 
111     /**
112      * Parse lightweight details about a single APK file.
113      */
parseMonolithicPackageLite(ParseInput input, File packageFile, int flags)114     public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
115             File packageFile, int flags) {
116         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
117         try {
118             final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
119             if (result.isError()) {
120                 return input.error(result);
121             }
122 
123             final ApkLite baseApk = result.getResult();
124             final String packagePath = packageFile.getAbsolutePath();
125             return input.success(
126                     new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
127                             null /* isFeatureSplits */, null /* usesSplitNames */,
128                             null /* configForSplit */, null /* splitApkPaths */,
129                             null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
130                             null /* requiredSplitTypes */, null /* splitTypes */));
131         } finally {
132             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
133         }
134     }
135 
136     /**
137      * Parse lightweight details about a single APK file passed as an FD.
138      */
parseMonolithicPackageLite(ParseInput input, FileDescriptor packageFd, String debugPathName, int flags)139     public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
140             FileDescriptor packageFd, String debugPathName, int flags) {
141         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
142         try {
143             final ParseResult<ApkLite> result = parseApkLite(input, packageFd, debugPathName,
144                     flags);
145             if (result.isError()) {
146                 return input.error(result);
147             }
148 
149             final ApkLite baseApk = result.getResult();
150             final String packagePath = debugPathName;
151             return input.success(
152                     new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
153                             null /* isFeatureSplits */, null /* usesSplitNames */,
154                             null /* configForSplit */, null /* splitApkPaths */,
155                             null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
156                             null /* requiredSplitTypes */, null /* splitTypes */));
157         } finally {
158             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
159         }
160     }
161 
162     /**
163      * Parse lightweight details about a directory of APKs.
164      *
165      * @param packageDir is the folder that contains split apks for a regular app
166      */
parseClusterPackageLite(ParseInput input, File packageDir, int flags)167     public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
168             File packageDir, int flags) {
169         final File[] files;
170         files = packageDir.listFiles();
171         if (ArrayUtils.isEmpty(files)) {
172             return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
173                     "No packages found in split");
174         }
175         // Apk directory is directly nested under the current directory
176         if (files.length == 1 && files[0].isDirectory()) {
177             return parseClusterPackageLite(input, files[0], flags);
178         }
179 
180         String packageName = null;
181         int versionCode = 0;
182         ApkLite baseApk = null;
183 
184         final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
185         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
186         try {
187             for (File file : files) {
188                 if (!isApkFile(file)) {
189                     continue;
190                 }
191 
192                 final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
193                 if (result.isError()) {
194                     return input.error(result);
195                 }
196 
197                 final ApkLite lite = result.getResult();
198                 // Assert that all package names and version codes are
199                 // consistent with the first one we encounter.
200                 if (packageName == null) {
201                     packageName = lite.getPackageName();
202                     versionCode = lite.getVersionCode();
203                 } else {
204                     if (!packageName.equals(lite.getPackageName())) {
205                         return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
206                                 "Inconsistent package " + lite.getPackageName() + " in " + file
207                                         + "; expected " + packageName);
208                     }
209                     if (versionCode != lite.getVersionCode()) {
210                         return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
211                                 "Inconsistent version " + lite.getVersionCode() + " in " + file
212                                         + "; expected " + versionCode);
213                     }
214                 }
215 
216                 // Assert that each split is defined only once
217                 ApkLite prev = apks.put(lite.getSplitName(), lite);
218                 if (prev != null) {
219                     return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
220                             "Split name " + lite.getSplitName()
221                                     + " defined more than once; most recent was " + file
222                                     + ", previous was " + prev.getPath());
223                 }
224             }
225             baseApk = apks.remove(null);
226         } finally {
227             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
228         }
229         return composePackageLiteFromApks(input, packageDir, baseApk, apks);
230     }
231 
232     /**
233      * Utility method that retrieves lightweight details about the package by given location,
234      * base APK, and split APKs.
235      *
236      * @param packageDir Path to the package
237      * @param baseApk Parsed base APK
238      * @param splitApks Parsed split APKs
239      * @return PackageLite
240      */
composePackageLiteFromApks(ParseInput input, File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks)241     public static ParseResult<PackageLite> composePackageLiteFromApks(ParseInput input,
242             File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks) {
243         return composePackageLiteFromApks(input, packageDir, baseApk, splitApks, false);
244     }
245 
246     /**
247      * Utility method that retrieves lightweight details about the package by given location,
248      * base APK, and split APKs.
249      *
250      * @param packageDir Path to the package
251      * @param baseApk Parsed base APK
252      * @param splitApks Parsed split APKs
253      * @param apkRenamed Indicate whether the APKs are renamed after parsed.
254      * @return PackageLite
255      */
composePackageLiteFromApks( ParseInput input, File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks, boolean apkRenamed)256     public static ParseResult<PackageLite> composePackageLiteFromApks(
257             ParseInput input, File packageDir, ApkLite baseApk,
258             ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
259         if (baseApk == null) {
260             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
261                     "Missing base APK in " + packageDir);
262         }
263         // Always apply deterministic ordering based on splitName
264         final int size = ArrayUtils.size(splitApks);
265 
266         String[] splitNames = null;
267         Set<String>[] requiredSplitTypes = null;
268         Set<String>[] splitTypes = null;
269         boolean[] isFeatureSplits = null;
270         String[] usesSplitNames = null;
271         String[] configForSplits = null;
272         String[] splitCodePaths = null;
273         int[] splitRevisionCodes = null;
274         if (size > 0) {
275             splitNames = new String[size];
276             requiredSplitTypes = new Set[size];
277             splitTypes = new Set[size];
278             isFeatureSplits = new boolean[size];
279             usesSplitNames = new String[size];
280             configForSplits = new String[size];
281             splitCodePaths = new String[size];
282             splitRevisionCodes = new int[size];
283 
284             splitNames = splitApks.keySet().toArray(splitNames);
285             Arrays.sort(splitNames, sSplitNameComparator);
286 
287             for (int i = 0; i < size; i++) {
288                 final ApkLite apk = splitApks.get(splitNames[i]);
289                 requiredSplitTypes[i] = apk.getRequiredSplitTypes();
290                 splitTypes[i] = apk.getSplitTypes();
291                 usesSplitNames[i] = apk.getUsesSplitName();
292                 isFeatureSplits[i] = apk.isFeatureSplit();
293                 configForSplits[i] = apk.getConfigForSplit();
294                 splitCodePaths[i] = apkRenamed ? new File(packageDir,
295                         splitNameToFileName(apk)).getAbsolutePath() : apk.getPath();
296                 splitRevisionCodes[i] = apk.getRevisionCode();
297             }
298         }
299 
300         final String codePath = packageDir.getAbsolutePath();
301         final String baseCodePath = apkRenamed ? new File(packageDir,
302                 splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath();
303         return input.success(
304                 new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
305                         usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes,
306                         baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes));
307     }
308 
309     /**
310      * Utility method that retrieves canonical file name by given split name from parsed APK.
311      *
312      * @param apk Parsed APK
313      * @return The canonical file name
314      */
splitNameToFileName(@onNull ApkLite apk)315     public static String splitNameToFileName(@NonNull ApkLite apk) {
316         Objects.requireNonNull(apk);
317         final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName();
318         return fileName + APK_FILE_EXTENSION;
319     }
320 
321     /**
322      * Utility method that retrieves lightweight details about a single APK
323      * file, including package name, split name, and install location.
324      *
325      * @param apkFile path to a single APK
326      * @param flags optional parse flags, such as
327      *            {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
328      */
parseApkLite(ParseInput input, File apkFile, int flags)329     public static ParseResult<ApkLite> parseApkLite(ParseInput input, File apkFile, int flags) {
330         return parseApkLiteInner(input, apkFile, null, null, flags);
331     }
332 
333     /**
334      * Utility method that retrieves lightweight details about a single APK
335      * file, including package name, split name, and install location.
336      *
337      * @param fd already open file descriptor of an apk file
338      * @param debugPathName arbitrary text name for this file, for debug output
339      * @param flags optional parse flags, such as
340      *            {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
341      */
parseApkLite(ParseInput input, FileDescriptor fd, String debugPathName, int flags)342     public static ParseResult<ApkLite> parseApkLite(ParseInput input,
343             FileDescriptor fd, String debugPathName, int flags) {
344         return parseApkLiteInner(input, null, fd, debugPathName, flags);
345     }
346 
parseApkLiteInner(ParseInput input, File apkFile, FileDescriptor fd, String debugPathName, int flags)347     private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input,
348             File apkFile, FileDescriptor fd, String debugPathName, int flags) {
349         final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
350 
351         XmlResourceParser parser = null;
352         ApkAssets apkAssets = null;
353         try {
354             try {
355                 apkAssets = fd != null
356                         ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
357                         : ApkAssets.loadFromPath(apkPath);
358             } catch (IOException e) {
359                 return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
360                         "Failed to parse " + apkPath, e);
361             }
362 
363             parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);
364 
365             final SigningDetails signingDetails;
366             if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
367                 final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0;
368                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
369                 try {
370                     final ParseResult<SigningDetails> result =
371                             FrameworkParsingPackageUtils.getSigningDetails(input,
372                                     apkFile.getAbsolutePath(),
373                                     skipVerify, /* isStaticSharedLibrary */ false,
374                                     SigningDetails.UNKNOWN, DEFAULT_TARGET_SDK_VERSION);
375                     if (result.isError()) {
376                         return input.error(result);
377                     }
378                     signingDetails = result.getResult();
379                 } finally {
380                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
381                 }
382             } else {
383                 signingDetails = SigningDetails.UNKNOWN;
384             }
385 
386             return parseApkLite(input, apkPath, parser, signingDetails, flags);
387         } catch (XmlPullParserException | IOException | RuntimeException e) {
388             Slog.w(TAG, "Failed to parse " + apkPath, e);
389             return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
390                     "Failed to parse " + apkPath, e);
391         } finally {
392             IoUtils.closeQuietly(parser);
393             if (apkAssets != null) {
394                 try {
395                     apkAssets.close();
396                 } catch (Throwable ignored) {
397                 }
398             }
399             // TODO(b/72056911): Implement AutoCloseable on ApkAssets.
400         }
401     }
402 
parseApkLite(ParseInput input, String codePath, XmlResourceParser parser, SigningDetails signingDetails, int flags)403     private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
404             XmlResourceParser parser, SigningDetails signingDetails, int flags)
405             throws IOException, XmlPullParserException {
406         ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser);
407         if (result.isError()) {
408             return input.error(result);
409         }
410         Pair<String, String> packageSplit = result.getResult();
411 
412         final ParseResult<Pair<Set<String>, Set<String>>> requiredSplitTypesResult =
413                 parseRequiredSplitTypes(input, parser);
414         if (requiredSplitTypesResult.isError()) {
415             return input.error(result);
416         }
417         Pair<Set<String>, Set<String>> requiredSplitTypes = requiredSplitTypesResult.getResult();
418 
419         int installLocation = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
420                 "installLocation", PARSE_DEFAULT_INSTALL_LOCATION);
421         int versionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "versionCode", 0);
422         int versionCodeMajor = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
423                 "versionCodeMajor",
424                 0);
425         int revisionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "revisionCode", 0);
426         boolean coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
427         boolean isolatedSplits = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
428                 "isolatedSplits", false);
429         boolean isFeatureSplit = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
430                 "isFeatureSplit", false);
431         boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
432                 "isSplitRequired", false);
433         String configForSplit = parser.getAttributeValue(null, "configForSplit");
434 
435         int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
436         int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
437         boolean debuggable = false;
438         boolean profilableByShell = false;
439         boolean multiArch = false;
440         boolean use32bitAbi = false;
441         boolean extractNativeLibs = true;
442         boolean useEmbeddedDex = false;
443         String usesSplitName = null;
444         String targetPackage = null;
445         boolean overlayIsStatic = false;
446         int overlayPriority = 0;
447         int rollbackDataPolicy = 0;
448 
449         String requiredSystemPropertyName = null;
450         String requiredSystemPropertyValue = null;
451 
452         boolean hasDeviceAdminReceiver = false;
453 
454         boolean isSdkLibrary = false;
455 
456         // Only search the tree when the tag is the direct child of <manifest> tag
457         int type;
458         final int searchDepth = parser.getDepth() + 1;
459 
460         final List<VerifierInfo> verifiers = new ArrayList<>();
461         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
462                 && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
463             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
464                 continue;
465             }
466 
467             if (parser.getDepth() != searchDepth) {
468                 continue;
469             }
470 
471             if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
472                 final VerifierInfo verifier = parseVerifier(parser);
473                 if (verifier != null) {
474                     verifiers.add(verifier);
475                 }
476             } else if (TAG_APPLICATION.equals(parser.getName())) {
477                 debuggable = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "debuggable",
478                         false);
479                 multiArch = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "multiArch",
480                         false);
481                 use32bitAbi = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "use32bitAbi",
482                         false);
483                 extractNativeLibs = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
484                         "extractNativeLibs", true);
485                 useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
486                         "useEmbeddedDex", false);
487                 rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
488                         "rollbackDataPolicy", 0);
489                 String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
490                         "permission");
491                 boolean hasBindDeviceAdminPermission =
492                         android.Manifest.permission.BIND_DEVICE_ADMIN.equals(permission);
493 
494                 final int innerDepth = parser.getDepth();
495                 int innerType;
496                 while ((innerType = parser.next()) != XmlPullParser.END_DOCUMENT
497                         && (innerType != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
498                     if (innerType == XmlPullParser.END_TAG || innerType == XmlPullParser.TEXT) {
499                         continue;
500                     }
501 
502                     if (parser.getDepth() != innerDepth + 1) {
503                         // Search only under <application>.
504                         continue;
505                     }
506 
507                     if (TAG_PROFILEABLE.equals(parser.getName())) {
508                         profilableByShell = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
509                                 "shell", profilableByShell);
510                     } else if (TAG_RECEIVER.equals(parser.getName())) {
511                         hasDeviceAdminReceiver |= isDeviceAdminReceiver(
512                                 parser, hasBindDeviceAdminPermission);
513                     } else if (TAG_SDK_LIBRARY.equals(parser.getName())) {
514                         isSdkLibrary = true;
515                     }
516                 }
517             } else if (TAG_OVERLAY.equals(parser.getName())) {
518                 requiredSystemPropertyName = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
519                         "requiredSystemPropertyName");
520                 requiredSystemPropertyValue = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
521                         "requiredSystemPropertyValue");
522                 targetPackage = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "targetPackage");
523                 overlayIsStatic = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isStatic",
524                         false);
525                 overlayPriority = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "priority", 0);
526             } else if (TAG_USES_SPLIT.equals(parser.getName())) {
527                 if (usesSplitName != null) {
528                     Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
529                     continue;
530                 }
531 
532                 usesSplitName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
533                 if (usesSplitName == null) {
534                     return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
535                             "<uses-split> tag requires 'android:name' attribute");
536                 }
537             } else if (TAG_USES_SDK.equals(parser.getName())) {
538                 // Mirrors FrameworkParsingPackageUtils#parseUsesSdk until lite and full parsing is combined
539                 String minSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
540                         "minSdkVersion");
541                 String targetSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
542                         "targetSdkVersion");
543 
544                 int minVer = DEFAULT_MIN_SDK_VERSION;
545                 String minCode = null;
546                 boolean minAssigned = false;
547                 int targetVer = DEFAULT_TARGET_SDK_VERSION;
548                 String targetCode = null;
549 
550                 if (!TextUtils.isEmpty(minSdkVersionString)) {
551                     try {
552                         minVer = Integer.parseInt(minSdkVersionString);
553                         minAssigned = true;
554                     } catch (NumberFormatException ignored) {
555                         minCode = minSdkVersionString;
556                         minAssigned = !TextUtils.isEmpty(minCode);
557                     }
558                 }
559 
560                 if (!TextUtils.isEmpty(targetSdkVersionString)) {
561                     try {
562                         targetVer = Integer.parseInt(targetSdkVersionString);
563                     } catch (NumberFormatException ignored) {
564                         targetCode = targetSdkVersionString;
565                         if (!minAssigned) {
566                             minCode = targetCode;
567                         }
568                     }
569                 } else {
570                     targetVer = minVer;
571                     targetCode = minCode;
572                 }
573 
574                 boolean allowUnknownCodenames = false;
575                 if ((flags & FrameworkParsingPackageUtils.PARSE_APK_IN_APEX) != 0) {
576                     allowUnknownCodenames = true;
577                 }
578 
579                 ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
580                         targetVer, targetCode, SDK_CODENAMES, input,
581                         allowUnknownCodenames);
582                 if (targetResult.isError()) {
583                     return input.error(targetResult);
584                 }
585                 targetSdkVersion = targetResult.getResult();
586 
587                 ParseResult<Integer> minResult = FrameworkParsingPackageUtils.computeMinSdkVersion(
588                         minVer, minCode, SDK_VERSION, SDK_CODENAMES, input);
589                 if (minResult.isError()) {
590                     return input.error(minResult);
591                 }
592                 minSdkVersion = minResult.getResult();
593             }
594         }
595 
596         // Check to see if overlay should be excluded based on system property condition
597         if ((flags & FrameworkParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY)
598                 == 0 && !FrameworkParsingPackageUtils.checkRequiredSystemProperties(
599                 requiredSystemPropertyName, requiredSystemPropertyValue)) {
600             String message = "Skipping target and overlay pair " + targetPackage + " and "
601                     + codePath + ": overlay ignored due to required system property: "
602                     + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue;
603             Slog.i(TAG, message);
604             return input.skip(message);
605         }
606 
607         return input.success(
608                 new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
609                         configForSplit, usesSplitName, isSplitRequired, versionCode,
610                         versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
611                         coreApp, debuggable, profilableByShell, multiArch, use32bitAbi,
612                         useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage,
613                         overlayIsStatic, overlayPriority, requiredSystemPropertyName,
614                         requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
615                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
616                         hasDeviceAdminReceiver, isSdkLibrary));
617     }
618 
isDeviceAdminReceiver( XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)619     private static boolean isDeviceAdminReceiver(
620             XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)
621             throws XmlPullParserException, IOException {
622         String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
623                 "permission");
624         if (!applicationHasBindDeviceAdminPermission
625                 && !android.Manifest.permission.BIND_DEVICE_ADMIN.equals(permission)) {
626             return false;
627         }
628 
629         boolean hasDeviceAdminReceiver = false;
630         final int depth = parser.getDepth();
631         int type;
632         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
633                 && (type != XmlPullParser.END_TAG || parser.getDepth() > depth)) {
634             if (type == XmlPullParser.END_TAG
635                     || type == XmlPullParser.TEXT) {
636                 continue;
637             }
638             if (parser.getDepth() != depth + 1) {
639                 // Search only under <receiver>.
640                 continue;
641             }
642             if (!hasDeviceAdminReceiver && "meta-data".equals(parser.getName())) {
643                 String name = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
644                         "name");
645                 if (DeviceAdminReceiver.DEVICE_ADMIN_META_DATA.equals(name)) {
646                     hasDeviceAdminReceiver = true;
647                 }
648             }
649         }
650         return hasDeviceAdminReceiver;
651     }
652 
parsePackageSplitNames(ParseInput input, XmlResourceParser parser)653     public static ParseResult<Pair<String, String>> parsePackageSplitNames(ParseInput input,
654             XmlResourceParser parser) throws IOException, XmlPullParserException {
655         int type;
656         while ((type = parser.next()) != XmlPullParser.START_TAG
657                 && type != XmlPullParser.END_DOCUMENT) {
658         }
659 
660         if (type != XmlPullParser.START_TAG) {
661             return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
662                     "No start tag found");
663         }
664         if (!parser.getName().equals(TAG_MANIFEST)) {
665             return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
666                     "No <manifest> tag");
667         }
668 
669         final String packageName = parser.getAttributeValue(null, "package");
670         if (!"android".equals(packageName)) {
671             final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input,
672                     packageName, true, true);
673             if (nameResult.isError()) {
674                 return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
675                         "Invalid manifest package: " + nameResult.getErrorMessage());
676             }
677         }
678 
679         String splitName = parser.getAttributeValue(null, "split");
680         if (splitName != null) {
681             if (splitName.length() == 0) {
682                 splitName = null;
683             } else {
684                 final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input,
685                         splitName, false, false);
686                 if (nameResult.isError()) {
687                     return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
688                             "Invalid manifest split: " + nameResult.getErrorMessage());
689                 }
690             }
691         }
692 
693         return input.success(Pair.create(packageName.intern(),
694                 (splitName != null) ? splitName.intern() : splitName));
695     }
696 
697     /**
698      * Utility method that parses attributes android:requiredSplitTypes and android:splitTypes.
699      */
parseRequiredSplitTypes( ParseInput input, XmlResourceParser parser)700     public static ParseResult<Pair<Set<String>, Set<String>>> parseRequiredSplitTypes(
701             ParseInput input, XmlResourceParser parser) {
702         Set<String> requiredSplitTypes = null;
703         Set<String> splitTypes = null;
704         String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "requiredSplitTypes");
705         if (!TextUtils.isEmpty(value)) {
706             final ParseResult<Set<String>> result = separateAndValidateSplitTypes(input, value);
707             if (result.isError()) {
708                 return input.error(result);
709             }
710             requiredSplitTypes = result.getResult();
711         }
712 
713         value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "splitTypes");
714         if (!TextUtils.isEmpty(value)) {
715             final ParseResult<Set<String>> result = separateAndValidateSplitTypes(input, value);
716             if (result.isError()) {
717                 return input.error(result);
718             }
719             splitTypes = result.getResult();
720         }
721 
722         return input.success(Pair.create(requiredSplitTypes, splitTypes));
723     }
724 
separateAndValidateSplitTypes(ParseInput input, String values)725     private static ParseResult<Set<String>> separateAndValidateSplitTypes(ParseInput input,
726             String values) {
727         final Set<String> ret = new ArraySet<>();
728         for (String value : values.trim().split(",")) {
729             final String type = value.trim();
730             // Using requireFilename as true because it limits length of the name to the
731             // {@link #MAX_FILE_NAME_SIZE}.
732             final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, type,
733                     false /* requireSeparator */, true /* requireFilename */);
734             if (nameResult.isError()) {
735                 return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
736                         "Invalid manifest split types: " + nameResult.getErrorMessage());
737             }
738             if (!ret.add(type)) {
739                 Slog.w(TAG, type + " was defined multiple times");
740             }
741         }
742         return input.success(ret);
743     }
744 
parseVerifier(AttributeSet attrs)745     public static VerifierInfo parseVerifier(AttributeSet attrs) {
746         String packageName = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
747         String encodedPublicKey = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "publicKey");
748 
749         if (packageName == null || packageName.length() == 0) {
750             Slog.i(TAG, "verifier package name was null; skipping");
751             return null;
752         }
753 
754         final PublicKey publicKey = FrameworkParsingPackageUtils.parsePublicKey(encodedPublicKey);
755         if (publicKey == null) {
756             Slog.i(TAG, "Unable to parse verifier public key for " + packageName);
757             return null;
758         }
759 
760         return new VerifierInfo(packageName, publicKey);
761     }
762 
763     /**
764      * Used to sort a set of APKs based on their split names, always placing the
765      * base APK (with {@code null} split name) first.
766      */
767     private static class SplitNameComparator implements Comparator<String> {
768         @Override
compare(String lhs, String rhs)769         public int compare(String lhs, String rhs) {
770             if (lhs == null) {
771                 return -1;
772             } else if (rhs == null) {
773                 return 1;
774             } else {
775                 return lhs.compareTo(rhs);
776             }
777         }
778     }
779 
780     /**
781      * Check if the given file is an APK file.
782      *
783      * @param file the file to check.
784      * @return {@code true} if the given file is an APK file.
785      */
isApkFile(File file)786     public static boolean isApkFile(File file) {
787         return isApkPath(file.getName());
788     }
789 
790     /**
791      * Check if the given path ends with APK file extension.
792      *
793      * @param path the path to check.
794      * @return {@code true} if the given path ends with APK file extension.
795      */
isApkPath(String path)796     public static boolean isApkPath(String path) {
797         return path.endsWith(APK_FILE_EXTENSION);
798     }
799 }
800