832 lines
37 KiB
Java
832 lines
37 KiB
Java
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.content.pm.parsing;
|
|
|
|
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
|
|
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
|
|
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.app.admin.DeviceAdminReceiver;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.SigningDetails;
|
|
import android.content.pm.VerifierInfo;
|
|
import android.content.pm.parsing.result.ParseInput;
|
|
import android.content.pm.parsing.result.ParseResult;
|
|
import android.content.res.ApkAssets;
|
|
import android.content.res.XmlResourceParser;
|
|
import android.os.Build;
|
|
import android.os.Trace;
|
|
import android.text.TextUtils;
|
|
import android.util.ArrayMap;
|
|
import android.util.ArraySet;
|
|
import android.util.AttributeSet;
|
|
import android.util.Pair;
|
|
import android.util.Slog;
|
|
|
|
import com.android.internal.pm.pkg.component.flags.Flags;
|
|
import com.android.internal.util.ArrayUtils;
|
|
|
|
import libcore.io.IoUtils;
|
|
|
|
import org.xmlpull.v1.XmlPullParser;
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
|
|
import java.io.File;
|
|
import java.io.FileDescriptor;
|
|
import java.io.IOException;
|
|
import java.security.PublicKey;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
|
|
/** @hide */
|
|
public class ApkLiteParseUtils {
|
|
|
|
private static final String TAG = "ApkLiteParseUtils";
|
|
|
|
private static final int PARSE_DEFAULT_INSTALL_LOCATION =
|
|
PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
|
|
|
|
private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
|
|
|
|
public static final String APK_FILE_EXTENSION = ".apk";
|
|
|
|
|
|
// Constants copied from services.jar side since they're not accessible
|
|
private static final String ANDROID_RES_NAMESPACE =
|
|
"http://schemas.android.com/apk/res/android";
|
|
public static final int DEFAULT_MIN_SDK_VERSION = 1;
|
|
private static final int DEFAULT_TARGET_SDK_VERSION = 0;
|
|
public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
|
|
private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
|
|
private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
|
|
private static final String TAG_APPLICATION = "application";
|
|
private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
|
|
private static final String TAG_PROFILEABLE = "profileable";
|
|
private static final String TAG_RECEIVER = "receiver";
|
|
private static final String TAG_OVERLAY = "overlay";
|
|
private static final String TAG_USES_SDK = "uses-sdk";
|
|
private static final String TAG_USES_SPLIT = "uses-split";
|
|
private static final String TAG_MANIFEST = "manifest";
|
|
private static final String TAG_SDK_LIBRARY = "sdk-library";
|
|
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
|
|
private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
|
|
private static final String TAG_PROCESSES = "processes";
|
|
private static final String TAG_PROCESS = "process";
|
|
|
|
/**
|
|
* Parse only lightweight details about the package at the given location.
|
|
* Automatically detects if the package is a monolithic style (single APK
|
|
* file) or cluster style (directory of APKs).
|
|
* <p>
|
|
* This performs validity checking on cluster style packages, such as
|
|
* requiring identical package name and version codes, a single base APK,
|
|
* and unique split names.
|
|
*/
|
|
public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
|
|
File packageFile, int flags) {
|
|
if (packageFile.isDirectory()) {
|
|
return parseClusterPackageLite(input, packageFile, flags);
|
|
} else {
|
|
return parseMonolithicPackageLite(input, packageFile, flags);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse lightweight details about a single APK file.
|
|
*/
|
|
public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
|
|
File packageFile, int flags) {
|
|
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
|
|
try {
|
|
final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
|
|
if (result.isError()) {
|
|
return input.error(result);
|
|
}
|
|
|
|
final ApkLite baseApk = result.getResult();
|
|
final String packagePath = packageFile.getAbsolutePath();
|
|
return input.success(
|
|
new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
|
|
null /* isFeatureSplits */, null /* usesSplitNames */,
|
|
null /* configForSplit */, null /* splitApkPaths */,
|
|
null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
|
|
null /* requiredSplitTypes */, null /* splitTypes */));
|
|
} finally {
|
|
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse lightweight details about a single APK file passed as an FD.
|
|
*/
|
|
public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
|
|
FileDescriptor packageFd, String debugPathName, int flags) {
|
|
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
|
|
try {
|
|
final ParseResult<ApkLite> result = parseApkLite(input, packageFd, debugPathName,
|
|
flags);
|
|
if (result.isError()) {
|
|
return input.error(result);
|
|
}
|
|
|
|
final ApkLite baseApk = result.getResult();
|
|
final String packagePath = debugPathName;
|
|
return input.success(
|
|
new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
|
|
null /* isFeatureSplits */, null /* usesSplitNames */,
|
|
null /* configForSplit */, null /* splitApkPaths */,
|
|
null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
|
|
null /* requiredSplitTypes */, null /* splitTypes */));
|
|
} finally {
|
|
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse lightweight details about a directory of APKs.
|
|
*
|
|
* @param packageDir is the folder that contains split apks for a regular app
|
|
*/
|
|
public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
|
|
File packageDir, int flags) {
|
|
final File[] files;
|
|
files = packageDir.listFiles();
|
|
if (ArrayUtils.isEmpty(files)) {
|
|
return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
|
|
"No packages found in split");
|
|
}
|
|
// Apk directory is directly nested under the current directory
|
|
if (files.length == 1 && files[0].isDirectory()) {
|
|
return parseClusterPackageLite(input, files[0], flags);
|
|
}
|
|
|
|
String packageName = null;
|
|
int versionCode = 0;
|
|
ApkLite baseApk = null;
|
|
|
|
final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
|
|
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
|
|
try {
|
|
for (File file : files) {
|
|
if (!isApkFile(file)) {
|
|
continue;
|
|
}
|
|
|
|
final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
|
|
if (result.isError()) {
|
|
return input.error(result);
|
|
}
|
|
|
|
final ApkLite lite = result.getResult();
|
|
// Assert that all package names and version codes are
|
|
// consistent with the first one we encounter.
|
|
if (packageName == null) {
|
|
packageName = lite.getPackageName();
|
|
versionCode = lite.getVersionCode();
|
|
} else {
|
|
if (!packageName.equals(lite.getPackageName())) {
|
|
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
|
|
"Inconsistent package " + lite.getPackageName() + " in " + file
|
|
+ "; expected " + packageName);
|
|
}
|
|
if (versionCode != lite.getVersionCode()) {
|
|
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
|
|
"Inconsistent version " + lite.getVersionCode() + " in " + file
|
|
+ "; expected " + versionCode);
|
|
}
|
|
}
|
|
|
|
// Assert that each split is defined only once
|
|
ApkLite prev = apks.put(lite.getSplitName(), lite);
|
|
if (prev != null) {
|
|
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
|
|
"Split name " + lite.getSplitName()
|
|
+ " defined more than once; most recent was " + file
|
|
+ ", previous was " + prev.getPath());
|
|
}
|
|
}
|
|
baseApk = apks.remove(null);
|
|
} finally {
|
|
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
|
}
|
|
return composePackageLiteFromApks(input, packageDir, baseApk, apks);
|
|
}
|
|
|
|
/**
|
|
* Utility method that retrieves lightweight details about the package by given location,
|
|
* base APK, and split APKs.
|
|
*
|
|
* @param packageDir Path to the package
|
|
* @param baseApk Parsed base APK
|
|
* @param splitApks Parsed split APKs
|
|
* @return PackageLite
|
|
*/
|
|
public static ParseResult<PackageLite> composePackageLiteFromApks(ParseInput input,
|
|
File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks) {
|
|
return composePackageLiteFromApks(input, packageDir, baseApk, splitApks, false);
|
|
}
|
|
|
|
/**
|
|
* Utility method that retrieves lightweight details about the package by given location,
|
|
* base APK, and split APKs.
|
|
*
|
|
* @param packageDir Path to the package
|
|
* @param baseApk Parsed base APK
|
|
* @param splitApks Parsed split APKs
|
|
* @param apkRenamed Indicate whether the APKs are renamed after parsed.
|
|
* @return PackageLite
|
|
*/
|
|
public static ParseResult<PackageLite> composePackageLiteFromApks(
|
|
ParseInput input, File packageDir, ApkLite baseApk,
|
|
ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
|
|
if (baseApk == null) {
|
|
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
|
|
"Missing base APK in " + packageDir);
|
|
}
|
|
// Always apply deterministic ordering based on splitName
|
|
final int size = ArrayUtils.size(splitApks);
|
|
|
|
String[] splitNames = null;
|
|
Set<String>[] requiredSplitTypes = null;
|
|
Set<String>[] splitTypes = null;
|
|
boolean[] isFeatureSplits = null;
|
|
String[] usesSplitNames = null;
|
|
String[] configForSplits = null;
|
|
String[] splitCodePaths = null;
|
|
int[] splitRevisionCodes = null;
|
|
if (size > 0) {
|
|
splitNames = new String[size];
|
|
requiredSplitTypes = new Set[size];
|
|
splitTypes = new Set[size];
|
|
isFeatureSplits = new boolean[size];
|
|
usesSplitNames = new String[size];
|
|
configForSplits = new String[size];
|
|
splitCodePaths = new String[size];
|
|
splitRevisionCodes = new int[size];
|
|
|
|
splitNames = splitApks.keySet().toArray(splitNames);
|
|
Arrays.sort(splitNames, sSplitNameComparator);
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
final ApkLite apk = splitApks.get(splitNames[i]);
|
|
requiredSplitTypes[i] = apk.getRequiredSplitTypes();
|
|
splitTypes[i] = apk.getSplitTypes();
|
|
usesSplitNames[i] = apk.getUsesSplitName();
|
|
isFeatureSplits[i] = apk.isFeatureSplit();
|
|
configForSplits[i] = apk.getConfigForSplit();
|
|
splitCodePaths[i] = apkRenamed ? new File(packageDir,
|
|
splitNameToFileName(apk)).getAbsolutePath() : apk.getPath();
|
|
splitRevisionCodes[i] = apk.getRevisionCode();
|
|
}
|
|
}
|
|
|
|
final String codePath = packageDir.getAbsolutePath();
|
|
final String baseCodePath = apkRenamed ? new File(packageDir,
|
|
splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath();
|
|
return input.success(
|
|
new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
|
|
usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes,
|
|
baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes));
|
|
}
|
|
|
|
/**
|
|
* Utility method that retrieves canonical file name by given split name from parsed APK.
|
|
*
|
|
* @param apk Parsed APK
|
|
* @return The canonical file name
|
|
*/
|
|
public static String splitNameToFileName(@NonNull ApkLite apk) {
|
|
Objects.requireNonNull(apk);
|
|
final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName();
|
|
return fileName + APK_FILE_EXTENSION;
|
|
}
|
|
|
|
/**
|
|
* Utility method that retrieves lightweight details about a single APK
|
|
* file, including package name, split name, and install location.
|
|
*
|
|
* @param apkFile path to a single APK
|
|
* @param flags optional parse flags, such as
|
|
* {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
|
|
*/
|
|
public static ParseResult<ApkLite> parseApkLite(ParseInput input, File apkFile, int flags) {
|
|
return parseApkLiteInner(input, apkFile, null, null, flags);
|
|
}
|
|
|
|
/**
|
|
* Utility method that retrieves lightweight details about a single APK
|
|
* file, including package name, split name, and install location.
|
|
*
|
|
* @param fd already open file descriptor of an apk file
|
|
* @param debugPathName arbitrary text name for this file, for debug output
|
|
* @param flags optional parse flags, such as
|
|
* {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
|
|
*/
|
|
public static ParseResult<ApkLite> parseApkLite(ParseInput input,
|
|
FileDescriptor fd, String debugPathName, int flags) {
|
|
return parseApkLiteInner(input, null, fd, debugPathName, flags);
|
|
}
|
|
|
|
private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input,
|
|
File apkFile, FileDescriptor fd, String debugPathName, int flags) {
|
|
final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
|
|
|
|
XmlResourceParser parser = null;
|
|
ApkAssets apkAssets = null;
|
|
try {
|
|
try {
|
|
apkAssets = fd != null
|
|
? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
|
|
: ApkAssets.loadFromPath(apkPath);
|
|
} catch (IOException e) {
|
|
return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
|
|
"Failed to parse " + apkPath, e);
|
|
}
|
|
|
|
parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);
|
|
|
|
final SigningDetails signingDetails;
|
|
if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
|
|
final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0;
|
|
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
|
|
try {
|
|
final ParseResult<SigningDetails> result =
|
|
FrameworkParsingPackageUtils.getSigningDetails(input,
|
|
apkFile.getAbsolutePath(),
|
|
skipVerify, /* isStaticSharedLibrary */ false,
|
|
SigningDetails.UNKNOWN, DEFAULT_TARGET_SDK_VERSION);
|
|
if (result.isError()) {
|
|
return input.error(result);
|
|
}
|
|
signingDetails = result.getResult();
|
|
} finally {
|
|
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
|
}
|
|
} else {
|
|
signingDetails = SigningDetails.UNKNOWN;
|
|
}
|
|
|
|
return parseApkLite(input, apkPath, parser, signingDetails, flags);
|
|
} catch (XmlPullParserException | IOException | RuntimeException e) {
|
|
Slog.w(TAG, "Failed to parse " + apkPath, e);
|
|
return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
|
|
"Failed to parse " + apkPath, e);
|
|
} finally {
|
|
IoUtils.closeQuietly(parser);
|
|
if (apkAssets != null) {
|
|
try {
|
|
apkAssets.close();
|
|
} catch (Throwable ignored) {
|
|
}
|
|
}
|
|
// TODO(b/72056911): Implement AutoCloseable on ApkAssets.
|
|
}
|
|
}
|
|
|
|
private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
|
|
XmlResourceParser parser, SigningDetails signingDetails, int flags)
|
|
throws IOException, XmlPullParserException {
|
|
ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser);
|
|
if (result.isError()) {
|
|
return input.error(result);
|
|
}
|
|
Pair<String, String> packageSplit = result.getResult();
|
|
|
|
final ParseResult<Pair<Set<String>, Set<String>>> requiredSplitTypesResult =
|
|
parseRequiredSplitTypes(input, parser);
|
|
if (requiredSplitTypesResult.isError()) {
|
|
return input.error(result);
|
|
}
|
|
Pair<Set<String>, Set<String>> requiredSplitTypes = requiredSplitTypesResult.getResult();
|
|
|
|
int installLocation = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
|
|
"installLocation", PARSE_DEFAULT_INSTALL_LOCATION);
|
|
int versionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "versionCode", 0);
|
|
int versionCodeMajor = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
|
|
"versionCodeMajor",
|
|
0);
|
|
int revisionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "revisionCode", 0);
|
|
boolean coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
|
|
boolean updatableSystem = parser.getAttributeBooleanValue(null, "updatableSystem", true);
|
|
boolean isolatedSplits = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
|
|
"isolatedSplits", false);
|
|
boolean isFeatureSplit = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
|
|
"isFeatureSplit", false);
|
|
boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
|
|
"isSplitRequired", false);
|
|
String configForSplit = parser.getAttributeValue(null, "configForSplit");
|
|
String emergencyInstaller = parser.getAttributeValue(null, "emergencyInstaller");
|
|
|
|
int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
|
|
int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
|
|
boolean debuggable = false;
|
|
boolean profilableByShell = false;
|
|
boolean multiArch = false;
|
|
boolean use32bitAbi = false;
|
|
boolean extractNativeLibs = true;
|
|
boolean useEmbeddedDex = false;
|
|
String usesSplitName = null;
|
|
String targetPackage = null;
|
|
boolean overlayIsStatic = false;
|
|
int overlayPriority = 0;
|
|
int rollbackDataPolicy = 0;
|
|
|
|
String requiredSystemPropertyName = null;
|
|
String requiredSystemPropertyValue = null;
|
|
|
|
boolean hasDeviceAdminReceiver = false;
|
|
|
|
boolean isSdkLibrary = false;
|
|
|
|
// Only search the tree when the tag is the direct child of <manifest> tag
|
|
int type;
|
|
final int searchDepth = parser.getDepth() + 1;
|
|
|
|
final List<VerifierInfo> verifiers = new ArrayList<>();
|
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
|
&& (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
|
|
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
|
|
continue;
|
|
}
|
|
|
|
if (parser.getDepth() != searchDepth) {
|
|
continue;
|
|
}
|
|
|
|
if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
|
|
final VerifierInfo verifier = parseVerifier(parser);
|
|
if (verifier != null) {
|
|
verifiers.add(verifier);
|
|
}
|
|
} else if (TAG_APPLICATION.equals(parser.getName())) {
|
|
debuggable = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "debuggable",
|
|
false);
|
|
multiArch = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "multiArch",
|
|
false);
|
|
use32bitAbi = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "use32bitAbi",
|
|
false);
|
|
extractNativeLibs = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
|
|
"extractNativeLibs", true);
|
|
useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
|
|
"useEmbeddedDex", false);
|
|
|
|
rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
|
|
"rollbackDataPolicy", 0);
|
|
String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
|
|
"permission");
|
|
boolean hasBindDeviceAdminPermission =
|
|
android.Manifest.permission.BIND_DEVICE_ADMIN.equals(permission);
|
|
|
|
final int innerDepth = parser.getDepth();
|
|
int innerType;
|
|
while ((innerType = parser.next()) != XmlPullParser.END_DOCUMENT
|
|
&& (innerType != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
|
|
if (innerType == XmlPullParser.END_TAG || innerType == XmlPullParser.TEXT) {
|
|
continue;
|
|
}
|
|
|
|
if (parser.getDepth() != innerDepth + 1) {
|
|
// Search only under <application>.
|
|
continue;
|
|
}
|
|
|
|
switch (parser.getName()) {
|
|
case TAG_PROFILEABLE:
|
|
profilableByShell = parser.getAttributeBooleanValue(
|
|
ANDROID_RES_NAMESPACE, "shell", profilableByShell);
|
|
break;
|
|
case TAG_RECEIVER:
|
|
hasDeviceAdminReceiver |= isDeviceAdminReceiver(parser,
|
|
hasBindDeviceAdminPermission);
|
|
break;
|
|
case TAG_SDK_LIBRARY:
|
|
isSdkLibrary = true;
|
|
break;
|
|
case TAG_PROCESSES:
|
|
final int processesDepth = parser.getDepth();
|
|
int processesType;
|
|
while ((processesType = parser.next()) != XmlPullParser.END_DOCUMENT
|
|
&& (processesType != XmlPullParser.END_TAG
|
|
|| parser.getDepth() > processesDepth)) {
|
|
if (processesType == XmlPullParser.END_TAG
|
|
|| processesType == XmlPullParser.TEXT) {
|
|
continue;
|
|
}
|
|
|
|
if (parser.getDepth() != processesDepth + 1) {
|
|
// Search only under <processes>.
|
|
continue;
|
|
}
|
|
|
|
if (parser.getName().equals(TAG_PROCESS)
|
|
&& Flags.enablePerProcessUseEmbeddedDexAttr()) {
|
|
useEmbeddedDex |= parser.getAttributeBooleanValue(
|
|
ANDROID_RES_NAMESPACE, "useEmbeddedDex", false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (TAG_OVERLAY.equals(parser.getName())) {
|
|
requiredSystemPropertyName = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
|
|
"requiredSystemPropertyName");
|
|
requiredSystemPropertyValue = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
|
|
"requiredSystemPropertyValue");
|
|
targetPackage = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "targetPackage");
|
|
overlayIsStatic = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isStatic",
|
|
false);
|
|
overlayPriority = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "priority", 0);
|
|
} else if (TAG_USES_SPLIT.equals(parser.getName())) {
|
|
if (usesSplitName != null) {
|
|
Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
|
|
continue;
|
|
}
|
|
|
|
usesSplitName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
|
|
if (usesSplitName == null) {
|
|
return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
|
|
"<uses-split> tag requires 'android:name' attribute");
|
|
}
|
|
} else if (TAG_USES_SDK.equals(parser.getName())) {
|
|
// Mirrors FrameworkParsingPackageUtils#parseUsesSdk until lite and full parsing is combined
|
|
String minSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
|
|
"minSdkVersion");
|
|
String targetSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
|
|
"targetSdkVersion");
|
|
|
|
int minVer = DEFAULT_MIN_SDK_VERSION;
|
|
String minCode = null;
|
|
boolean minAssigned = false;
|
|
int targetVer = DEFAULT_TARGET_SDK_VERSION;
|
|
String targetCode = null;
|
|
|
|
if (!TextUtils.isEmpty(minSdkVersionString)) {
|
|
try {
|
|
minVer = Integer.parseInt(minSdkVersionString);
|
|
minAssigned = true;
|
|
} catch (NumberFormatException ignored) {
|
|
minCode = minSdkVersionString;
|
|
minAssigned = !TextUtils.isEmpty(minCode);
|
|
}
|
|
}
|
|
|
|
if (!TextUtils.isEmpty(targetSdkVersionString)) {
|
|
try {
|
|
targetVer = Integer.parseInt(targetSdkVersionString);
|
|
} catch (NumberFormatException ignored) {
|
|
targetCode = targetSdkVersionString;
|
|
if (!minAssigned) {
|
|
minCode = targetCode;
|
|
}
|
|
}
|
|
} else {
|
|
targetVer = minVer;
|
|
targetCode = minCode;
|
|
}
|
|
|
|
boolean allowUnknownCodenames = false;
|
|
if ((flags & FrameworkParsingPackageUtils.PARSE_APK_IN_APEX) != 0) {
|
|
allowUnknownCodenames = true;
|
|
}
|
|
|
|
ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
|
|
targetVer, targetCode, SDK_CODENAMES, input,
|
|
allowUnknownCodenames);
|
|
if (targetResult.isError()) {
|
|
return input.error(targetResult);
|
|
}
|
|
targetSdkVersion = targetResult.getResult();
|
|
|
|
ParseResult<Integer> minResult = FrameworkParsingPackageUtils.computeMinSdkVersion(
|
|
minVer, minCode, SDK_VERSION, SDK_CODENAMES, input);
|
|
if (minResult.isError()) {
|
|
return input.error(minResult);
|
|
}
|
|
minSdkVersion = minResult.getResult();
|
|
}
|
|
}
|
|
|
|
// Check to see if overlay should be excluded based on system property condition
|
|
if ((flags & FrameworkParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY)
|
|
== 0 && !FrameworkParsingPackageUtils.checkRequiredSystemProperties(
|
|
requiredSystemPropertyName, requiredSystemPropertyValue)) {
|
|
String message = "Skipping target and overlay pair " + targetPackage + " and "
|
|
+ codePath + ": overlay ignored due to required system property: "
|
|
+ requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue;
|
|
Slog.i(TAG, message);
|
|
return input.skip(message);
|
|
}
|
|
|
|
return input.success(
|
|
new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
|
|
configForSplit, usesSplitName, isSplitRequired, versionCode,
|
|
versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
|
|
coreApp, debuggable, profilableByShell, multiArch, use32bitAbi,
|
|
useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage,
|
|
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
|
|
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
|
|
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
|
|
hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller));
|
|
}
|
|
|
|
private static boolean isDeviceAdminReceiver(
|
|
XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)
|
|
throws XmlPullParserException, IOException {
|
|
String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
|
|
"permission");
|
|
if (!applicationHasBindDeviceAdminPermission
|
|
&& !android.Manifest.permission.BIND_DEVICE_ADMIN.equals(permission)) {
|
|
return false;
|
|
}
|
|
|
|
boolean hasDeviceAdminReceiver = false;
|
|
final int depth = parser.getDepth();
|
|
int type;
|
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
|
&& (type != XmlPullParser.END_TAG || parser.getDepth() > depth)) {
|
|
if (type == XmlPullParser.END_TAG
|
|
|| type == XmlPullParser.TEXT) {
|
|
continue;
|
|
}
|
|
if (parser.getDepth() != depth + 1) {
|
|
// Search only under <receiver>.
|
|
continue;
|
|
}
|
|
if (!hasDeviceAdminReceiver && "meta-data".equals(parser.getName())) {
|
|
String name = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
|
|
"name");
|
|
if (DeviceAdminReceiver.DEVICE_ADMIN_META_DATA.equals(name)) {
|
|
hasDeviceAdminReceiver = true;
|
|
}
|
|
}
|
|
}
|
|
return hasDeviceAdminReceiver;
|
|
}
|
|
|
|
public static ParseResult<Pair<String, String>> parsePackageSplitNames(ParseInput input,
|
|
XmlResourceParser parser) throws IOException, XmlPullParserException {
|
|
int type;
|
|
while ((type = parser.next()) != XmlPullParser.START_TAG
|
|
&& type != XmlPullParser.END_DOCUMENT) {
|
|
}
|
|
|
|
if (type != XmlPullParser.START_TAG) {
|
|
return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
|
|
"No start tag found");
|
|
}
|
|
if (!parser.getName().equals(TAG_MANIFEST)) {
|
|
return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
|
|
"No <manifest> tag");
|
|
}
|
|
|
|
final String packageName = parser.getAttributeValue(null, "package");
|
|
if (!"android".equals(packageName)) {
|
|
final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input,
|
|
packageName, true, true);
|
|
if (nameResult.isError()) {
|
|
return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
|
|
"Invalid manifest package: " + nameResult.getErrorMessage());
|
|
}
|
|
}
|
|
|
|
String splitName = parser.getAttributeValue(null, "split");
|
|
if (splitName != null) {
|
|
if (splitName.length() == 0) {
|
|
splitName = null;
|
|
} else {
|
|
final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input,
|
|
splitName, false, false);
|
|
if (nameResult.isError()) {
|
|
return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
|
|
"Invalid manifest split: " + nameResult.getErrorMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
return input.success(Pair.create(packageName.intern(),
|
|
(splitName != null) ? splitName.intern() : splitName));
|
|
}
|
|
|
|
/**
|
|
* Utility method that parses attributes android:requiredSplitTypes and android:splitTypes.
|
|
*/
|
|
public static ParseResult<Pair<Set<String>, Set<String>>> parseRequiredSplitTypes(
|
|
ParseInput input, XmlResourceParser parser) {
|
|
Set<String> requiredSplitTypes = null;
|
|
Set<String> splitTypes = null;
|
|
String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "requiredSplitTypes");
|
|
if (!TextUtils.isEmpty(value)) {
|
|
final ParseResult<Set<String>> result = separateAndValidateSplitTypes(input, value);
|
|
if (result.isError()) {
|
|
return input.error(result);
|
|
}
|
|
requiredSplitTypes = result.getResult();
|
|
}
|
|
|
|
value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "splitTypes");
|
|
if (!TextUtils.isEmpty(value)) {
|
|
final ParseResult<Set<String>> result = separateAndValidateSplitTypes(input, value);
|
|
if (result.isError()) {
|
|
return input.error(result);
|
|
}
|
|
splitTypes = result.getResult();
|
|
}
|
|
|
|
return input.success(Pair.create(requiredSplitTypes, splitTypes));
|
|
}
|
|
|
|
private static ParseResult<Set<String>> separateAndValidateSplitTypes(ParseInput input,
|
|
String values) {
|
|
final Set<String> ret = new ArraySet<>();
|
|
for (String value : values.trim().split(",")) {
|
|
final String type = value.trim();
|
|
// Using requireFilename as true because it limits length of the name to the
|
|
// {@link #MAX_FILE_NAME_SIZE}.
|
|
final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, type,
|
|
false /* requireSeparator */, true /* requireFilename */);
|
|
if (nameResult.isError()) {
|
|
return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
|
|
"Invalid manifest split types: " + nameResult.getErrorMessage());
|
|
}
|
|
if (!ret.add(type)) {
|
|
Slog.w(TAG, type + " was defined multiple times");
|
|
}
|
|
}
|
|
return input.success(ret);
|
|
}
|
|
|
|
public static VerifierInfo parseVerifier(AttributeSet attrs) {
|
|
String packageName = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
|
|
String encodedPublicKey = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "publicKey");
|
|
|
|
if (packageName == null || packageName.length() == 0) {
|
|
Slog.i(TAG, "verifier package name was null; skipping");
|
|
return null;
|
|
}
|
|
|
|
final PublicKey publicKey = FrameworkParsingPackageUtils.parsePublicKey(encodedPublicKey);
|
|
if (publicKey == null) {
|
|
Slog.i(TAG, "Unable to parse verifier public key for " + packageName);
|
|
return null;
|
|
}
|
|
|
|
return new VerifierInfo(packageName, publicKey);
|
|
}
|
|
|
|
/**
|
|
* Used to sort a set of APKs based on their split names, always placing the
|
|
* base APK (with {@code null} split name) first.
|
|
*/
|
|
private static class SplitNameComparator implements Comparator<String> {
|
|
@Override
|
|
public int compare(String lhs, String rhs) {
|
|
if (lhs == null) {
|
|
return -1;
|
|
} else if (rhs == null) {
|
|
return 1;
|
|
} else {
|
|
return lhs.compareTo(rhs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the given file is an APK file.
|
|
*
|
|
* @param file the file to check.
|
|
* @return {@code true} if the given file is an APK file.
|
|
*/
|
|
public static boolean isApkFile(File file) {
|
|
return isApkPath(file.getName());
|
|
}
|
|
|
|
/**
|
|
* Check if the given path ends with APK file extension.
|
|
*
|
|
* @param path the path to check.
|
|
* @return {@code true} if the given path ends with APK file extension.
|
|
*/
|
|
public static boolean isApkPath(String path) {
|
|
return path.endsWith(APK_FILE_EXTENSION);
|
|
}
|
|
}
|