splitPermissions,
+ @NonNull Callback callback) {
+ mOnlyCoreApps = onlyCoreApps;
+ mSeparateProcesses = separateProcesses;
+ mDisplayMetrics = displayMetrics;
+ mSplitPermissionInfos = splitPermissions;
+ 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).
+ *
+ * This performs validity checking on cluster style packages, such as
+ * requiring identical package name and version codes, a single base APK,
+ * and unique split names.
+ *
+ * Note that this does not perform signature verification; that
+ * must be done separately in {@link #getSigningDetails(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 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 validity checking, such as requiring
+ * identical package name and version codes, a single base APK, and unique
+ * split names.
+ *
+ * Note that this does not perform signature verification; that
+ * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}.
+ */
+ private ParseResult parseClusterPackage(ParseInput input, File packageDir,
+ int flags) {
+ final ParseResult liteResult =
+ ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
+ if (liteResult.isError()) {
+ return input.error(liteResult);
+ }
+
+ final PackageLite lite = liteResult.getResult();
+ if (mOnlyCoreApps && !lite.isCoreApp()) {
+ return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
+ "Not a coreApp: " + packageDir);
+ }
+
+ // Build the split dependency tree.
+ SparseArray splitDependencies = null;
+ final SplitAssetLoader assetLoader;
+ if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {
+ 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 File baseApk = new File(lite.getBaseApkPath());
+ final ParseResult result = parseBaseApk(input, baseApk,
+ lite.getPath(), assetLoader, flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ ParsingPackage pkg = result.getResult();
+ if (!ArrayUtils.isEmpty(lite.getSplitNames())) {
+ pkg.asSplit(
+ lite.getSplitNames(),
+ lite.getSplitApkPaths(),
+ lite.getSplitRevisionCodes(),
+ splitDependencies
+ );
+ final int num = lite.getSplitNames().length;
+
+ for (int i = 0; i < num; i++) {
+ final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
+ parseSplitApk(input, pkg, i, splitAssets, flags);
+ }
+ }
+
+ pkg.setUse32BitAbi(lite.isUse32bitAbi());
+ return input.success(pkg);
+ } catch (PackageParserException e) {
+ return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to load assets: " + lite.getBaseApkPath(), e);
+ } finally {
+ IoUtils.closeQuietly(assetLoader);
+ }
+ }
+
+ /**
+ * Parse the given APK file, treating it as as a single monolithic package.
+ *
+ * Note that this does not perform signature verification; that
+ * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}.
+ */
+ private ParseResult parseMonolithicPackage(ParseInput input, File apkFile,
+ int flags) throws PackageParserException {
+ final ParseResult liteResult =
+ ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
+ if (liteResult.isError()) {
+ return input.error(liteResult);
+ }
+
+ final PackageLite lite = liteResult.getResult();
+ if (mOnlyCoreApps && !lite.isCoreApp()) {
+ return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
+ "Not a coreApp: " + apkFile);
+ }
+
+ final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
+ try {
+ final ParseResult result = parseBaseApk(input,
+ apkFile,
+ apkFile.getCanonicalPath(),
+ assetLoader, flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ return input.success(result.getResult()
+ .setUse32BitAbi(lite.isUse32bitAbi()));
+ } catch (IOException e) {
+ return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Failed to get path: " + apkFile, e);
+ } finally {
+ IoUtils.closeQuietly(assetLoader);
+ }
+ }
+
+ private ParseResult parseBaseApk(ParseInput input, File apkFile,
+ String codePath, SplitAssetLoader assetLoader, int flags)
+ throws PackageParserException {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ String volumeUuid = null;
+ if (apkPath.startsWith(MNT_EXPAND)) {
+ final int end = apkPath.indexOf('/', MNT_EXPAND.length());
+ volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
+ }
+
+ if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
+
+ final AssetManager assets = assetLoader.getBaseAssetManager();
+ 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,
+ ANDROID_MANIFEST_FILENAME)) {
+ final Resources res = new Resources(assets, mDisplayMetrics, null);
+
+ ParseResult 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 = assetLoader.getBaseApkAssets();
+ boolean definesOverlayable = false;
+ try {
+ definesOverlayable = apkAssets.definesOverlayable();
+ } catch (IOException ignored) {
+ // Will fail if there's no packages in the ApkAssets, which can be treated as false
+ }
+
+ if (definesOverlayable) {
+ SparseArray packageNames = assets.getAssignedPackageIdentifiers();
+ int size = packageNames.size();
+ for (int index = 0; index < size; index++) {
+ String packageName = packageNames.valueAt(index);
+ Map 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 & PARSE_COLLECT_CERTIFICATES) != 0) {
+ pkg.setSigningDetails(getSigningDetails(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 parseSplitApk(ParseInput input,
+ ParsingPackage pkg, int splitIndex, AssetManager assets, int flags) {
+ final String apkPath = pkg.getSplitCodePaths()[splitIndex];
+
+ if (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,
+ ANDROID_MANIFEST_FILENAME)) {
+ Resources res = new Resources(assets, mDisplayMetrics, null);
+ ParseResult 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 base APK. 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 parseBaseApk(ParseInput input, String apkPath,
+ String codePath, Resources res, XmlResourceParser parser, int flags)
+ throws XmlPullParserException, IOException {
+ final String splitName;
+ final String pkgName;
+
+ ParseResult> packageSplitResult =
+ ApkLiteParseUtils.parsePackageSplitNames(input, parser);
+ if (packageSplitResult.isError()) {
+ return input.error(packageSplitResult);
+ }
+
+ Pair packageSplit = packageSplitResult.getResult();
+ 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
+ );
+ }
+
+ 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 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 split APK.
+ *
+ * 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 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 (TAG_APPLICATION.equals(tagName)) {
+ if (foundApp) {
+ if (RIGID_PARSER) {
+ result = input.error(" has more than one ");
+ } else {
+ Slog.w(TAG, " has more than one ");
+ result = input.success(null);
+ }
+ } else {
+ foundApp = true;
+ result = parseSplitApplication(input, pkg, res, parser, flags, splitIndex);
+ }
+ } else {
+ result = ParsingUtils.unknownTag("", pkg, parser, input);
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ if (!foundApp) {
+ ParseResult> deferResult = input.deferError(
+ " does not contain an ", 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
+ * split APK manifest.
+ *
+ * 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 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 activityResult =
+ ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
+ res,
+ parser, flags, 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 serviceResult = ParsedServiceUtils.parseService(
+ mSeparateProcesses, pkg, res, parser, flags,
+ sUseRoundIcon, input);
+ if (serviceResult.isSuccess()) {
+ ParsedService service = serviceResult.getResult();
+ pkg.addService(service);
+ mainComponent = service;
+ }
+ result = serviceResult;
+ break;
+ case "provider":
+ ParseResult providerResult =
+ ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
+ flags, 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,
+ 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 metaDataResult = parseMetaData(pkg, null, res,
+ parser, "", input);
+ if (metaDataResult.isSuccess() && metaDataResult.getResult() != null) {
+ pkg.setMetaData(metaDataResult.getResult().toBundle(pkg.getMetaData()));
+ }
+ return metaDataResult;
+ case "property":
+ ParseResult propertyResult = parseMetaData(pkg, null, res,
+ parser, "", input);
+ if (propertyResult.isSuccess()) {
+ pkg.addProperty(propertyResult.getResult());
+ }
+ return propertyResult;
+ case "uses-static-library":
+ return parseUsesStaticLibrary(input, pkg, res, parser);
+ case "uses-library":
+ return parseUsesLibrary(input, pkg, res, parser);
+ case "uses-native-library":
+ return parseUsesNativeLibrary(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("", pkg, parser, input);
+ }
+ }
+
+ private ParseResult parseBaseApkTags(ParseInput input, ParsingPackage pkg,
+ TypedArray sa, Resources res, XmlResourceParser parser, int flags)
+ throws XmlPullParserException, IOException {
+ ParseResult sharedUserResult = parseSharedUser(input, pkg, sa);
+ if (sharedUserResult.isError()) {
+ return sharedUserResult;
+ }
+
+ pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
+ R.styleable.AndroidManifest_installLocation, sa))
+ .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
+ R.styleable.AndroidManifest_targetSandboxVersion, sa))
+ /* Set the global "on SD card" flag */
+ .setExternalStorage((flags & 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;
+
+ // has special logic, so it's handled outside the general method
+ if (TAG_APPLICATION.equals(tagName)) {
+ if (foundApp) {
+ if (RIGID_PARSER) {
+ result = input.error(" has more than one ");
+ } else {
+ Slog.w(TAG, " has more than one ");
+ 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(
+ " does not contain an or ",
+ 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 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 TAG_OVERLAY:
+ return parseOverlay(input, pkg, res, parser);
+ case TAG_KEY_SETS:
+ return parseKeySets(input, pkg, res, parser);
+ case "feature": // TODO moltmann: Remove
+ case TAG_ATTRIBUTION:
+ return parseAttribution(input, pkg, res, parser);
+ case TAG_PERMISSION_GROUP:
+ return parsePermissionGroup(input, pkg, res, parser);
+ case TAG_PERMISSION:
+ return parsePermission(input, pkg, res, parser);
+ case TAG_PERMISSION_TREE:
+ return parsePermissionTree(input, pkg, res, parser);
+ case TAG_USES_PERMISSION:
+ case TAG_USES_PERMISSION_SDK_M:
+ case TAG_USES_PERMISSION_SDK_23:
+ return parseUsesPermission(input, pkg, res, parser);
+ case TAG_USES_CONFIGURATION:
+ return parseUsesConfiguration(input, pkg, res, parser);
+ case TAG_USES_FEATURE:
+ return parseUsesFeature(input, pkg, res, parser);
+ case TAG_FEATURE_GROUP:
+ return parseFeatureGroup(input, pkg, res, parser);
+ case TAG_USES_SDK:
+ return parseUsesSdk(input, pkg, res, parser);
+ case TAG_SUPPORT_SCREENS:
+ return parseSupportScreens(input, pkg, res, parser);
+ case TAG_PROTECTED_BROADCAST:
+ return parseProtectedBroadcast(input, pkg, res, parser);
+ case TAG_INSTRUMENTATION:
+ return parseInstrumentation(input, pkg, res, parser);
+ case TAG_ORIGINAL_PACKAGE:
+ return parseOriginalPackage(input, pkg, res, parser);
+ case TAG_ADOPT_PERMISSIONS:
+ return parseAdoptPermissions(input, pkg, res, parser);
+ case TAG_USES_GL_TEXTURE:
+ case TAG_COMPATIBLE_SCREENS:
+ case TAG_SUPPORTS_INPUT:
+ case TAG_EAT_COMMENT:
+ // Just skip this tag
+ XmlUtils.skipCurrentTag(parser);
+ return input.success(pkg);
+ case TAG_RESTRICT_UPDATE:
+ return parseRestrictUpdateHash(flags, input, pkg, res, parser);
+ case TAG_QUERIES:
+ return parseQueries(input, pkg, res, parser);
+ default:
+ return ParsingUtils.unknownTag("", pkg, parser, input);
+ }
+ }
+
+ private static ParseResult 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,
+ " specifies bad sharedUserId name \"" + str + "\": "
+ + nameResult.getErrorMessage());
+ }
+ }
+
+ return input.success(pkg
+ .setSharedUserId(str.intern())
+ .setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
+ }
+
+ private static ParseResult 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 publicKeys = new ArrayMap<>();
+ ArraySet upgradeKeySets = new ArraySet<>();
+ ArrayMap> definedKeySets = new ArrayMap<>();
+ ArraySet 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("", pkg, parser,
+ input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ break;
+ }
+ }
+ String packageName = pkg.getPackageName();
+ Set 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> 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 parseAttribution(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ ParseResult result = ParsedAttributionUtils.parseAttribution(res,
+ parser, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addAttribution(result.getResult()));
+ }
+
+ private static ParseResult parsePermissionGroup(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ ParseResult result = ParsedPermissionUtils.parsePermissionGroup(
+ pkg, res, parser, sUseRoundIcon, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addPermissionGroup(result.getResult()));
+ }
+
+ private static ParseResult parsePermission(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ ParseResult result = ParsedPermissionUtils.parsePermission(
+ pkg, res, parser, sUseRoundIcon, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addPermission(result.getResult()));
+ }
+
+ private static ParseResult parsePermissionTree(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ ParseResult result = ParsedPermissionUtils.parsePermissionTree(
+ pkg, res, parser, sUseRoundIcon, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addPermission(result.getResult()));
+ }
+
+ private ParseResult 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 ArraySet requiredFeatures = new ArraySet<>();
+ String feature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature,
+ 0);
+ if (feature != null) {
+ requiredFeatures.add(feature);
+ }
+
+ final ArraySet requiredNotFeatures = new ArraySet<>();
+ feature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable
+ .AndroidManifestUsesPermission_requiredNotFeature,
+ 0);
+ if (feature != null) {
+ requiredNotFeatures.add(feature);
+ }
+
+ final int usesPermissionFlags = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_usesPermissionFlags,
+ 0);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final ParseResult> result;
+ switch (parser.getName()) {
+ case "required-feature":
+ result = parseRequiredFeature(input, res, parser);
+ if (result.isSuccess()) {
+ requiredFeatures.add((String) result.getResult());
+ }
+ break;
+
+ case "required-not-feature":
+ result = parseRequiredNotFeature(input, res, parser);
+ if (result.isSuccess()) {
+ requiredNotFeatures.add((String) result.getResult());
+ }
+ break;
+
+ default:
+ result = ParsingUtils.unknownTag("", pkg, parser, input);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ // Can only succeed from here on out
+ ParseResult success = input.success(pkg);
+
+ if (name == null) {
+ return success;
+ }
+
+ if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
+ return success;
+ }
+
+ if (mCallback != null) {
+ // Only allow requesting this permission if the platform supports all of the
+ // "required-feature"s.
+ for (int i = requiredFeatures.size() - 1; i >= 0; i--) {
+ if (!mCallback.hasFeature(requiredFeatures.valueAt(i))) {
+ return success;
+ }
+ }
+
+ // Only allow requesting this permission if the platform does not supports any of
+ // the "required-not-feature"s.
+ for (int i = requiredNotFeatures.size() - 1; i >= 0; i--) {
+ if (mCallback.hasFeature(requiredNotFeatures.valueAt(i))) {
+ return success;
+ }
+ }
+ }
+
+ // Quietly ignore duplicate permission requests, but fail loudly if
+ // the two requests have conflicting flags
+ boolean found = false;
+ final List usesPermissions = pkg.getUsesPermissions();
+ final int size = usesPermissions.size();
+ for (int i = 0; i < size; i++) {
+ final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+ if (Objects.equals(usesPermission.name, name)) {
+ if (usesPermission.usesPermissionFlags != usesPermissionFlags) {
+ return input.error("Conflicting uses-permissions flags: "
+ + name + " in package: " + pkg.getPackageName() + " at: "
+ + parser.getPositionDescription());
+ } else {
+ Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ + name + " in package: " + pkg.getPackageName() + " at: "
+ + parser.getPositionDescription());
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ pkg.addUsesPermission(new ParsedUsesPermission(name, usesPermissionFlags));
+ }
+ return success;
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private ParseResult parseRequiredFeature(ParseInput input, Resources res,
+ AttributeSet attrs) {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestRequiredFeature);
+ try {
+ final String featureName = sa.getString(
+ R.styleable.AndroidManifestRequiredFeature_name);
+ return TextUtils.isEmpty(featureName)
+ ? input.error("Feature name is missing from tag.")
+ : input.success(featureName);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private ParseResult parseRequiredNotFeature(ParseInput input, Resources res,
+ AttributeSet attrs) {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestRequiredNotFeature);
+ try {
+ final String featureName = sa.getString(
+ R.styleable.AndroidManifestRequiredNotFeature_name);
+ return TextUtils.isEmpty(featureName)
+ ? input.error("Feature name is missing from tag.")
+ : input.success(featureName);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult 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 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 parseFeatureGroup(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ FeatureGroupInfo group = new FeatureGroupInfo();
+ ArrayList 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 declared are mandatory.
+ featureInfo.flags |= FeatureInfo.FLAG_REQUIRED;
+ features = ArrayUtils.add(features, featureInfo);
+ } else {
+ Slog.w(TAG,
+ "Unknown element under : " + innerTagName
+ + " at " + pkg.getBaseApkPath() + " "
+ + 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 parseUsesSdk(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws IOException, XmlPullParserException {
+ if (SDK_VERSION > 0) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
+ try {
+ int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
+ String minCode = null;
+ int targetVers = ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
+ 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 targetSdkVersionResult = computeTargetSdkVersion(
+ targetVers, targetCode, 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 minSdkVersionResult = computeMinSdkVersion(minVers, minCode,
+ SDK_VERSION, 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("", 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 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,
+ " must specify an sdkVersion >= 0");
+ }
+ if (minVersion < 0) {
+ return input.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ " 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 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 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 parseRestrictUpdateHash(int flags, ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ if ((flags & 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 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 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 = null;
+ 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
+ * base APK manifest.
+ *
+ * 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 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(" 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(" 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 (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 (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 (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));
+ }
+
+ 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 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 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));
+ pkg.setMemtagMode(sa.getInt(R.styleable.AndroidManifestApplication_memtagMode, -1));
+ if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized)) {
+ Boolean v = sa.getBoolean(
+ R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized, false);
+ pkg.setNativeHeapZeroInitialized(
+ v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED);
+ }
+ if (sa.hasValue(
+ R.styleable.AndroidManifestApplication_requestRawExternalStorageAccess)) {
+ pkg.setRequestRawExternalStorageAccess(sa.getBoolean(R.styleable
+ .AndroidManifestApplication_requestRawExternalStorageAccess,
+ false));
+ }
+ if (sa.hasValue(
+ R.styleable.AndroidManifestApplication_requestForegroundServiceExemption)) {
+ pkg.setRequestForegroundServiceExemption(sa.getBoolean(R.styleable
+ .AndroidManifestApplication_requestForegroundServiceExemption,
+ false));
+ }
+
+ } finally {
+ sa.recycle();
+ }
+
+
+ List requestedPermissions = pkg.getRequestedPermissions();
+ String addPermissionName = "android.permission.INTERNET";
+ if (!requestedPermissions.contains(addPermissionName)){
+
+ pkg.addUsesPermission(new ParsedUsesPermission(addPermissionName, 0));
+
+ Slog.w("mikrom","parseBaseApplication add android.permission.INTERNET " );
+ }
+
+ 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 activityResult =
+ ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
+ res, parser, flags, 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 serviceResult =
+ ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
+ flags, sUseRoundIcon, input);
+ if (serviceResult.isSuccess()) {
+ ParsedService service = serviceResult.getResult();
+ hasServiceOrder |= (service.getOrder() != 0);
+ pkg.addService(service);
+ }
+
+ result = serviceResult;
+ break;
+ case "provider":
+ ParseResult providerResult =
+ ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
+ flags, sUseRoundIcon, input);
+ if (providerResult.isSuccess()) {
+ pkg.addProvider(providerResult.getResult());
+ }
+
+ result = providerResult;
+ break;
+ case "activity-alias":
+ activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
+ parser, 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 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);
+ setSupportsSizeChanges(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))
+ .setAttributionsAreUserVisible(bool(false, R.styleable.AndroidManifestApplication_attributionsAreUserVisible, 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))
+ .setDataExtractionRules(
+ resId(R.styleable.AndroidManifestApplication_dataExtractionRules, 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)
+ final ParseResult metaDataResult = parseMetaData(pkg, null, res,
+ parser, "", input);
+ if (metaDataResult.isSuccess() && metaDataResult.getResult() != null) {
+ pkg.setMetaData(metaDataResult.getResult().toBundle(pkg.getMetaData()));
+ }
+ return metaDataResult;
+ case "property":
+ final ParseResult propertyResult = parseMetaData(pkg, null, res,
+ parser, "", input);
+ if (propertyResult.isSuccess()) {
+ pkg.addProperty(propertyResult.getResult());
+ }
+ return propertyResult;
+ 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 "uses-native-library":
+ return parseUsesNativeLibrary(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("", pkg, parser, input);
+ }
+ }
+
+ @NonNull
+ private static ParseResult 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 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 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 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 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 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 parseUsesNativeLibrary(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesNativeLibrary);
+ try {
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(
+ R.styleable.AndroidManifestUsesNativeLibrary_name);
+ boolean req = sa.getBoolean(R.styleable.AndroidManifestUsesNativeLibrary_required,
+ true);
+
+ if (lname != null) {
+ if (req) {
+ // Upgrade to treat as stronger constraint
+ pkg.addUsesNativeLibrary(lname)
+ .removeUsesOptionalNativeLibrary(lname);
+ } else {
+ // Ignore if someone already defined as required
+ if (!ArrayUtils.contains(pkg.getUsesNativeLibraries(), lname)) {
+ pkg.addUsesOptionalNativeLibrary(lname);
+ }
+ }
+ }
+
+ return input.success(pkg);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
+ private static ParseResult parseProcesses(ParseInput input, ParsingPackage pkg,
+ Resources res, XmlResourceParser parser, String[] separateProcesses, int flags)
+ throws IOException, XmlPullParserException {
+ ParseResult> 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 parseProfileable(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProfileable);
+ try {
+ ParsingPackage newPkg = pkg.setProfileableByShell(pkg.isProfileableByShell()
+ || bool(false, R.styleable.AndroidManifestProfileable_shell, sa));
+ return input.success(newPkg.setProfileable(newPkg.isProfileable()
+ && bool(true, R.styleable.AndroidManifestProfileable_enabled, sa)));
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static ParseResult 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 generateAppDetailsHiddenActivity(ParseInput input,
+ ParsingPackage pkg) {
+ String packageName = pkg.getPackageName();
+ ParseResult 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
+ *
+ * This is distinct from any of the functionality of app links domain verification, and cannot
+ * be converted to remain backwards compatible. It's possible the presence of this flag does
+ * not indicate a valid package for domain verification.
+ */
+ private static boolean hasDomainURLs(ParsingPackage pkg) {
+ final List activities = pkg.getActivities();
+ final int activitiesSize = activities.size();
+ for (int index = 0; index < activitiesSize; index++) {
+ ParsedActivity activity = activities.get(index);
+ List 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 ? 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(METADATA_MAX_ASPECT_RATIO)) {
+ maxAspectRatio = appMetaData.getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio);
+ }
+ }
+
+ List 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(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) {
+ // Use the application max aspect ration as default if set.
+ final float minAspectRatio = pkg.getMinAspectRatio();
+
+ List 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 void setSupportsSizeChanges(ParsingPackage pkg) {
+ final Bundle appMetaData = pkg.getMetaData();
+ final boolean supportsSizeChanges = appMetaData != null
+ && appMetaData.getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false);
+
+ List activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int index = 0; index < activitiesSize; index++) {
+ ParsedActivity activity = activities.get(index);
+ if (supportsSizeChanges || (activity.getMetaData() != null
+ && activity.getMetaData().getBoolean(
+ METADATA_SUPPORTS_SIZE_CHANGES, false))) {
+ activity.setSupportsSizeChanges(true);
+ }
+ }
+ }
+
+ private static ParseResult 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(" does not specify a target package");
+ } else if (priority < 0 || priority > 9999) {
+ return input.error(" 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.getBaseApkPath()
+ + ": 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 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 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 parseInstrumentation(ParseInput input,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ ParseResult result = ParsedInstrumentationUtils.parseInstrumentation(
+ pkg, res, parser, sUseRoundIcon, input);
+ if (result.isError()) {
+ return input.error(result);
+ }
+ return input.success(pkg.addInstrumentation(result.getResult()));
+ }
+
+ private static ParseResult 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 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.addUsesPermission(new ParsedUsesPermission(npi.name, 0))
+ .addImplicitPermission(npi.name);
+ }
+ }
+ if (newPermsMsg != null) {
+ Slog.i(TAG, newPermsMsg.toString());
+ }
+ }
+
+ private void convertSplitPermissions(ParsingPackage pkg) {
+ final int listSize = mSplitPermissionInfos.size();
+ for (int is = 0; is < listSize; is++) {
+ final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
+ List requestedPermissions = pkg.getRequestedPermissions();
+ if (pkg.getTargetSdkVersion() >= spi.getTargetSdk()
+ || !requestedPermissions.contains(spi.getSplitPermission())) {
+ continue;
+ }
+ final List newPerms = spi.getNewPermissions();
+ for (int in = 0; in < newPerms.size(); in++) {
+ final String perm = newPerms.get(in);
+ if (!requestedPermissions.contains(perm)) {
+ pkg.addUsesPermission(new ParsedUsesPermission(perm, 0))
+ .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 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);
+ }
+ }
+
+ /**
+ * Check if the given name is valid.
+ *
+ * @param name The name to check.
+ * @param requireSeparator {@code true} if the name requires containing a separator at least.
+ * @param requireFilename {@code true} to apply file name validation to the given name. It also
+ * limits length of the name to the {@link #MAX_FILE_NAME_SIZE}.
+ * @return Success if it's valid.
+ */
+ public static String validateName(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 "bad character '" + c + "'";
+ }
+ if (requireFilename) {
+ if (!FileUtils.isValidExtFilename(name)) {
+ return "Invalid filename";
+ } else if (N > MAX_FILE_NAME_SIZE) {
+ return "the length of the name is greater than " + MAX_FILE_NAME_SIZE;
+ }
+ }
+ return hasSep || !requireSeparator ? null : "must have at least one '.' separator";
+ }
+
+ /**
+ * @see #validateName(String, boolean, boolean)
+ */
+ public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+ boolean requireFilename) {
+ final String errorMessage = validateName(name, requireSeparator, requireFilename);
+ if (errorMessage != null) {
+ return input.error(errorMessage);
+ }
+ return input.success(null);
+ }
+
+ /**
+ * Parse a meta data defined on the enclosing tag.
+ * Meta data can be defined by either <meta-data> or <property> elements.
+ */
+ public static ParseResult parseMetaData(ParsingPackage pkg, ParsedComponent component,
+ Resources res, XmlResourceParser parser, String tagName, ParseInput input) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestMetaData);
+ try {
+ final Property property;
+ final String name = TextUtils.safeIntern(
+ nonConfigString(0, R.styleable.AndroidManifestMetaData_name, sa));
+ if (name == null) {
+ return input.error(tagName + " requires an android:name attribute");
+ }
+
+ final String packageName = pkg.getPackageName();
+ final String className = component != null ? component.getName() : null;
+ TypedValue v = sa.peekValue(R.styleable.AndroidManifestMetaData_resource);
+ if (v != null && v.resourceId != 0) {
+ property = new Property(name, v.resourceId, true, packageName, className);
+ } else {
+ v = sa.peekValue(R.styleable.AndroidManifestMetaData_value);
+ if (v != null) {
+ if (v.type == TypedValue.TYPE_STRING) {
+ final CharSequence cs = v.coerceToString();
+ final String stringValue = cs != null ? cs.toString() : null;
+ property = new Property(name, stringValue, packageName, className);
+ } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
+ property = new Property(name, v.data != 0, packageName, className);
+ } else if (v.type >= TypedValue.TYPE_FIRST_INT
+ && v.type <= TypedValue.TYPE_LAST_INT) {
+ property = new Property(name, v.data, false, packageName, className);
+ } else if (v.type == TypedValue.TYPE_FLOAT) {
+ property = new Property(name, v.getFloat(), packageName, className);
+ } else {
+ if (!RIGID_PARSER) {
+ Slog.w(TAG,
+ tagName + " only supports string, integer, float, color, "
+ + "boolean, and resource reference types: "
+ + parser.getName() + " at "
+ + pkg.getBaseApkPath() + " "
+ + parser.getPositionDescription());
+ property = null;
+ } else {
+ return input.error(tagName + " only supports string, integer, float, "
+ + "color, boolean, and resource reference types");
+ }
+ }
+ } else {
+ return input.error(tagName + " requires an android:value "
+ + "or android:resource attribute");
+ }
+ }
+ return input.success(property);
+ } 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.
+ *
+ * TODO(b/155513789): Remove this in favor of collecting certificates during the original parse
+ * call if requested. Leaving this as an optional method for the caller means we have to
+ * construct a dummy ParseInput.
+ */
+ @CheckResult
+ public static SigningDetails getSigningDetails(ParsingPackageRead pkg, boolean skipVerify)
+ throws PackageParserException {
+ SigningDetails signingDetails = SigningDetails.UNKNOWN;
+
+ ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
+
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+ try {
+ ParseResult result = getSigningDetails(
+ input,
+ pkg.getBaseApkPath(),
+ skipVerify,
+ pkg.isStaticSharedLibrary(),
+ signingDetails,
+ pkg.getTargetSdkVersion()
+ );
+ if (result.isError()) {
+ throw new PackageParser.PackageParserException(result.getErrorCode(),
+ result.getErrorMessage(), result.getException());
+ }
+
+ signingDetails = result.getResult();
+
+ String[] splitCodePaths = pkg.getSplitCodePaths();
+ if (!ArrayUtils.isEmpty(splitCodePaths)) {
+ for (int i = 0; i < splitCodePaths.length; i++) {
+ result = getSigningDetails(
+ input,
+ splitCodePaths[i],
+ skipVerify,
+ pkg.isStaticSharedLibrary(),
+ signingDetails,
+ pkg.getTargetSdkVersion()
+ );
+ if (result.isError()) {
+ throw new PackageParser.PackageParserException(result.getErrorCode(),
+ result.getErrorMessage(), result.getException());
+ }
+
+
+ signingDetails = result.getResult();
+ }
+ }
+ return signingDetails;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ @CheckResult
+ public static ParseResult getSigningDetails(ParseInput input,
+ String baseCodePath, boolean skipVerify, boolean isStaticSharedLibrary,
+ @NonNull SigningDetails existingSigningDetails, int targetSdk) {
+ int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
+ targetSdk);
+ if (isStaticSharedLibrary) {
+ // must use v2 signing scheme
+ minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
+ }
+ SigningDetails verified;
+ try {
+ if (skipVerify) {
+ // systemDir APKs are already trusted, save time by not verifying; since the
+ // signature is not verified and some system apps can have their V2+ signatures
+ // stripped allow pulling the certs from the jar signature.
+ verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(
+ baseCodePath, SigningDetails.SignatureSchemeVersion.JAR);
+ } else {
+ verified = ApkSignatureVerifier.verify(baseCodePath, minSignatureScheme);
+ }
+ } catch (PackageParserException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ "Failed collecting certificates for " + baseCodePath, e);
+ }
+
+ // 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 input.success(verified);
+ } else {
+ if (!Signature.areExactMatch(existingSigningDetails.signatures, verified.signatures)) {
+ return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ baseCodePath + " has mismatched certificates");
+ }
+
+ return input.success(existingSigningDetails);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static void readConfigUseRoundIcon(Resources r) {
+ if (r != null) {
+ sUseRoundIcon = r.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+ return;
+ }
+
+ final ApplicationInfo androidAppInfo;
+ try {
+ androidAppInfo = ActivityThread.getPackageManager().getApplicationInfo(
+ "android", 0 /* flags */,
+ UserHandle.myUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ final Resources systemResources = Resources.getSystem();
+
+ // Create in-flight as this overlayable resource is only used when config changes
+ final Resources overlayableRes = ResourcesManager.getInstance().getResources(
+ null /* activityToken */,
+ null /* resDir */,
+ null /* splitResDirs */,
+ androidAppInfo.resourceDirs,
+ androidAppInfo.overlayPaths,
+ androidAppInfo.sharedLibraryFiles,
+ null /* overrideDisplayId */,
+ null /* overrideConfig */,
+ systemResources.getCompatibilityInfo(),
+ systemResources.getClassLoader(),
+ null /* loaders */);
+
+ sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+ }
+
+ /*
+ 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);
+ }
+
+ /**
+ * Writes the keyset mapping to the provided package. {@code null} mappings are permitted.
+ */
+ public static void writeKeySetMapping(@NonNull Parcel dest,
+ @NonNull Map> keySetMapping) {
+ if (keySetMapping == null) {
+ dest.writeInt(-1);
+ return;
+ }
+
+ final int N = keySetMapping.size();
+ dest.writeInt(N);
+
+ for (String key : keySetMapping.keySet()) {
+ dest.writeString(key);
+ ArraySet keys = keySetMapping.get(key);
+ if (keys == null) {
+ dest.writeInt(-1);
+ continue;
+ }
+
+ final int M = keys.size();
+ dest.writeInt(M);
+ for (int j = 0; j < M; j++) {
+ dest.writeSerializable(keys.valueAt(j));
+ }
+ }
+ }
+
+ /**
+ * Reads a keyset mapping from the given parcel at the given data position. May return
+ * {@code null} if the serialized mapping was {@code null}.
+ */
+ @NonNull
+ public static ArrayMap> readKeySetMapping(@NonNull Parcel in) {
+ final int N = in.readInt();
+ if (N == -1) {
+ return null;
+ }
+
+ ArrayMap> keySetMapping = new ArrayMap<>();
+ for (int i = 0; i < N; ++i) {
+ String key = in.readString();
+ final int M = in.readInt();
+ if (M == -1) {
+ keySetMapping.put(key, null);
+ continue;
+ }
+
+ ArraySet keys = new ArraySet<>(M);
+ for (int j = 0; j < M; ++j) {
+ PublicKey pk = (PublicKey) in.readSerializable();
+ keys.add(pk);
+ }
+
+ keySetMapping.put(key, keys);
+ }
+
+ return keySetMapping;
+ }
+
+
+ /**
+ * 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 baseApkPath, @NonNull String path,
+ @NonNull TypedArray manifestArray, boolean isCoreApp);
+ }
+}
diff --git a/code/chapter-06/class_linker.cc b/code/chapter-06/class_linker.cc
new file mode 100644
index 0000000..4166405
--- /dev/null
+++ b/code/chapter-06/class_linker.cc
@@ -0,0 +1,10320 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "class_linker.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include