blob: aeb4db4849fd479bc4fed170c2a86c42921ebcbd [file] [log] [blame]
/*
* Copyright (C) 2020 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.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
import static android.os.Build.VERSION_CODES.DONUT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import android.annotation.AnyRes;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleableRes;
import android.app.ActivityThread;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.FeatureGroupInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.Signature;
import android.content.pm.parsing.component.ComponentParseUtils;
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedActivityUtils;
import android.content.pm.parsing.component.ParsedAttribution;
import android.content.pm.parsing.component.ParsedAttributionUtils;
import android.content.pm.parsing.component.ParsedInstrumentation;
import android.content.pm.parsing.component.ParsedInstrumentationUtils;
import android.content.pm.parsing.component.ParsedIntentInfo;
import android.content.pm.parsing.component.ParsedIntentInfoUtils;
import android.content.pm.parsing.component.ParsedMainComponent;
import android.content.pm.parsing.component.ParsedPermission;
import android.content.pm.parsing.component.ParsedPermissionGroup;
import android.content.pm.parsing.component.ParsedPermissionUtils;
import android.content.pm.parsing.component.ParsedProcess;
import android.content.pm.parsing.component.ParsedProcessUtils;
import android.content.pm.parsing.component.ParsedProvider;
import android.content.pm.parsing.component.ParsedProviderUtils;
import android.content.pm.parsing.component.ParsedService;
import android.content.pm.parsing.component.ParsedServiceUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseInput.DeferredError;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
import android.content.pm.split.SplitAssetLoader;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.Trace;
import android.os.ext.SdkExtensions;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.R;
import com.android.internal.os.ClassLoaderFactory;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/**
* TODO(b/135203078): Differentiate between parse_ methods and some add_ method for whether it
* mutates the passed-in component or not. Or consolidate so all parse_ methods mutate.
*
* @hide
*/
public class ParsingPackageUtils {
public static final String TAG = ParsingUtils.TAG;
/**
* For cases outside of PackageManagerService when an APK needs to be parsed as a one-off
* request, without caching the input object and without querying the internal system state
* for feature support.
*/
@NonNull
public static ParseResult<ParsingPackage> parseDefaultOneTime(File file, int flags,
@NonNull ParseInput.Callback inputCallback, @NonNull Callback callback) {
if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
// Caller expressed no opinion about what encryption
// aware/unaware components they want to see, so match both
flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
}
ParseInput input = new ParseTypeImpl(inputCallback).reset();
ParseResult<ParsingPackage> result;
ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, callback);
try {
result = parser.parsePackage(input, file, flags);
if (result.isError()) {
return result;
}
} catch (PackageParser.PackageParserException e) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Error parsing package", e);
}
try {
ParsingPackage pkg = result.getResult();
if ((flags & PackageManager.GET_SIGNATURES) != 0
|| (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
ParsingPackageUtils.collectCertificates(pkg, false /* skipVerify */);
}
return input.success(pkg);
} catch (PackageParser.PackageParserException e) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Error collecting package certificates", e);
}
}
private boolean mOnlyCoreApps;
private String[] mSeparateProcesses;
private DisplayMetrics mDisplayMetrics;
private Callback mCallback;
public ParsingPackageUtils(boolean onlyCoreApps, String[] separateProcesses,
DisplayMetrics displayMetrics, @NonNull Callback callback) {
mOnlyCoreApps = onlyCoreApps;
mSeparateProcesses = separateProcesses;
mDisplayMetrics = displayMetrics;
mCallback = callback;
}
/**
* Parse 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 sanity checking on cluster style packages, such as
* requiring identical package name and version codes, a single base APK,
* and unique split names.
* <p>
* Note that this <em>does not</em> perform signature verification; that
* must be done separately in {@link #collectCertificates(ParsingPackageRead, boolean)}.
*
* If {@code useCaches} is true, the package parser might return a cached
* result from a previous parse of the same {@code packageFile} with the same
* {@code flags}. Note that this method does not check whether {@code packageFile}
* has changed since the last parse, it's up to callers to do so.
*
* @see PackageParser#parsePackageLite(File, int)
*/
public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile,
int flags)
throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(input, packageFile, flags);
} else {
return parseMonolithicPackage(input, packageFile, flags);
}
}
/**
* Parse all APKs contained in the given directory, treating them as a
* single package. This also performs sanity checking, such as requiring
* identical package name and version codes, a single base APK, and unique
* split names.
* <p>
* Note that this <em>does not</em> perform signature verification; that
* must be done separately in {@link #collectCertificates(ParsingPackageRead, boolean)}.
*/
private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
int flags) throws PackageParserException {
final PackageParser.PackageLite lite = ApkLiteParseUtils.parseClusterPackageLite(packageDir,
0);
if (mOnlyCoreApps && !lite.coreApp) {
return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
"Not a coreApp: " + packageDir);
}
// Build the split dependency tree.
SparseArray<int[]> splitDependencies = null;
final SplitAssetLoader assetLoader;
if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
try {
splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
} catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
}
} else {
assetLoader = new DefaultSplitAssetLoader(lite, flags);
}
try {
final AssetManager assets = assetLoader.getBaseAssetManager();
final File baseApk = new File(lite.baseCodePath);
ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
lite.codePath, assets, flags);
if (result.isError()) {
return input.error(result);
}
ParsingPackage pkg = result.getResult();
if (!ArrayUtils.isEmpty(lite.splitNames)) {
pkg.asSplit(
lite.splitNames,
lite.splitCodePaths,
lite.splitRevisionCodes,
splitDependencies
);
final int num = lite.splitNames.length;
for (int i = 0; i < num; i++) {
final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
parseSplitApk(input, pkg, i, splitAssets, flags);
}
}
pkg.setUse32BitAbi(lite.use32bitAbi);
return input.success(pkg);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}
/**
* Parse the given APK file, treating it as as a single monolithic package.
* <p>
* Note that this <em>does not</em> perform signature verification; that
* must be done separately in {@link #collectCertificates(ParsingPackageRead, boolean)}.
*/
private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
int flags) throws PackageParserException {
final PackageParser.PackageLite lite = ApkLiteParseUtils.parseMonolithicPackageLite(apkFile,
flags);
if (mOnlyCoreApps && !lite.coreApp) {
return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
"Not a coreApp: " + apkFile);
}
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
ParseResult<ParsingPackage> result = parseBaseApk(input,
apkFile,
apkFile.getCanonicalPath(),
assetLoader.getBaseAssetManager(), flags);
if (result.isError()) {
return input.error(result);
}
return input.success(result.getResult()
.setUse32BitAbi(lite.use32bitAbi));
} catch (IOException e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
String codePath, AssetManager assets, int flags) {
final String apkPath = apkFile.getAbsolutePath();
String volumeUuid = null;
if (apkPath.startsWith(PackageParser.MNT_EXPAND)) {
final int end = apkPath.indexOf('/', PackageParser.MNT_EXPAND.length());
volumeUuid = apkPath.substring(PackageParser.MNT_EXPAND.length(), end);
}
if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
PackageParser.ANDROID_MANIFEST_FILENAME)) {
final Resources res = new Resources(assets, mDisplayMetrics, null);
ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
parser, flags);
if (result.isError()) {
return input.error(result.getErrorCode(),
apkPath + " (at " + parser.getPositionDescription() + "): "
+ result.getErrorMessage());
}
final ParsingPackage pkg = result.getResult();
if (assets.containsAllocatedTable()) {
final ParseResult<?> deferResult = input.deferError(
"Targeting R+ (version " + Build.VERSION_CODES.R + " and above) requires"
+ " the resources.arsc of installed APKs to be stored uncompressed"
+ " and aligned on a 4-byte boundary",
DeferredError.RESOURCES_ARSC_COMPRESSED);
if (deferResult.isError()) {
return input.error(INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED,
deferResult.getErrorMessage());
}
}
ApkAssets apkAssets = assets.getApkAssets()[0];
if (apkAssets.definesOverlayable()) {
SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
int size = packageNames.size();
for (int index = 0; index < size; index++) {
String packageName = packageNames.get(index);
Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
for (String overlayable : overlayableToActor.keySet()) {
pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable));
}
}
}
}
pkg.setVolumeUuid(volumeUuid);
if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) {
pkg.setSigningDetails(ParsingPackageUtils.collectCertificates(pkg, false));
} else {
pkg.setSigningDetails(SigningDetails.UNKNOWN);
}
return input.success(pkg);
} catch (Exception e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
}
}
private ParseResult<ParsingPackage> parseSplitApk(ParseInput input,
ParsingPackage pkg, int splitIndex, AssetManager assets, int flags) {
final String apkPath = pkg.getSplitCodePaths()[splitIndex];
if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
// This must always succeed, as the path has been added to the AssetManager before.
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
PackageParser.ANDROID_MANIFEST_FILENAME)) {
Resources res = new Resources(assets, mDisplayMetrics, null);
ParseResult<ParsingPackage> parseResult = parseSplitApk(input, pkg, res,
parser, flags, splitIndex);
if (parseResult.isError()) {
return input.error(parseResult.getErrorCode(),
apkPath + " (at " + parser.getPositionDescription() + "): "
+ parseResult.getErrorMessage());
}
return parseResult;
} catch (Exception e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
}
}
/**
* Parse the manifest of a <em>base APK</em>. When adding new features you
* need to consider whether they should be supported by split APKs and child
* packages.
*
* @param apkPath The package apk file path
* @param res The resources from which to resolve values
* @param parser The manifest parser
* @param flags Flags how to parse
* @return Parsed package or null on error.
*/
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
String codePath, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException, PackageParserException {
final String splitName;
final String pkgName;
try {
Pair<String, String> packageSplit = PackageParser.parsePackageSplitNames(parser,
parser);
pkgName = packageSplit.first;
splitName = packageSplit.second;
if (!TextUtils.isEmpty(splitName)) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
"Expected base APK, but found split " + splitName
);
}
} catch (PackageParserException e) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME);
}
final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
try {
final boolean isCoreApp =
parser.getAttributeBooleanValue(null, "coreApp", false);
final ParsingPackage pkg = mCallback.startParsingPackage(
pkgName, apkPath, codePath, manifestArray, isCoreApp);
final ParseResult<ParsingPackage> result =
parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
if (result.isError()) {
return result;
}
return input.success(pkg);
} finally {
manifestArray.recycle();
}
}
/**
* Parse the manifest of a <em>split APK</em>.
* <p>
* Note that split APKs have many more restrictions on what they're capable
* of doing, so many valid features of a base APK have been carefully
* omitted here.
*
* @param pkg builder to fill
* @return false on failure
*/
private ParseResult<ParsingPackage> parseSplitApk(ParseInput input, ParsingPackage pkg,
Resources res, XmlResourceParser parser, int flags, int splitIndex)
throws XmlPullParserException, IOException, PackageParserException {
AttributeSet attrs = parser;
// We parsed manifest tag earlier; just skip past it
PackageParser.parsePackageSplitNames(parser, attrs);
int type;
boolean foundApp = false;
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) {
continue;
}
final ParseResult result;
String tagName = parser.getName();
if (PackageParser.TAG_APPLICATION.equals(tagName)) {
if (foundApp) {
if (PackageParser.RIGID_PARSER) {
result = input.error("<manifest> has more than one <application>");
} else {
Slog.w(TAG, "<manifest> has more than one <application>");
result = input.success(null);
}
} else {
foundApp = true;
result = parseSplitApplication(input, pkg, res, parser, flags, splitIndex);
}
} else {
result = ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
}
if (result.isError()) {
return input.error(result);
}
}
if (!foundApp) {
ParseResult<?> deferResult = input.deferError(
"<manifest> does not contain an <application>", DeferredError.MISSING_APP_TAG);
if (deferResult.isError()) {
return input.error(deferResult);
}
}
return input.success(pkg);
}
/**
* Parse the {@code application} XML tree at the current parse location in a
* <em>split APK</em> manifest.
* <p>
* Note that split APKs have many more restrictions on what they're capable
* of doing, so many valid features of a base APK have been carefully
* omitted here.
*/
private ParseResult<ParsingPackage> parseSplitApplication(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, int splitIndex)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
try {
pkg.setSplitHasCode(splitIndex, sa.getBoolean(
R.styleable.AndroidManifestApplication_hasCode, true));
final String classLoaderName = sa.getString(
R.styleable.AndroidManifestApplication_classLoader);
if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(
classLoaderName)) {
pkg.setSplitClassLoaderName(splitIndex, classLoaderName);
} else {
return input.error("Invalid class loader name: " + classLoaderName);
}
} finally {
sa.recycle();
}
final int depth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
ParsedMainComponent mainComponent = null;
final ParseResult result;
String tagName = parser.getName();
boolean isActivity = false;
switch (tagName) {
case "activity":
isActivity = true;
// fall-through
case "receiver":
ParseResult<ParsedActivity> activityResult =
ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
res,
parser, flags, PackageParser.sUseRoundIcon, input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
if (isActivity) {
pkg.addActivity(activity);
} else {
pkg.addReceiver(activity);
}
mainComponent = activity;
}
result = activityResult;
break;
case "service":
ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(
mSeparateProcesses, pkg, res, parser, flags,
PackageParser.sUseRoundIcon, input);
if (serviceResult.isSuccess()) {
ParsedService service = serviceResult.getResult();
pkg.addService(service);
mainComponent = service;
}
result = serviceResult;
break;
case "provider":
ParseResult<ParsedProvider> providerResult =
ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
flags, PackageParser.sUseRoundIcon, input);
if (providerResult.isSuccess()) {
ParsedProvider provider = providerResult.getResult();
pkg.addProvider(provider);
mainComponent = provider;
}
result = providerResult;
break;
case "activity-alias":
activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser,
PackageParser.sUseRoundIcon, input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
pkg.addActivity(activity);
mainComponent = activity;
}
result = activityResult;
break;
default:
result = parseSplitBaseAppChildTags(input, tagName, pkg, res, parser);
break;
}
if (result.isError()) {
return input.error(result);
}
if (mainComponent != null && mainComponent.getSplitName() == null) {
// If the loaded component did not specify a split, inherit the split name
// based on the split it is defined in.
// This is used to later load the correct split when starting this
// component.
mainComponent.setSplitName(pkg.getSplitNames()[splitIndex]);
}
}
return input.success(pkg);
}
/**
* For parsing non-MainComponents. Main ones have an order and some special handling which is
* done directly in {@link #parseSplitApplication(ParseInput, ParsingPackage, Resources,
* XmlResourceParser, int, int)}.
*/
private ParseResult parseSplitBaseAppChildTags(ParseInput input, String tag, ParsingPackage pkg,
Resources res, XmlResourceParser parser) throws IOException, XmlPullParserException {
switch (tag) {
case "meta-data":
// note: application meta-data is stored off to the side, so it can
// remain null in the primary copy (we like to avoid extra copies because
// it can be large)
ParseResult<Bundle> metaDataResult = parseMetaData(pkg, res, parser,
pkg.getMetaData(), input);
if (metaDataResult.isSuccess()) {
pkg.setMetaData(metaDataResult.getResult());
}
return metaDataResult;
case "uses-static-library":
return parseUsesStaticLibrary(input, pkg, res, parser);
case "uses-library":
return parseUsesLibrary(input, pkg, res, parser);
case "uses-package":
// Dependencies for app installers; we don't currently try to
// enforce this.
return input.success(null);
default:
return ParsingUtils.unknownTag("<application>", pkg, parser, input);
}
}
private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
TypedArray sa, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException {
ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
if (sharedUserResult.isError()) {
return sharedUserResult;
}
pkg.setInstallLocation(anInteger(PackageParser.PARSE_DEFAULT_INSTALL_LOCATION,
R.styleable.AndroidManifest_installLocation, sa))
.setTargetSandboxVersion(anInteger(PackageParser.PARSE_DEFAULT_TARGET_SANDBOX,
R.styleable.AndroidManifest_targetSandboxVersion, sa))
/* Set the global "on SD card" flag */
.setExternalStorage((flags & PackageParser.PARSE_EXTERNAL_STORAGE) != 0);
boolean foundApp = 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.START_TAG) {
continue;
}
String tagName = parser.getName();
final ParseResult result;
// TODO(b/135203078): Convert to instance methods to share variables
// <application> has special logic, so it's handled outside the general method
if (PackageParser.TAG_APPLICATION.equals(tagName)) {
if (foundApp) {
if (PackageParser.RIGID_PARSER) {
result = input.error("<manifest> has more than one <application>");
} else {
Slog.w(TAG, "<manifest> has more than one <application>");
result = input.success(null);
}
} else {
foundApp = true;
result = parseBaseApplication(input, pkg, res, parser, flags);
}
} else {
result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
}
if (result.isError()) {
return input.error(result);
}
}
if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
ParseResult<?> deferResult = input.deferError(
"<manifest> does not contain an <application> or <instrumentation>",
DeferredError.MISSING_APP_TAG);
if (deferResult.isError()) {
return input.error(deferResult);
}
}
if (!ParsedAttribution.isCombinationValid(pkg.getAttributions())) {
return input.error(
INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Combination <feature> tags are not valid"
);
}
convertNewPermissions(pkg);
convertSplitPermissions(pkg);
// At this point we can check if an application is not supporting densities and hence
// cannot be windowed / resized. Note that an SDK version of 0 is common for
// pre-Doughnut applications.
if (pkg.getTargetSdkVersion() < DONUT
|| (!pkg.isSupportsSmallScreens()
&& !pkg.isSupportsNormalScreens()
&& !pkg.isSupportsLargeScreens()
&& !pkg.isSupportsExtraLargeScreens()
&& !pkg.isResizeable()
&& !pkg.isAnyDensity())) {
adjustPackageToBeUnresizeableAndUnpipable(pkg);
}
return input.success(pkg);
}
private ParseResult parseBaseApkTag(String tag, ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws IOException, XmlPullParserException {
switch (tag) {
case PackageParser.TAG_OVERLAY:
return parseOverlay(input, pkg, res, parser);
case PackageParser.TAG_KEY_SETS:
return parseKeySets(input, pkg, res, parser);
case "feature": // TODO moltmann: Remove
case PackageParser.TAG_ATTRIBUTION:
return parseAttribution(input, pkg, res, parser);
case PackageParser.TAG_PERMISSION_GROUP:
return parsePermissionGroup(input, pkg, res, parser);
case PackageParser.TAG_PERMISSION:
return parsePermission(input, pkg, res, parser);
case PackageParser.TAG_PERMISSION_TREE:
return parsePermissionTree(input, pkg, res, parser);
case PackageParser.TAG_USES_PERMISSION:
case PackageParser.TAG_USES_PERMISSION_SDK_M:
case PackageParser.TAG_USES_PERMISSION_SDK_23:
return parseUsesPermission(input, pkg, res, parser);
case PackageParser.TAG_USES_CONFIGURATION:
return parseUsesConfiguration(input, pkg, res, parser);
case PackageParser.TAG_USES_FEATURE:
return parseUsesFeature(input, pkg, res, parser);
case PackageParser.TAG_FEATURE_GROUP:
return parseFeatureGroup(input, pkg, res, parser);
case PackageParser.TAG_USES_SDK:
return parseUsesSdk(input, pkg, res, parser);
case PackageParser.TAG_SUPPORT_SCREENS:
return parseSupportScreens(input, pkg, res, parser);
case PackageParser.TAG_PROTECTED_BROADCAST:
return parseProtectedBroadcast(input, pkg, res, parser);
case PackageParser.TAG_INSTRUMENTATION:
return parseInstrumentation(input, pkg, res, parser);
case PackageParser.TAG_ORIGINAL_PACKAGE:
return parseOriginalPackage(input, pkg, res, parser);
case PackageParser.TAG_ADOPT_PERMISSIONS:
return parseAdoptPermissions(input, pkg, res, parser);
case PackageParser.TAG_USES_GL_TEXTURE:
case PackageParser.TAG_COMPATIBLE_SCREENS:
case PackageParser.TAG_SUPPORTS_INPUT:
case PackageParser.TAG_EAT_COMMENT:
// Just skip this tag
XmlUtils.skipCurrentTag(parser);
return input.success(pkg);
case PackageParser.TAG_RESTRICT_UPDATE:
return parseRestrictUpdateHash(flags, input, pkg, res, parser);
case PackageParser.TAG_QUERIES:
return parseQueries(input, pkg, res, parser);
default:
return ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
}
}
private static ParseResult<ParsingPackage> parseSharedUser(ParseInput input,
ParsingPackage pkg, TypedArray sa) {
String str = nonConfigString(0, R.styleable.AndroidManifest_sharedUserId, sa);
if (TextUtils.isEmpty(str)) {
return input.success(pkg);
}
if (!"android".equals(pkg.getPackageName())) {
ParseResult<?> nameResult = validateName(input, str, true, true);
if (nameResult.isError()) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
"<manifest> specifies bad sharedUserId name \"" + str + "\": "
+ nameResult.getErrorMessage());
}
}
return input.success(pkg
.setSharedUserId(str.intern())
.setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
}
private static ParseResult<ParsingPackage> parseKeySets(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
// we've encountered the 'key-sets' tag
// all the keys and keysets that we want must be defined here
// so we're going to iterate over the parser and pull out the things we want
int outerDepth = parser.getDepth();
int currentKeySetDepth = -1;
int type;
String currentKeySet = null;
ArrayMap<String, PublicKey> publicKeys = new ArrayMap<>();
ArraySet<String> upgradeKeySets = new ArraySet<>();
ArrayMap<String, ArraySet<String>> definedKeySets = new ArrayMap<>();
ArraySet<String> improperKeySets = new ArraySet<>();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG) {
if (parser.getDepth() == currentKeySetDepth) {
currentKeySet = null;
currentKeySetDepth = -1;
}
continue;
}
String tagName = parser.getName();
switch (tagName) {
case "key-set": {
if (currentKeySet != null) {
return input.error("Improperly nested 'key-set' tag at "
+ parser.getPositionDescription());
}
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestKeySet);
try {
final String keysetName = sa.getNonResourceString(
R.styleable.AndroidManifestKeySet_name);
definedKeySets.put(keysetName, new ArraySet<>());
currentKeySet = keysetName;
currentKeySetDepth = parser.getDepth();
} finally {
sa.recycle();
}
} break;
case "public-key": {
if (currentKeySet == null) {
return input.error("Improperly nested 'key-set' tag at "
+ parser.getPositionDescription());
}
TypedArray sa = res.obtainAttributes(parser,
R.styleable.AndroidManifestPublicKey);
try {
final String publicKeyName = nonResString(
R.styleable.AndroidManifestPublicKey_name, sa);
final String encodedKey = nonResString(
R.styleable.AndroidManifestPublicKey_value, sa);
if (encodedKey == null && publicKeys.get(publicKeyName) == null) {
return input.error("'public-key' " + publicKeyName
+ " must define a public-key value on first use at "
+ parser.getPositionDescription());
} else if (encodedKey != null) {
PublicKey currentKey = PackageParser.parsePublicKey(encodedKey);
if (currentKey == null) {
Slog.w(TAG, "No recognized valid key in 'public-key' tag at "
+ parser.getPositionDescription() + " key-set "
+ currentKeySet
+ " will not be added to the package's defined key-sets.");
improperKeySets.add(currentKeySet);
XmlUtils.skipCurrentTag(parser);
continue;
}
if (publicKeys.get(publicKeyName) == null
|| publicKeys.get(publicKeyName).equals(currentKey)) {
/* public-key first definition, or matches old definition */
publicKeys.put(publicKeyName, currentKey);
} else {
return input.error("Value of 'public-key' " + publicKeyName
+ " conflicts with previously defined value at "
+ parser.getPositionDescription());
}
}
definedKeySets.get(currentKeySet).add(publicKeyName);
XmlUtils.skipCurrentTag(parser);
} finally {
sa.recycle();
}
} break;
case "upgrade-key-set": {
TypedArray sa = res.obtainAttributes(parser,
R.styleable.AndroidManifestUpgradeKeySet);
try {
String name = sa.getNonResourceString(
R.styleable.AndroidManifestUpgradeKeySet_name);
upgradeKeySets.add(name);
XmlUtils.skipCurrentTag(parser);
} finally {
sa.recycle();
}
} break;
default:
ParseResult result = ParsingUtils.unknownTag("<key-sets>", pkg, parser,
input);
if (result.isError()) {
return input.error(result);
}
break;
}
}
String packageName = pkg.getPackageName();
Set<String> publicKeyNames = publicKeys.keySet();
if (publicKeyNames.removeAll(definedKeySets.keySet())) {
return input.error("Package" + packageName
+ " AndroidManifest.xml 'key-set' and 'public-key' names must be distinct.");
}
for (ArrayMap.Entry<String, ArraySet<String>> e : definedKeySets.entrySet()) {
final String keySetName = e.getKey();
if (e.getValue().size() == 0) {
Slog.w(TAG, "Package" + packageName + " AndroidManifest.xml "
+ "'key-set' " + keySetName + " has no valid associated 'public-key'."
+ " Not including in package's defined key-sets.");
continue;
} else if (improperKeySets.contains(keySetName)) {
Slog.w(TAG, "Package" + packageName + " AndroidManifest.xml "
+ "'key-set' " + keySetName + " contained improper 'public-key'"
+ " tags. Not including in package's defined key-sets.");
continue;
}
for (String s : e.getValue()) {
pkg.addKeySet(keySetName, publicKeys.get(s));
}
}
if (pkg.getKeySetMapping().keySet().containsAll(upgradeKeySets)) {
pkg.setUpgradeKeySets(upgradeKeySets);
} else {
return input.error("Package" + packageName
+ " AndroidManifest.xml does not define all 'upgrade-key-set's .");
}
return input.success(pkg);
}
private static ParseResult<ParsingPackage> parseAttribution(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws IOException, XmlPullParserException {
ParseResult<ParsedAttribution> result = ParsedAttributionUtils.parseAttribution(res,
parser, input);
if (result.isError()) {
return input.error(result);
}
return input.success(pkg.addAttribution(result.getResult()));
}
private static ParseResult<ParsingPackage> parsePermissionGroup(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
ParseResult<ParsedPermissionGroup> result = ParsedPermissionUtils.parsePermissionGroup(
pkg, res, parser, PackageParser.sUseRoundIcon, input);
if (result.isError()) {
return input.error(result);
}
return input.success(pkg.addPermissionGroup(result.getResult()));
}
private static ParseResult<ParsingPackage> parsePermission(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermission(
pkg, res, parser, PackageParser.sUseRoundIcon, input);
if (result.isError()) {
return input.error(result);
}
return input.success(pkg.addPermission(result.getResult()));
}
private static ParseResult<ParsingPackage> parsePermissionTree(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermissionTree(
pkg, res, parser, PackageParser.sUseRoundIcon, input);
if (result.isError()) {
return input.error(result);
}
return input.success(pkg.addPermission(result.getResult()));
}
private ParseResult<ParsingPackage> parseUsesPermission(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws IOException, XmlPullParserException {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesPermission);
try {
// Note: don't allow this value to be a reference to a resource
// that may change.
String name = sa.getNonResourceString(
R.styleable.AndroidManifestUsesPermission_name);
int maxSdkVersion = 0;
TypedValue val = sa.peekValue(
R.styleable.AndroidManifestUsesPermission_maxSdkVersion);
if (val != null) {
if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) {
maxSdkVersion = val.data;
}
}
final String requiredFeature = sa.getNonConfigurationString(
R.styleable.AndroidManifestUsesPermission_requiredFeature, 0);
final String requiredNotfeature = sa.getNonConfigurationString(
R.styleable.AndroidManifestUsesPermission_requiredNotFeature,
0);
XmlUtils.skipCurrentTag(parser);
// Can only succeed from here on out
ParseResult<ParsingPackage> success = input.success(pkg);
if (name == null) {
return success;
}
if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
return success;
}
// Only allow requesting this permission if the platform supports the given feature.
if (requiredFeature != null && mCallback != null && !mCallback.hasFeature(
requiredFeature)) {
return success;
}
// Only allow requesting this permission if the platform doesn't support the given
// feature.
if (requiredNotfeature != null && mCallback != null
&& mCallback.hasFeature(requiredNotfeature)) {
return success;
}
if (!pkg.getRequestedPermissions().contains(name)) {
pkg.addRequestedPermission(name.intern());
} else {
Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ name + " in package: " + pkg.getPackageName() + " at: "
+ parser.getPositionDescription());
}
return success;
} finally {
sa.recycle();
}
}
private static ParseResult<ParsingPackage> parseUsesConfiguration(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
ConfigurationInfo cPref = new ConfigurationInfo();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesConfiguration);
try {
cPref.reqTouchScreen = sa.getInt(
R.styleable.AndroidManifestUsesConfiguration_reqTouchScreen,
Configuration.TOUCHSCREEN_UNDEFINED);
cPref.reqKeyboardType = sa.getInt(
R.styleable.AndroidManifestUsesConfiguration_reqKeyboardType,
Configuration.KEYBOARD_UNDEFINED);
if (sa.getBoolean(
R.styleable.AndroidManifestUsesConfiguration_reqHardKeyboard,
false)) {
cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
}
cPref.reqNavigation = sa.getInt(
R.styleable.AndroidManifestUsesConfiguration_reqNavigation,
Configuration.NAVIGATION_UNDEFINED);
if (sa.getBoolean(
R.styleable.AndroidManifestUsesConfiguration_reqFiveWayNav,
false)) {
cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
}
pkg.addConfigPreference(cPref);
return input.success(pkg);
} finally {
sa.recycle();
}
}
private static ParseResult<ParsingPackage> parseUsesFeature(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
FeatureInfo fi = parseFeatureInfo(res, parser);
pkg.addReqFeature(fi);
if (fi.name == null) {
ConfigurationInfo cPref = new ConfigurationInfo();
cPref.reqGlEsVersion = fi.reqGlEsVersion;
pkg.addConfigPreference(cPref);
}
return input.success(pkg);
}
private static FeatureInfo parseFeatureInfo(Resources res, AttributeSet attrs) {
FeatureInfo fi = new FeatureInfo();
TypedArray sa = res.obtainAttributes(attrs, R.styleable.AndroidManifestUsesFeature);
try {
// Note: don't allow this value to be a reference to a resource
// that may change.
fi.name = sa.getNonResourceString(R.styleable.AndroidManifestUsesFeature_name);
fi.version = sa.getInt(R.styleable.AndroidManifestUsesFeature_version, 0);
if (fi.name == null) {
fi.reqGlEsVersion = sa.getInt(R.styleable.AndroidManifestUsesFeature_glEsVersion,
FeatureInfo.GL_ES_VERSION_UNDEFINED);
}
if (sa.getBoolean(R.styleable.AndroidManifestUsesFeature_required, true)) {
fi.flags |= FeatureInfo.FLAG_REQUIRED;
}
return fi;
} finally {
sa.recycle();
}
}
private static ParseResult<ParsingPackage> parseFeatureGroup(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws IOException, XmlPullParserException {
FeatureGroupInfo group = new FeatureGroupInfo();
ArrayList<FeatureInfo> features = null;
final int depth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String innerTagName = parser.getName();
if (innerTagName.equals("uses-feature")) {
FeatureInfo featureInfo = parseFeatureInfo(res, parser);
// FeatureGroups are stricter and mandate that
// any <uses-feature> declared are mandatory.
featureInfo.flags |= FeatureInfo.FLAG_REQUIRED;
features = ArrayUtils.add(features, featureInfo);
} else {
Slog.w(TAG,
"Unknown element under <feature-group>: " + innerTagName +
" at " + pkg.getBaseCodePath() + " " +
parser.getPositionDescription());
}
}
if (features != null) {
group.features = new FeatureInfo[features.size()];
group.features = features.toArray(group.features);
}
pkg.addFeatureGroup(group);
return input.success(pkg);
}
private static ParseResult<ParsingPackage> parseUsesSdk(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws IOException, XmlPullParserException {
if (PackageParser.SDK_VERSION > 0) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
try {
int minVers = 1;
String minCode = null;
int targetVers = 0;
String targetCode = null;
TypedValue val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_minSdkVersion);
if (val != null) {
if (val.type == TypedValue.TYPE_STRING && val.string != null) {
minCode = val.string.toString();
} else {
// If it's not a string, it's an integer.
minVers = val.data;
}
}
val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
if (val != null) {
if (val.type == TypedValue.TYPE_STRING && val.string != null) {
targetCode = val.string.toString();
if (minCode == null) {
minCode = targetCode;
}
} else {
// If it's not a string, it's an integer.
targetVers = val.data;
}
} else {
targetVers = minVers;
targetCode = minCode;
}
ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion(
targetVers, targetCode, PackageParser.SDK_CODENAMES, input);
if (targetSdkVersionResult.isError()) {
return input.error(targetSdkVersionResult);
}
int targetSdkVersion = targetSdkVersionResult.getResult();
ParseResult<?> deferResult =
input.enableDeferredError(pkg.getPackageName(), targetSdkVersion);
if (deferResult.isError()) {
return input.error(deferResult);
}
ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode,
PackageParser.SDK_VERSION, PackageParser.SDK_CODENAMES, input);
if (minSdkVersionResult.isError()) {
return input.error(minSdkVersionResult);
}
int minSdkVersion = minSdkVersionResult.getResult();
pkg.setMinSdkVersion(minSdkVersion)
.setTargetSdkVersion(targetSdkVersion);
int type;
final int innerDepth = parser.getDepth();
SparseIntArray minExtensionVersions = null;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
final ParseResult result;
if (parser.getName().equals("extension-sdk")) {
if (minExtensionVersions == null) {
minExtensionVersions = new SparseIntArray();
}
result = parseExtensionSdk(input, res, parser, minExtensionVersions);
XmlUtils.skipCurrentTag(parser);
} else {
result = ParsingUtils.unknownTag("<uses-sdk>", pkg, parser, input);
}
if (result.isError()) {
return input.error(result);
}
}
pkg.setMinExtensionVersions(exactSizedCopyOfSparseArray(minExtensionVersions));
} finally {
sa.recycle();
}
}
return input.success(pkg);
}
@Nullable
private static SparseIntArray exactSizedCopyOfSparseArray(@Nullable SparseIntArray input) {
if (input == null) {
return null;
}
SparseIntArray output = new SparseIntArray(input.size());
for (int i = 0; i < input.size(); i++) {
output.put(input.keyAt(i), input.valueAt(i));
}
return output;
}
private static ParseResult<SparseIntArray> parseExtensionSdk(
ParseInput input, Resources res, XmlResourceParser parser,
SparseIntArray minExtensionVersions) {
int sdkVersion;
int minVersion;
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestExtensionSdk);
try {
sdkVersion = sa.getInt(R.styleable.AndroidManifestExtensionSdk_sdkVersion, -1);
minVersion = sa.getInt(R.styleable.AndroidManifestExtensionSdk_minExtensionVersion, -1);
} finally {
sa.recycle();
}
if (sdkVersion < 0) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"<extension-sdk> must specify an sdkVersion >= 0");
}
if (minVersion < 0) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"<extension-sdk> must specify minExtensionVersion >= 0");
}
try {
int version = SdkExtensions.getExtensionVersion(sdkVersion);
if (version < minVersion) {
return input.error(
PackageManager.INSTALL_FAILED_OLDER_SDK,
"Package requires " + sdkVersion + " extension version " + minVersion
+ " which exceeds device version " + version);
}
} catch (RuntimeException e) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Specified sdkVersion " + sdkVersion + " is not valid");
}
minExtensionVersions.put(sdkVersion, minVersion);
return input.success(minExtensionVersions);
}
/**
* {@link ParseResult} version of
* {@link PackageParser#computeMinSdkVersion(int, String, int, String[], String[])}
*/
public static ParseResult<Integer> computeMinSdkVersion(@IntRange(from = 1) int minVers,
@Nullable String minCode, @IntRange(from = 1) int platformSdkVersion,
@NonNull String[] platformSdkCodenames, @NonNull ParseInput input) {
// If it's a release SDK, make sure we meet the minimum SDK requirement.
if (minCode == null) {
if (minVers <= platformSdkVersion) {
return input.success(minVers);
}
// We don't meet the minimum SDK requirement.
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
"Requires newer sdk version #" + minVers
+ " (current version is #" + platformSdkVersion + ")");
}
// If it's a pre-release SDK and the codename matches this platform, we
// definitely meet the minimum SDK requirement.
if (matchTargetCode(platformSdkCodenames, minCode)) {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
"Requires development platform " + minCode
+ " (current platform is any of "
+ Arrays.toString(platformSdkCodenames) + ")");
} else {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
"Requires development platform " + minCode
+ " but this is a release platform.");
}
}
/**
* {@link ParseResult} version of
* {@link PackageParser#computeTargetSdkVersion(int, String, String[], String[])}
*/
public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
@Nullable String targetCode, @NonNull String[] platformSdkCodenames,
@NonNull ParseInput input) {
// If it's a release SDK, return the version number unmodified.
if (targetCode == null) {
return input.success(targetVers);
}
// If it's a pre-release SDK and the codename matches this platform, it
// definitely targets this SDK.
if (matchTargetCode(platformSdkCodenames, targetCode)) {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
"Requires development platform " + targetCode
+ " (current platform is any of "
+ Arrays.toString(platformSdkCodenames) + ")");
} else {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
"Requires development platform " + targetCode
+ " but this is a release platform.");
}
}
/**
* Matches a given {@code targetCode} against a set of release codeNames. Target codes can
* either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form
* {@code [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}).
*/
private static boolean matchTargetCode(@NonNull String[] codeNames,
@NonNull String targetCode) {
final String targetCodeName;
final int targetCodeIdx = targetCode.indexOf('.');
if (targetCodeIdx == -1) {
targetCodeName = targetCode;
} else {
targetCodeName = targetCode.substring(0, targetCodeIdx);
}
return ArrayUtils.contains(codeNames, targetCodeName);
}
private static ParseResult<ParsingPackage> parseRestrictUpdateHash(int flags, ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
if ((flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestRestrictUpdate);
try {
final String hash = sa.getNonConfigurationString(
R.styleable.AndroidManifestRestrictUpdate_hash,
0);
if (hash != null) {
final int hashLength = hash.length();
final byte[] hashBytes = new byte[hashLength / 2];
for (int i = 0; i < hashLength; i += 2) {
hashBytes[i / 2] = (byte) ((Character.digit(hash.charAt(i), 16)
<< 4)
+ Character.digit(hash.charAt(i + 1), 16));
}
pkg.setRestrictUpdateHash(hashBytes);
} else {
pkg.setRestrictUpdateHash(null);
}
} finally {
sa.recycle();
}
}
return input.success(pkg);
}
private static ParseResult<ParsingPackage> parseQueries(ParseInput input, ParsingPackage pkg,
Resources res, XmlResourceParser parser) throws IOException, XmlPullParserException {
final int depth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (parser.getName().equals("intent")) {
ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(null,
pkg, res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/, input);
if (result.isError()) {
return input.error(result);
}
ParsedIntentInfo intentInfo = result.getResult();
Uri data = null;
String dataType = null;
String host = IntentFilter.WILDCARD;
final int numActions = intentInfo.countActions();
final int numSchemes = intentInfo.countDataSchemes();
final int numTypes = intentInfo.countDataTypes();
final int numHosts = intentInfo.getHosts().length;
if ((numSchemes == 0 && numTypes == 0 && numActions == 0)) {
return input.error("intent tags must contain either an action or data.");
}
if (numActions > 1) {
return input.error("intent tag may have at most one action.");
}
if (numTypes > 1) {
return input.error("intent tag may have at most one data type.");
}
if (numSchemes > 1) {
return input.error("intent tag may have at most one data scheme.");
}
if (numHosts > 1) {
return input.error("intent tag may have at most one data host.");
}
Intent intent = new Intent();
for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
intent.addCategory(intentInfo.getCategory(i));
}
if (numHosts == 1) {
host = intentInfo.getHosts()[0];
}
if (numSchemes == 1) {
data = new Uri.Builder()
.scheme(intentInfo.getDataScheme(0))
.authority(host)
.path(IntentFilter.WILDCARD_PATH)
.build();
}
if (numTypes == 1) {
dataType = intentInfo.getDataType(0);
// The dataType may have had the '/' removed for the dynamic mimeType feature.
// If we detect that case, we add the * back.
if (!dataType.contains("/")) {
dataType = dataType + "/*";
}
if (data == null) {
data = new Uri.Builder()
.scheme("content")
.authority(IntentFilter.WILDCARD)
.path(IntentFilter.WILDCARD_PATH)
.build();
}
}
intent.setDataAndType(data, dataType);
if (numActions == 1) {
intent.setAction(intentInfo.getAction(0));
}
pkg.addQueriesIntent(intent);
} else if (parser.getName().equals("package")) {
final TypedArray sa = res.obtainAttributes(parser,
R.styleable.AndroidManifestQueriesPackage);
final String packageName = sa.getNonConfigurationString(
R.styleable.AndroidManifestQueriesPackage_name, 0);
if (TextUtils.isEmpty(packageName)) {
return input.error("Package name is missing from package tag.");
}
pkg.addQueriesPackage(packageName.intern());
} else if (parser.getName().equals("provider")) {
final TypedArray sa = res.obtainAttributes(parser,
R.styleable.AndroidManifestQueriesProvider);
try {
final String authorities = sa.getNonConfigurationString(
R.styleable.AndroidManifestQueriesProvider_authorities, 0);
if (TextUtils.isEmpty(authorities)) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Authority missing from provider tag."
);
}
StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";");
while (authoritiesTokenizer.hasMoreElements()) {
pkg.addQueriesProvider(authoritiesTokenizer.nextToken());
}
} finally {
sa.recycle();
}
}
}
return input.success(pkg);
}
/**
* Parse the {@code application} XML tree at the current parse location in a
* <em>base APK</em> manifest.
* <p>
* When adding new features, carefully consider if they should also be
* supported by split APKs.
*
* This method should avoid using a getter for fields set by this method. Prefer assigning
* a local variable and using it. Otherwise there's an ordering problem which can be broken
* if any code moves around.
*/
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException {
final String pkgName = pkg.getPackageName();
int targetSdk = pkg.getTargetSdkVersion();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
try {
// TODO(b/135203078): Remove this and force unit tests to mock an empty manifest
// This case can only happen in unit tests where we sometimes need to create fakes
// of various package parser data structures.
if (sa == null) {
return input.error("<application> does not contain any attributes");
}
String name = sa.getNonConfigurationString(R.styleable.AndroidManifestApplication_name,
0);
if (name != null) {
String packageName = pkg.getPackageName();
String outInfoName = ParsingUtils.buildClassName(packageName, name);
if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(outInfoName)) {
return input.error("<application> invalid android:name");
} else if (outInfoName == null) {
return input.error("Empty class name in package " + packageName);
}
pkg.setClassName(outInfoName);
}
TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
if (labelValue != null) {
pkg.setLabelRes(labelValue.resourceId);
if (labelValue.resourceId == 0) {
pkg.setNonLocalizedLabel(labelValue.coerceToString());
}
}
parseBaseAppBasicFlags(pkg, sa);
String manageSpaceActivity = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
R.styleable.AndroidManifestApplication_manageSpaceActivity, sa);
if (manageSpaceActivity != null) {
String manageSpaceActivityName = ParsingUtils.buildClassName(pkgName,
manageSpaceActivity);
if (manageSpaceActivityName == null) {
return input.error("Empty class name in package " + pkgName);
}
pkg.setManageSpaceActivityName(manageSpaceActivityName);
}
if (pkg.isAllowBackup()) {
// backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
// and restoreAnyVersion are only relevant if backup is possible for the
// given application.
String backupAgent = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
R.styleable.AndroidManifestApplication_backupAgent, sa);
if (backupAgent != null) {
String backupAgentName = ParsingUtils.buildClassName(pkgName, backupAgent);
if (backupAgentName == null) {
return input.error("Empty class name in package " + pkgName);
}
if (PackageParser.DEBUG_BACKUP) {
Slog.v(TAG, "android:backupAgent = " + backupAgentName
+ " from " + pkgName + "+" + backupAgent);
}
pkg.setBackupAgentName(backupAgentName)
.setKillAfterRestore(bool(true,
R.styleable.AndroidManifestApplication_killAfterRestore, sa))
.setRestoreAnyVersion(bool(false,
R.styleable.AndroidManifestApplication_restoreAnyVersion, sa))
.setFullBackupOnly(bool(false,
R.styleable.AndroidManifestApplication_fullBackupOnly, sa))
.setBackupInForeground(bool(false,
R.styleable.AndroidManifestApplication_backupInForeground, sa));
}
TypedValue v = sa.peekValue(
R.styleable.AndroidManifestApplication_fullBackupContent);
int fullBackupContent = 0;
if (v != null) {
fullBackupContent = v.resourceId;
if (v.resourceId == 0) {
if (PackageParser.DEBUG_BACKUP) {
Slog.v(TAG, "fullBackupContent specified as boolean=" +
(v.data == 0 ? "false" : "true"));
}
// "false" => -1, "true" => 0
fullBackupContent = v.data == 0 ? -1 : 0;
}
pkg.setFullBackupContent(fullBackupContent);
}
if (PackageParser.DEBUG_BACKUP) {
Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
}
}
if (sa.getBoolean(R.styleable.AndroidManifestApplication_persistent, false)) {
// Check if persistence is based on a feature being present
final String requiredFeature = sa.getNonResourceString(R.styleable
.AndroidManifestApplication_persistentWhenFeatureAvailable);
pkg.setPersistent(requiredFeature == null || mCallback.hasFeature(requiredFeature));
}
// TODO(b/135203078): Should parsing code be responsible for this? Maybe move to a
// util or just have PackageImpl return true if either flag is set
// Debuggable implies profileable
pkg.setProfileableByShell(pkg.isProfileableByShell() || pkg.isDebuggable());
if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
pkg.setResizeableActivity(sa.getBoolean(
R.styleable.AndroidManifestApplication_resizeableActivity, true));
} else {
pkg.setResizeableActivityViaSdkVersion(
targetSdk >= Build.VERSION_CODES.N);
}
String taskAffinity;
if (targetSdk >= Build.VERSION_CODES.FROYO) {
taskAffinity = sa.getNonConfigurationString(
R.styleable.AndroidManifestApplication_taskAffinity,
Configuration.NATIVE_CONFIG_VERSION);
} else {
// Some older apps have been seen to use a resource reference
// here that on older builds was ignored (with a warning). We
// need to continue to do this for them so they don't break.
taskAffinity = sa.getNonResourceString(
R.styleable.AndroidManifestApplication_taskAffinity);
}
ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
pkgName, pkgName, taskAffinity, input);
if (taskAffinityResult.isError()) {
return input.error(taskAffinityResult);
}
pkg.setTaskAffinity(taskAffinityResult.getResult());
String factory = sa.getNonResourceString(
R.styleable.AndroidManifestApplication_appComponentFactory);
if (factory != null) {
String appComponentFactory = ParsingUtils.buildClassName(pkgName, factory);
if (appComponentFactory == null) {
return input.error("Empty class name in package " + pkgName);
}
pkg.setAppComponentFactory(appComponentFactory);
}
CharSequence pname;
if (targetSdk >= Build.VERSION_CODES.FROYO) {
pname = sa.getNonConfigurationString(
R.styleable.AndroidManifestApplication_process,
Configuration.NATIVE_CONFIG_VERSION);
} else {
// Some older apps have been seen to use a resource reference
// here that on older builds was ignored (with a warning). We
// need to continue to do this for them so they don't break.
pname = sa.getNonResourceString(
R.styleable.AndroidManifestApplication_process);
}
ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
pkgName, null, pname, flags, mSeparateProcesses, input);
if (processNameResult.isError()) {
return input.error(processNameResult);
}
String processName = processNameResult.getResult();
pkg.setProcessName(processName);
if (pkg.isCantSaveState()) {
// A heavy-weight application can not be in a custom process.
// We can do direct compare because we intern all strings.
if (processName != null && !processName.equals(pkgName)) {
return input.error(
"cantSaveState applications can not use custom processes");
}
}
String classLoaderName = pkg.getClassLoaderName();
if (classLoaderName != null
&& !ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
return input.error("Invalid class loader name: " + classLoaderName);
}
pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
} finally {
sa.recycle();
}
boolean hasActivityOrder = false;
boolean hasReceiverOrder = false;
boolean hasServiceOrder = 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.START_TAG) {
continue;
}
final ParseResult result;
String tagName = parser.getName();
boolean isActivity = false;
switch (tagName) {
case "activity":
isActivity = true;
// fall-through
case "receiver":
ParseResult<ParsedActivity> activityResult =
ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
res, parser, flags, PackageParser.sUseRoundIcon, input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
if (isActivity) {
hasActivityOrder |= (activity.getOrder() != 0);
pkg.addActivity(activity);
} else {
hasReceiverOrder |= (activity.getOrder() != 0);
pkg.addReceiver(activity);
}
}
result = activityResult;
break;
case "service":
ParseResult<ParsedService> serviceResult =
ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
flags, PackageParser.sUseRoundIcon, input);
if (serviceResult.isSuccess()) {
ParsedService service = serviceResult.getResult();
hasServiceOrder |= (service.getOrder() != 0);
pkg.addService(service);
}
result = serviceResult;
break;
case "provider":
ParseResult<ParsedProvider> providerResult =
ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
flags, PackageParser.sUseRoundIcon, input);
if (providerResult.isSuccess()) {
pkg.addProvider(providerResult.getResult());
}
result = providerResult;
break;
case "activity-alias":
activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
parser, PackageParser.sUseRoundIcon, input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
hasActivityOrder |= (activity.getOrder() != 0);
pkg.addActivity(activity);
}
result = activityResult;
break;
default:
result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
break;
}
if (result.isError()) {
return input.error(result);
}
}
if (TextUtils.isEmpty(pkg.getStaticSharedLibName())) {
// Add a hidden app detail activity to normal apps which forwards user to App Details
// page.
ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
if (a.isError()) {
// Error should be impossible here, as the only failure case as of SDK R is a
// string validation error on a constant ":app_details" string passed in by the
// parsing code itself. For this reason, this is just a hard failure instead of
// deferred.
return input.error(a);
}
pkg.addActivity(a.getResult());
}
if (hasActivityOrder) {
pkg.sortActivities();
}
if (hasReceiverOrder) {
pkg.sortReceivers();
}
if (hasServiceOrder) {
pkg.sortServices();
}
// Must be run after the entire {@link ApplicationInfo} has been fully processed and after
// every activity info has had a chance to set it from its attributes.
setMaxAspectRatio(pkg);
setMinAspectRatio(pkg);
pkg.setHasDomainUrls(hasDomainURLs(pkg));
return input.success(pkg);
}
/**
* Collection of single-line, no (or little) logic assignments. Separated for readability.
*
* Flags are separated by type and by default value. They are sorted alphabetically within each
* section.
*/
private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
int targetSdk = pkg.getTargetSdkVersion();
//@formatter:off
// CHECKSTYLE:off
pkg
// Default true
.setAllowBackup(bool(true, R.styleable.AndroidManifestApplication_allowBackup, sa))
.setAllowClearUserData(bool(true, R.styleable.AndroidManifestApplication_allowClearUserData, sa))
.setAllowClearUserDataOnFailedRestore(bool(true, R.styleable.AndroidManifestApplication_allowClearUserDataOnFailedRestore, sa))
.setAllowNativeHeapPointerTagging(bool(true, R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, sa))
.setEnabled(bool(true, R.styleable.AndroidManifestApplication_enabled, sa))
.setExtractNativeLibs(bool(true, R.styleable.AndroidManifestApplication_extractNativeLibs, sa))
.setHasCode(bool(true, R.styleable.AndroidManifestApplication_hasCode, sa))
// Default false
.setAllowTaskReparenting(bool(false, R.styleable.AndroidManifestApplication_allowTaskReparenting, sa))
.setCantSaveState(bool(false, R.styleable.AndroidManifestApplication_cantSaveState, sa))
.setCrossProfile(bool(false, R.styleable.AndroidManifestApplication_crossProfile, sa))
.setDebuggable(bool(false, R.styleable.AndroidManifestApplication_debuggable, sa))
.setDefaultToDeviceProtectedStorage(bool(false, R.styleable.AndroidManifestApplication_defaultToDeviceProtectedStorage, sa))
.setDirectBootAware(bool(false, R.styleable.AndroidManifestApplication_directBootAware, sa))
.setForceQueryable(bool(false, R.styleable.AndroidManifestApplication_forceQueryable, sa))
.setGame(bool(false, R.styleable.AndroidManifestApplication_isGame, sa))
.setHasFragileUserData(bool(false, R.styleable.AndroidManifestApplication_hasFragileUserData, sa))
.setLargeHeap(bool(false, R.styleable.AndroidManifestApplication_largeHeap, sa))
.setMultiArch(bool(false, R.styleable.AndroidManifestApplication_multiArch, sa))
.setPreserveLegacyExternalStorage(bool(false, R.styleable.AndroidManifestApplication_preserveLegacyExternalStorage, sa))
.setRequiredForAllUsers(bool(false, R.styleable.AndroidManifestApplication_requiredForAllUsers, sa))
.setSupportsRtl(bool(false, R.styleable.AndroidManifestApplication_supportsRtl, sa))
.setTestOnly(bool(false, R.styleable.AndroidManifestApplication_testOnly, sa))
.setUseEmbeddedDex(bool(false, R.styleable.AndroidManifestApplication_useEmbeddedDex, sa))
.setUsesNonSdkApi(bool(false, R.styleable.AndroidManifestApplication_usesNonSdkApi, sa))
.setVmSafeMode(bool(false, R.styleable.AndroidManifestApplication_vmSafeMode, sa))
.setAutoRevokePermissions(anInt(R.styleable.AndroidManifestApplication_autoRevokePermissions, sa))
// targetSdkVersion gated
.setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
.setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
.setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
.setUsesCleartextTraffic(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
// Ints Default 0
.setUiOptions(anInt(R.styleable.AndroidManifestApplication_uiOptions, sa))
// Ints
.setCategory(anInt(ApplicationInfo.CATEGORY_UNDEFINED, R.styleable.AndroidManifestApplication_appCategory, sa))
// Floats Default 0f
.setMaxAspectRatio(aFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, sa))
.setMinAspectRatio(aFloat(R.styleable.AndroidManifestApplication_minAspectRatio, sa))
// Resource ID
.setBanner(resId(R.styleable.AndroidManifestApplication_banner, sa))
.setDescriptionRes(resId(R.styleable.AndroidManifestApplication_description, sa))
.setIconRes(resId(R.styleable.AndroidManifestApplication_icon, sa))
.setLogo(resId(R.styleable.AndroidManifestApplication_logo, sa))
.setNetworkSecurityConfigRes(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa))
.setRoundIconRes(resId(R.styleable.AndroidManifestApplication_roundIcon, sa))
.setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa))
// Strings
.setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa))
.setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa))
.setRestrictedAccountType(string(R.styleable.AndroidManifestApplication_restrictedAccountType, sa))
.setZygotePreloadName(string(R.styleable.AndroidManifestApplication_zygotePreloadName, sa))
// Non-Config String
.setPermission(nonConfigString(0, R.styleable.AndroidManifestApplication_permission, sa));
// CHECKSTYLE:on
//@formatter:on
}
/**
* For parsing non-MainComponents. Main ones have an order and some special handling which is
* done directly in {@link #parseBaseApplication(ParseInput, ParsingPackage, Resources,
* XmlResourceParser, int)}.
*/
private ParseResult parseBaseAppChildTag(ParseInput input, String tag, ParsingPackage pkg,
Resources res, XmlResourceParser parser, int flags)
throws IOException, XmlPullParserException {
switch (tag) {
case "meta-data":
// TODO(b/135203078): I have no idea what this comment means
// note: application meta-data is stored off to the side, so it can
// remain null in the primary copy (we like to avoid extra copies because
// it can be large)
ParseResult<Bundle> metaDataResult = parseMetaData(pkg, res, parser,
pkg.getMetaData(), input);
if (metaDataResult.isSuccess()) {
pkg.setMetaData(metaDataResult.getResult());
}
return metaDataResult;
case "static-library":
return parseStaticLibrary(pkg, res, parser, input);
case "library":
return parseLibrary(pkg, res, parser, input);
case "uses-static-library":
return parseUsesStaticLibrary(input, pkg, res, parser);
case "uses-library":
return parseUsesLibrary(input, pkg, res, parser);
case "processes":
return parseProcesses(input, pkg, res, parser, mSeparateProcesses, flags);
case "uses-package":
// Dependencies for app installers; we don't currently try to
// enforce this.
return input.success(null);
case "profileable":
return parseProfileable(input, pkg, res, parser);
default:
return ParsingUtils.unknownTag("<application>", pkg, parser, input);
}
}
@NonNull
private static ParseResult<ParsingPackage> parseStaticLibrary(
ParsingPackage pkg, Resources res,
XmlResourceParser parser, ParseInput input) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestStaticLibrary);
try {
// Note: don't allow this value to be a reference to a resource
// that may change.
String lname = sa.getNonResourceString(
R.styleable.AndroidManifestStaticLibrary_name);
final int version = sa.getInt(
R.styleable.AndroidManifestStaticLibrary_version, -1);
final int versionMajor = sa.getInt(
R.styleable.AndroidManifestStaticLibrary_versionMajor,
0);
// Since the app canot run without a static lib - fail if malformed
if (lname == null || version < 0) {
return input.error("Bad static-library declaration name: " + lname
+ " version: " + version);
} else if (pkg.getSharedUserId() != null) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
"sharedUserId not allowed in static shared library"
);
} else if (pkg.getStaticSharedLibName() != null) {
return input.error("Multiple static-shared libs for package "
+ pkg.getPackageName());
}
return input.success(pkg.setStaticSharedLibName(lname.intern())
.setStaticSharedLibVersion(
PackageInfo.composeLongVersionCode(versionMajor, version))
.setStaticSharedLibrary(true));
} finally {
sa.recycle();
}
}
@NonNull
private static ParseResult<ParsingPackage> parseLibrary(
ParsingPackage pkg, Resources res,
XmlResourceParser parser, ParseInput input) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestLibrary);
try {
// Note: don't allow this value to be a reference to a resource
// that may change.
String lname = sa.getNonResourceString(R.styleable.AndroidManifestLibrary_name);
if (lname != null) {
lname = lname.intern();
if (!ArrayUtils.contains(pkg.getLibraryNames(), lname)) {
pkg.addLibraryName(lname);
}
}
return input.success(pkg);
} finally {
sa.recycle();
}
}
@NonNull
private static ParseResult<ParsingPackage> parseUsesStaticLibrary(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesStaticLibrary);
try {
// Note: don't allow this value to be a reference to a resource that may change.
String lname = sa.getNonResourceString(
R.styleable.AndroidManifestUsesLibrary_name);
final int version = sa.getInt(
R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
String certSha256Digest = sa.getNonResourceString(R.styleable
.AndroidManifestUsesStaticLibrary_certDigest);
// Since an APK providing a static shared lib can only provide the lib - fail if
// malformed
if (lname == null || version < 0 || certSha256Digest == null) {
return input.error("Bad uses-static-library declaration name: " + lname
+ " version: " + version + " certDigest" + certSha256Digest);
}
// Can depend only on one version of the same library
List<String> usesStaticLibraries = pkg.getUsesStaticLibraries();
if (usesStaticLibraries.contains(lname)) {
return input.error(
"Depending on multiple versions of static library " + lname);
}
lname = lname.intern();
// We allow ":" delimiters in the SHA declaration as this is the format
// emitted by the certtool making it easy for developers to copy/paste.
certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
// Fot apps targeting O-MR1 we require explicit enumeration of all certs.
String[] additionalCertSha256Digests = EmptyArray.STRING;
if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O_MR1) {
ParseResult<String[]> certResult = parseAdditionalCertificates(input, res, parser);
if (certResult.isError()) {
return input.error(certResult);
}
additionalCertSha256Digests = certResult.getResult();
}
final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
certSha256Digests[0] = certSha256Digest;
System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
1, additionalCertSha256Digests.length);
return input.success(pkg.addUsesStaticLibrary(lname)
.addUsesStaticLibraryVersion(version)
.addUsesStaticLibraryCertDigests(certSha256Digests));
} finally {
sa.recycle();
}
}
@NonNull
private static ParseResult<ParsingPackage> parseUsesLibrary(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesLibrary);
try {
// Note: don't allow this value to be a reference to a resource
// that may change.
String lname = sa.getNonResourceString(R.styleable.AndroidManifestUsesLibrary_name);
boolean req = sa.getBoolean(R.styleable.AndroidManifestUsesLibrary_required, true);
if (lname != null) {
lname = lname.intern();
if (req) {
// Upgrade to treat as stronger constraint
pkg.addUsesLibrary(lname)
.removeUsesOptionalLibrary(lname);
} else {
// Ignore if someone already defined as required
if (!ArrayUtils.contains(pkg.getUsesLibraries(), lname)) {
pkg.addUsesOptionalLibrary(lname);
}
}
}
return input.success(pkg);
} finally {
sa.recycle();
}
}
@NonNull
private static ParseResult<ParsingPackage> parseProcesses(ParseInput input, ParsingPackage pkg,
Resources res, XmlResourceParser parser, String[] separateProcesses, int flags)
throws IOException, XmlPullParserException {
ParseResult<ArrayMap<String, ParsedProcess>> result =
ParsedProcessUtils.parseProcesses(separateProcesses, pkg, res, parser, flags,
input);
if (result.isError()) {
return input.error(result);
}
return input.success(pkg.setProcesses(result.getResult()));
}
@NonNull
private static ParseResult<ParsingPackage> parseProfileable(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProfileable);
try {
return input.success(pkg.setProfileableByShell(pkg.isProfileableByShell()
|| bool(false, R.styleable.AndroidManifestProfileable_shell, sa)));
} finally {
sa.recycle();
}
}
private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
Resources resources, XmlResourceParser parser)
throws XmlPullParserException, IOException {
String[] certSha256Digests = EmptyArray.STRING;
final int depth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String nodeName = parser.getName();
if (nodeName.equals("additional-certificate")) {
TypedArray sa = resources.obtainAttributes(parser,
R.styleable.AndroidManifestAdditionalCertificate);
try {
String certSha256Digest = sa.getNonResourceString(
R.styleable.AndroidManifestAdditionalCertificate_certDigest);
if (TextUtils.isEmpty(certSha256Digest)) {
return input.error("Bad additional-certificate declaration with empty"
+ " certDigest:" + certSha256Digest);
}
// We allow ":" delimiters in the SHA declaration as this is the format
// emitted by the certtool making it easy for developers to copy/paste.
certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
certSha256Digests = ArrayUtils.appendElement(String.class,
certSha256Digests, certSha256Digest);
} finally {
sa.recycle();
}
}
}
return input.success(certSha256Digests);
}
/**
* Generate activity object that forwards user to App Details page automatically.
* This activity should be invisible to user and user should not know or see it.
*/
@NonNull
private static ParseResult<ParsedActivity> generateAppDetailsHiddenActivity(ParseInput input,
ParsingPackage pkg) {
String packageName = pkg.getPackageName();
ParseResult<String> result = ComponentParseUtils.buildTaskAffinityName(
packageName, packageName, ":app_details", input);
if (result.isError()) {
return input.error(result);
}
String taskAffinity = result.getResult();
// Build custom App Details activity info instead of parsing it from xml
return input.success(ParsedActivity.makeAppDetailsActivity(packageName,
pkg.getProcessName(), pkg.getUiOptions(), taskAffinity,
pkg.isBaseHardwareAccelerated()));
}
/**
* Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
*/
private static boolean hasDomainURLs(ParsingPackage pkg) {
final List<ParsedActivity> activities = pkg.getActivities();
final int activitiesSize = activities.size();
for (int index = 0; index < activitiesSize; index++) {
ParsedActivity activity = activities.get(index);
List<ParsedIntentInfo> filters = activity.getIntents();
final int filtersSize = filters.size();
for (int filtersIndex = 0; filtersIndex < filtersSize; filtersIndex++) {
ParsedIntentInfo aii = filters.get(filtersIndex);
if (!aii.hasAction(Intent.ACTION_VIEW)) continue;
if (!aii.hasAction(Intent.ACTION_DEFAULT)) continue;
if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) {
return true;
}
}
}
return false;
}
/**
* Sets the max aspect ratio of every child activity that doesn't already have an aspect
* ratio set.
*/
private static void setMaxAspectRatio(ParsingPackage pkg) {
// Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
// NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
float maxAspectRatio = pkg.getTargetSdkVersion() < O
? PackageParser.DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
float packageMaxAspectRatio = pkg.getMaxAspectRatio();
if (packageMaxAspectRatio != 0) {
// Use the application max aspect ration as default if set.
maxAspectRatio = packageMaxAspectRatio;
} else {
Bundle appMetaData = pkg.getMetaData();
if (appMetaData != null && appMetaData.containsKey(
PackageParser.METADATA_MAX_ASPECT_RATIO)) {
maxAspectRatio = appMetaData.getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO,
maxAspectRatio);
}
}
List<ParsedActivity> activities = pkg.getActivities();
int activitiesSize = activities.size();
for (int index = 0; index < activitiesSize; index++) {
ParsedActivity activity = activities.get(index);
// If the max aspect ratio for the activity has already been set, skip.
if (activity.getMaxAspectRatio() != null) {
continue;
}
// By default we prefer to use a values defined on the activity directly than values
// defined on the application. We do not check the styled attributes on the activity
// as it would have already been set when we processed the activity. We wait to
// process the meta data here since this method is called at the end of processing
// the application and all meta data is guaranteed.
final float activityAspectRatio = activity.getMetaData() != null
? activity.getMetaData().getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO,
maxAspectRatio)
: maxAspectRatio;
activity.setMaxAspectRatio(activity.getResizeMode(), activityAspectRatio);
}
}
/**
* Sets the min aspect ratio of every child activity that doesn't already have an aspect
* ratio set.
*/
private void setMinAspectRatio(ParsingPackage pkg) {
final float minAspectRatio;
float packageMinAspectRatio = pkg.getMinAspectRatio();
if (packageMinAspectRatio != 0) {
// Use the application max aspect ration as default if set.
minAspectRatio = packageMinAspectRatio;
} else {
// Default to (1.33) 4:3 aspect ratio for pre-Q apps and unset for Q and greater.
// NOTE: 4:3 was the min aspect ratio Android devices can support pre-Q per the CDD,
// except for watches which always supported 1:1.
minAspectRatio = pkg.getTargetSdkVersion() >= Build.VERSION_CODES.Q
? 0
: (mCallback != null && mCallback.hasFeature(FEATURE_WATCH))
? PackageParser.DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH
: PackageParser.DEFAULT_PRE_Q_MIN_ASPECT_RATIO;
}
List<ParsedActivity> activities = pkg.getActivities();
int activitiesSize = activities.size();
for (int index = 0; index < activitiesSize; index++) {
ParsedActivity activity = activities.get(index);
if (activity.getMinAspectRatio() == null) {
activity.setMinAspectRatio(activity.getResizeMode(), minAspectRatio);
}
}
}
private static ParseResult<ParsingPackage> parseOverlay(ParseInput input, ParsingPackage pkg,
Resources res, XmlResourceParser parser) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestResourceOverlay);
try {
String target = sa.getString(R.styleable.AndroidManifestResourceOverlay_targetPackage);
int priority = anInt(0, R.styleable.AndroidManifestResourceOverlay_priority, sa);
if (target == null) {
return input.error("<overlay> does not specify a target package");
} else if (priority < 0 || priority > 9999) {
return input.error("<overlay> priority must be between 0 and 9999");
}
// check to see if overlay should be excluded based on system property condition
String propName = sa.getString(
R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName);
String propValue = sa.getString(
R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue);
if (!PackageParser.checkRequiredSystemProperties(propName, propValue)) {
String message = "Skipping target and overlay pair " + target + " and "
+ pkg.getBaseCodePath()
+ ": overlay ignored due to required system property: "
+ propName + " with value: " + propValue;
Slog.i(TAG, message);
return input.skip(message);
}
return input.success(pkg.setOverlay(true)
.setOverlayTarget(target)
.setOverlayPriority(priority)
.setOverlayTargetName(
sa.getString(R.styleable.AndroidManifestResourceOverlay_targetName))
.setOverlayCategory(
sa.getString(R.styleable.AndroidManifestResourceOverlay_category))
.setOverlayIsStatic(
bool(false, R.styleable.AndroidManifestResourceOverlay_isStatic, sa)));
} finally {
sa.recycle();
}
}
private static ParseResult<ParsingPackage> parseProtectedBroadcast(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProtectedBroadcast);
try {
// Note: don't allow this value to be a reference to a resource
// that may change.
String name = nonResString(R.styleable.AndroidManifestProtectedBroadcast_name, sa);
if (name != null) {
pkg.addProtectedBroadcast(name);
}
return input.success(pkg);
} finally {
sa.recycle();
}
}
private static ParseResult<ParsingPackage> parseSupportScreens(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestSupportsScreens);
try {
int requiresSmallestWidthDp = anInt(0,
R.styleable.AndroidManifestSupportsScreens_requiresSmallestWidthDp, sa);
int compatibleWidthLimitDp = anInt(0,
R.styleable.AndroidManifestSupportsScreens_compatibleWidthLimitDp, sa);
int largestWidthLimitDp = anInt(0,
R.styleable.AndroidManifestSupportsScreens_largestWidthLimitDp, sa);
// This is a trick to get a boolean and still able to detect
// if a value was actually set.
return input.success(pkg
.setSupportsSmallScreens(
anInt(1, R.styleable.AndroidManifestSupportsScreens_smallScreens, sa))
.setSupportsNormalScreens(
anInt(1, R.styleable.AndroidManifestSupportsScreens_normalScreens, sa))
.setSupportsLargeScreens(
anInt(1, R.styleable.AndroidManifestSupportsScreens_largeScreens, sa))
.setSupportsExtraLargeScreens(
anInt(1, R.styleable.AndroidManifestSupportsScreens_xlargeScreens, sa))
.setResizeable(
anInt(1, R.styleable.AndroidManifestSupportsScreens_resizeable, sa))
.setAnyDensity(
anInt(1, R.styleable.AndroidManifestSupportsScreens_anyDensity, sa))
.setRequiresSmallestWidthDp(requiresSmallestWidthDp)
.setCompatibleWidthLimitDp(compatibleWidthLimitDp)
.setLargestWidthLimitDp(largestWidthLimitDp));
} finally {
sa.recycle();
}
}
private static ParseResult<ParsingPackage> parseInstrumentation(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
ParseResult<ParsedInstrumentation> result = ParsedInstrumentationUtils.parseInstrumentation(
pkg, res, parser, PackageParser.sUseRoundIcon, input);
if (result.isError()) {
return input.error(result);
}
return input.success(pkg.addInstrumentation(result.getResult()));
}
private static ParseResult<ParsingPackage> parseOriginalPackage(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestOriginalPackage);
try {
String orig = sa.getNonConfigurationString(
R.styleable.AndroidManifestOriginalPackage_name,
0);
if (!pkg.getPackageName().equals(orig)) {
if (pkg.getOriginalPackages().isEmpty()) {
pkg.setRealPackage(pkg.getPackageName());
}
pkg.addOriginalPackage(orig);
}
return input.success(pkg);
} finally {
sa.recycle();
}
}
private static ParseResult<ParsingPackage> parseAdoptPermissions(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestOriginalPackage);
try {
String name = nonConfigString(0, R.styleable.AndroidManifestOriginalPackage_name, sa);
if (name != null) {
pkg.addAdoptPermission(name);
}
return input.success(pkg);
} finally {
sa.recycle();
}
}
private static void convertNewPermissions(ParsingPackage pkg) {
final int NP = PackageParser.NEW_PERMISSIONS.length;
StringBuilder newPermsMsg = null;
for (int ip = 0; ip < NP; ip++) {
final PackageParser.NewPermissionInfo npi
= PackageParser.NEW_PERMISSIONS[ip];
if (pkg.getTargetSdkVersion() >= npi.sdkVersion) {
break;
}
if (!pkg.getRequestedPermissions().contains(npi.name)) {
if (newPermsMsg == null) {
newPermsMsg = new StringBuilder(128);
newPermsMsg.append(pkg.getPackageName());
newPermsMsg.append(": compat added ");
} else {
newPermsMsg.append(' ');
}
newPermsMsg.append(npi.name);
pkg.addRequestedPermission(npi.name)
.addImplicitPermission(npi.name);
}
}
if (newPermsMsg != null) {
Slog.i(TAG, newPermsMsg.toString());
}
}
private static void convertSplitPermissions(ParsingPackage pkg) {
List<SplitPermissionInfoParcelable> splitPermissions;
try {
splitPermissions = ActivityThread.getPermissionManager().getSplitPermissions();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
final int listSize = splitPermissions.size();
for (int is = 0; is < listSize; is++) {
final SplitPermissionInfoParcelable spi = splitPermissions.get(is);
List<String> requestedPermissions = pkg.getRequestedPermissions();
if (pkg.getTargetSdkVersion() >= spi.getTargetSdk()
|| !requestedPermissions.contains(spi.getSplitPermission())) {
continue;
}
final List<String> newPerms = spi.getNewPermissions();
for (int in = 0; in < newPerms.size(); in++) {
final String perm = newPerms.get(in);
if (!requestedPermissions.contains(perm)) {
pkg.addRequestedPermission(perm)
.addImplicitPermission(perm);
}
}
}
}
/**
* This is a pre-density application which will get scaled - instead of being pixel perfect.
* This type of application is not resizable.
*
* @param pkg The package which needs to be marked as unresizable.
*/
private static void adjustPackageToBeUnresizeableAndUnpipable(ParsingPackage pkg) {
List<ParsedActivity> activities = pkg.getActivities();
int activitiesSize = activities.size();
for (int index = 0; index < activitiesSize; index++) {
ParsedActivity activity = activities.get(index);
activity.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setFlags(activity.getFlags() & ~FLAG_SUPPORTS_PICTURE_IN_PICTURE);
}
}
private static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
boolean requireFilename) {
final int N = name.length();
boolean hasSep = false;
boolean front = true;
for (int i = 0; i < N; i++) {
final char c = name.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
front = false;
continue;
}
if (!front) {
if ((c >= '0' && c <= '9') || c == '_') {
continue;
}
}
if (c == '.') {
hasSep = true;
front = true;
continue;
}
return input.error("bad character '" + c + "'");
}
if (requireFilename && !FileUtils.isValidExtFilename(name)) {
return input.error("Invalid filename");
}
return hasSep || !requireSeparator
? input.success(null)
: input.error("must have at least one '.' separator");
}
public static ParseResult<Bundle> parseMetaData(ParsingPackage pkg, Resources res,
XmlResourceParser parser, Bundle data, ParseInput input) {
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestMetaData);
try {
if (data == null) {
data = new Bundle();
}
String name = TextUtils.safeIntern(
nonConfigString(0, R.styleable.AndroidManifestMetaData_name, sa));
if (name == null) {
return input.error("<meta-data> requires an android:name attribute");
}
TypedValue v = sa.peekValue(R.styleable.AndroidManifestMetaData_resource);
if (v != null && v.resourceId != 0) {
//Slog.i(TAG, "Meta data ref " + name + ": " + v);
data.putInt(name, v.resourceId);
} else {
v = sa.peekValue(R.styleable.AndroidManifestMetaData_value);
//Slog.i(TAG, "Meta data " + name + ": " + v);
if (v != null) {
if (v.type == TypedValue.TYPE_STRING) {
CharSequence cs = v.coerceToString();
data.putString(name, cs != null ? cs.toString() : null);
} else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
data.putBoolean(name, v.data != 0);
} else if (v.type >= TypedValue.TYPE_FIRST_INT
&& v.type <= TypedValue.TYPE_LAST_INT) {
data.putInt(name, v.data);
} else if (v.type == TypedValue.TYPE_FLOAT) {
data.putFloat(name, v.getFloat());
} else {
if (!PackageParser.RIGID_PARSER) {
Slog.w(TAG,
"<meta-data> only supports string, integer, float, color, "
+ "boolean, and resource reference types: "
+ parser.getName() + " at "
+ pkg.getBaseCodePath() + " "
+ parser.getPositionDescription());
} else {
return input.error("<meta-data> only supports string, integer, float, "
+ "color, boolean, and resource reference types");
}
}
} else {
return input.error("<meta-data> requires an android:value "
+ "or android:resource attribute");
}
}
return input.success(data);
} finally {
sa.recycle();
}
}
/**
* Collect certificates from all the APKs described in the given package. Also asserts that
* all APK contents are signed correctly and consistently.
*/
public static SigningDetails collectCertificates(ParsingPackageRead pkg, boolean skipVerify)
throws PackageParserException {
SigningDetails signingDetails = SigningDetails.UNKNOWN;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
try {
signingDetails = collectCertificates(
pkg.getBaseCodePath(),
skipVerify,
pkg.isStaticSharedLibrary(),
signingDetails,
pkg.getTargetSdkVersion()
);
String[] splitCodePaths = pkg.getSplitCodePaths();
if (!ArrayUtils.isEmpty(splitCodePaths)) {
for (int i = 0; i < splitCodePaths.length; i++) {
signingDetails = collectCertificates(
splitCodePaths[i],
skipVerify,
pkg.isStaticSharedLibrary(),
signingDetails,
pkg.getTargetSdkVersion()
);
}
}
return signingDetails;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
public static SigningDetails collectCertificates(String baseCodePath, boolean skipVerify,
boolean isStaticSharedLibrary, @NonNull SigningDetails existingSigningDetails,
int targetSdk) throws PackageParserException {
int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
targetSdk);
if (isStaticSharedLibrary) {
// must use v2 signing scheme
minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
}
SigningDetails verified;
if (skipVerify) {
// systemDir APKs are already trusted, save time by not verifying
verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(
baseCodePath, minSignatureScheme);
} else {
verified = ApkSignatureVerifier.verify(baseCodePath, minSignatureScheme);
}
// Verify that entries are signed consistently with the first pkg
// we encountered. Note that for splits, certificates may have
// already been populated during an earlier parse of a base APK.
if (existingSigningDetails == SigningDetails.UNKNOWN) {
return verified;
} else {
if (!Signature.areExactMatch(existingSigningDetails.signatures, verified.signatures)) {
throw new PackageParserException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
baseCodePath + " has mismatched certificates");
}
return existingSigningDetails;
}
}
/*
The following set of methods makes code easier to read by re-ordering the TypedArray methods.
The first parameter is the default, which is the most important to understand for someone
reading through the parsing code.
That's followed by the attribute name, which is usually irrelevant during reading because
it'll look like setSomeValue(true, R.styleable.ReallyLongParentName_SomeValueAttr... and
the "setSomeValue" part is enough to communicate what the line does.
Last comes the TypedArray, which is by far the least important since each try-with-resources
should only have 1.
*/
// Note there is no variant of bool without a defaultValue parameter, since explicit true/false
// is important to specify when adding an attribute.
private static boolean bool(boolean defaultValue, @StyleableRes int attribute, TypedArray sa) {
return sa.getBoolean(attribute, defaultValue);
}
private static float aFloat(float defaultValue, @StyleableRes int attribute, TypedArray sa) {
return sa.getFloat(attribute, defaultValue);
}
private static float aFloat(@StyleableRes int attribute, TypedArray sa) {
return sa.getFloat(attribute, 0f);
}
private static int anInt(int defaultValue, @StyleableRes int attribute, TypedArray sa) {
return sa.getInt(attribute, defaultValue);
}
private static int anInteger(int defaultValue, @StyleableRes int attribute, TypedArray sa) {
return sa.getInteger(attribute, defaultValue);
}
private static int anInt(@StyleableRes int attribute, TypedArray sa) {
return sa.getInt(attribute, 0);
}
@AnyRes
private static int resId(@StyleableRes int attribute, TypedArray sa) {
return sa.getResourceId(attribute, 0);
}
private static String string(@StyleableRes int attribute, TypedArray sa) {
return sa.getString(attribute);
}
private static String nonConfigString(int allowedChangingConfigs, @StyleableRes int attribute,
TypedArray sa) {
return sa.getNonConfigurationString(attribute, allowedChangingConfigs);
}
private static String nonResString(@StyleableRes int index, TypedArray sa) {
return sa.getNonResourceString(index);
}
/**
* Callback interface for retrieving information that may be needed while parsing
* a package.
*/
public interface Callback {
boolean hasFeature(String feature);
ParsingPackage startParsingPackage(@NonNull String packageName,
@NonNull String baseCodePath, @NonNull String codePath,
@NonNull TypedArray manifestArray, boolean isCoreApp);
}
}