| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.content.pm.parsing; |
| |
| import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; |
| import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; |
| import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; |
| import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; |
| import static android.content.pm.PackageManager.FEATURE_WATCH; |
| import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; |
| import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; |
| import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; |
| import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; |
| import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; |
| import static android.os.Build.VERSION_CODES.O; |
| import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; |
| import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityTaskManager; |
| import android.app.ActivityThread; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.ConfigurationInfo; |
| import android.content.pm.FeatureGroupInfo; |
| import android.content.pm.FeatureInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageItemInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageParser; |
| import android.content.pm.PackageParser.PackageParserException; |
| import android.content.pm.PackageParser.SigningDetails; |
| import android.content.pm.Signature; |
| import android.content.pm.permission.SplitPermissionInfoParcelable; |
| import android.content.pm.split.DefaultSplitAssetLoader; |
| import android.content.pm.split.SplitAssetDependencyLoader; |
| import android.content.pm.split.SplitAssetLoader; |
| import android.content.res.ApkAssets; |
| import android.content.res.AssetManager; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.content.res.XmlResourceParser; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.FileUtils; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.os.Trace; |
| import android.os.ext.SdkExtensions; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.AttributeSet; |
| import android.util.DisplayMetrics; |
| import android.util.Pair; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.TypedValue; |
| import android.util.apk.ApkSignatureVerifier; |
| |
| import com.android.internal.R; |
| import com.android.internal.os.ClassLoaderFactory; |
| import com.android.internal.util.ArrayUtils; |
| import com.android.internal.util.XmlUtils; |
| |
| import libcore.io.IoUtils; |
| import libcore.util.EmptyArray; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.security.PublicKey; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** @hide */ |
| public class ApkParseUtils { |
| |
| // TODO(b/135203078): Consolidate log tags |
| static final String TAG = "PackageParsing"; |
| |
| /** |
| * Parse the package at the given location. Automatically detects if the |
| * package is a monolithic style (single APK file) or cluster style |
| * (directory of APKs). |
| * <p> |
| * This performs sanity checking on cluster style packages, such as |
| * requiring identical package name and version codes, a single base APK, |
| * and unique split names. |
| * <p> |
| * Note that this <em>does not</em> perform signature verification; that |
| * must be done separately in {@link #collectCertificates(ParsedPackage, 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 static ParsingPackage parsePackage( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| PackageParser.Callback callback, |
| DisplayMetrics displayMetrics, |
| boolean onlyCoreApps, |
| File packageFile, |
| int flags |
| ) throws PackageParserException { |
| if (packageFile.isDirectory()) { |
| return parseClusterPackage(parseInput, separateProcesses, callback, displayMetrics, |
| onlyCoreApps, packageFile, flags); |
| } else { |
| return parseMonolithicPackage(parseInput, separateProcesses, callback, displayMetrics, |
| onlyCoreApps, packageFile, flags); |
| } |
| } |
| |
| /** |
| * Parse all APKs contained in the given directory, treating them as a |
| * single package. This also performs sanity checking, such as requiring |
| * identical package name and version codes, a single base APK, and unique |
| * split names. |
| * <p> |
| * Note that this <em>does not</em> perform signature verification; that |
| * must be done separately in {@link #collectCertificates(ParsedPackage, boolean)}. |
| */ |
| private static ParsingPackage parseClusterPackage( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| PackageParser.Callback callback, |
| DisplayMetrics displayMetrics, |
| boolean onlyCoreApps, |
| File packageDir, |
| int flags |
| ) throws PackageParserException { |
| final PackageParser.PackageLite lite = ApkLiteParseUtils.parseClusterPackageLite(packageDir, |
| 0); |
| if (onlyCoreApps && !lite.coreApp) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Not a coreApp: " + packageDir); |
| } |
| |
| // Build the split dependency tree. |
| SparseArray<int[]> splitDependencies = null; |
| final SplitAssetLoader assetLoader; |
| if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) { |
| try { |
| splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite); |
| assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags); |
| } catch (SplitAssetDependencyLoader.IllegalDependencyException e) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage()); |
| } |
| } else { |
| assetLoader = new DefaultSplitAssetLoader(lite, flags); |
| } |
| |
| try { |
| final AssetManager assets = assetLoader.getBaseAssetManager(); |
| final File baseApk = new File(lite.baseCodePath); |
| ParsingPackage parsingPackage = parseBaseApk(parseInput, separateProcesses, callback, |
| displayMetrics, baseApk, assets, flags); |
| if (parsingPackage == null) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, |
| "Failed to parse base APK: " + baseApk); |
| } |
| |
| if (!ArrayUtils.isEmpty(lite.splitNames)) { |
| parsingPackage.asSplit( |
| lite.splitNames, |
| lite.splitCodePaths, |
| lite.splitRevisionCodes, |
| splitDependencies |
| ); |
| final int num = lite.splitNames.length; |
| |
| for (int i = 0; i < num; i++) { |
| final AssetManager splitAssets = assetLoader.getSplitAssetManager(i); |
| parseSplitApk(parseInput, displayMetrics, separateProcesses, parsingPackage, i, |
| splitAssets, flags); |
| } |
| } |
| |
| return parsingPackage.setCodePath(packageDir.getCanonicalPath()) |
| .setUse32BitAbi(lite.use32bitAbi); |
| } catch (IOException e) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, |
| "Failed to get path: " + lite.baseCodePath, e); |
| } finally { |
| IoUtils.closeQuietly(assetLoader); |
| } |
| } |
| |
| /** |
| * Parse the given APK file, treating it as as a single monolithic package. |
| * <p> |
| * Note that this <em>does not</em> perform signature verification; that |
| * must be done separately in {@link #collectCertificates(AndroidPackage, boolean)}. |
| */ |
| public static ParsingPackage parseMonolithicPackage( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| PackageParser.Callback callback, |
| DisplayMetrics displayMetrics, |
| boolean onlyCoreApps, |
| File apkFile, |
| int flags |
| ) throws PackageParserException { |
| final PackageParser.PackageLite lite = ApkLiteParseUtils.parseMonolithicPackageLite(apkFile, |
| flags); |
| if (onlyCoreApps) { |
| if (!lite.coreApp) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Not a coreApp: " + apkFile); |
| } |
| } |
| |
| final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); |
| try { |
| return parseBaseApk(parseInput, separateProcesses, callback, |
| displayMetrics, apkFile, assetLoader.getBaseAssetManager(), flags) |
| .setCodePath(apkFile.getCanonicalPath()) |
| .setUse32BitAbi(lite.use32bitAbi); |
| } catch (IOException e) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, |
| "Failed to get path: " + apkFile, e); |
| } finally { |
| IoUtils.closeQuietly(assetLoader); |
| } |
| } |
| |
| private static ParsingPackage parseBaseApk( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| PackageParser.Callback callback, |
| DisplayMetrics displayMetrics, |
| File apkFile, |
| AssetManager assets, |
| int flags |
| ) throws PackageParserException { |
| final String apkPath = apkFile.getAbsolutePath(); |
| |
| String volumeUuid = null; |
| if (apkPath.startsWith(PackageParser.MNT_EXPAND)) { |
| final int end = apkPath.indexOf('/', PackageParser.MNT_EXPAND.length()); |
| volumeUuid = apkPath.substring(PackageParser.MNT_EXPAND.length(), end); |
| } |
| |
| if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); |
| |
| XmlResourceParser parser = null; |
| try { |
| final int cookie = assets.findCookieForPath(apkPath); |
| if (cookie == 0) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, |
| "Failed adding asset path: " + apkPath); |
| } |
| parser = assets.openXmlResourceParser(cookie, PackageParser.ANDROID_MANIFEST_FILENAME); |
| final Resources res = new Resources(assets, displayMetrics, null); |
| |
| ParseResult result = parseBaseApk(parseInput, separateProcesses, callback, apkPath, res, |
| parser, flags); |
| if (!result.isSuccess()) { |
| throw new PackageParserException(result.getParseError(), |
| apkPath + " (at " + parser.getPositionDescription() + "): " |
| + result.getErrorMessage()); |
| } |
| |
| ParsingPackage pkg = result.getResultAndNull(); |
| ApkAssets apkAssets = assets.getApkAssets()[0]; |
| if (apkAssets.definesOverlayable()) { |
| SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers(); |
| int size = packageNames.size(); |
| for (int index = 0; index < size; index++) { |
| String packageName = packageNames.get(index); |
| Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName); |
| if (overlayableToActor != null && !overlayableToActor.isEmpty()) { |
| for (String overlayable : overlayableToActor.keySet()) { |
| pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable)); |
| } |
| } |
| } |
| } |
| |
| return pkg.setVolumeUuid(volumeUuid) |
| .setApplicationVolumeUuid(volumeUuid) |
| .setSigningDetails(SigningDetails.UNKNOWN); |
| } catch (PackageParserException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, |
| "Failed to read manifest from " + apkPath, e); |
| } finally { |
| IoUtils.closeQuietly(parser); |
| } |
| } |
| |
| private static void parseSplitApk( |
| ParseInput parseInput, |
| DisplayMetrics displayMetrics, |
| String[] separateProcesses, |
| ParsingPackage parsingPackage, |
| int splitIndex, |
| AssetManager assets, |
| int flags |
| ) throws PackageParserException { |
| final String apkPath = parsingPackage.getSplitCodePaths()[splitIndex]; |
| |
| if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); |
| |
| final Resources res; |
| XmlResourceParser parser = null; |
| try { |
| // This must always succeed, as the path has been added to the AssetManager before. |
| final int cookie = assets.findCookieForPath(apkPath); |
| if (cookie == 0) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, |
| "Failed adding asset path: " + apkPath); |
| } |
| |
| parser = assets.openXmlResourceParser(cookie, PackageParser.ANDROID_MANIFEST_FILENAME); |
| res = new Resources(assets, displayMetrics, null); |
| |
| final String[] outError = new String[1]; |
| ParseResult parseResult = parseSplitApk(parseInput, separateProcesses, parsingPackage, |
| res, parser, flags, splitIndex, outError); |
| if (!parseResult.isSuccess()) { |
| throw new PackageParserException(parseResult.getParseError(), |
| apkPath + " (at " + parser.getPositionDescription() + "): " |
| + parseResult.getErrorMessage()); |
| } |
| } catch (PackageParserException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, |
| "Failed to read manifest from " + apkPath, e); |
| } finally { |
| IoUtils.closeQuietly(parser); |
| } |
| } |
| |
| /** |
| * Parse the manifest of a <em>base APK</em>. When adding new features you |
| * need to consider whether they should be supported by split APKs and child |
| * packages. |
| * |
| * @param apkPath The package apk file path |
| * @param res The resources from which to resolve values |
| * @param parser The manifest parser |
| * @param flags Flags how to parse |
| * @return Parsed package or null on error. |
| */ |
| private static ParseResult parseBaseApk( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| PackageParser.Callback callback, |
| String apkPath, |
| Resources res, |
| XmlResourceParser parser, |
| int flags |
| ) throws XmlPullParserException, IOException { |
| final String splitName; |
| final String pkgName; |
| |
| try { |
| Pair<String, String> packageSplit = PackageParser.parsePackageSplitNames(parser, |
| parser); |
| pkgName = packageSplit.first; |
| splitName = packageSplit.second; |
| |
| if (!TextUtils.isEmpty(splitName)) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, |
| "Expected base APK, but found split " + splitName |
| ); |
| } |
| } catch (PackageParserException e) { |
| return parseInput.error(PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME); |
| } |
| |
| TypedArray manifestArray = null; |
| |
| try { |
| manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest); |
| |
| boolean isCoreApp = parser.getAttributeBooleanValue(null, "coreApp", false); |
| |
| ParsingPackage parsingPackage = PackageImpl.forParsing( |
| pkgName, |
| apkPath, |
| manifestArray, |
| isCoreApp |
| ); |
| |
| ParseResult result = parseBaseApkTags(parseInput, separateProcesses, callback, |
| parsingPackage, manifestArray, res, parser, flags); |
| if (!result.isSuccess()) { |
| return result; |
| } |
| |
| return parseInput.success(parsingPackage); |
| } finally { |
| if (manifestArray != null) { |
| manifestArray.recycle(); |
| } |
| } |
| } |
| |
| /** |
| * Parse the manifest of a <em>split APK</em>. |
| * <p> |
| * Note that split APKs have many more restrictions on what they're capable |
| * of doing, so many valid features of a base APK have been carefully |
| * omitted here. |
| * |
| * @param parsingPackage builder to fill |
| * @return false on failure |
| */ |
| private static ParseResult parseSplitApk( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser, |
| int flags, |
| int splitIndex, |
| String[] outError |
| ) 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 |
| && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { |
| if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { |
| continue; |
| } |
| |
| String tagName = parser.getName(); |
| if (tagName.equals(PackageParser.TAG_APPLICATION)) { |
| if (foundApp) { |
| if (PackageParser.RIGID_PARSER) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "<manifest> has more than one <application>" |
| ); |
| } else { |
| Slog.w(TAG, "<manifest> has more than one <application>"); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } |
| } |
| |
| foundApp = true; |
| ParseResult parseResult = parseSplitApplication(parseInput, separateProcesses, |
| parsingPackage, res, |
| parser, flags, |
| splitIndex, outError); |
| if (!parseResult.isSuccess()) { |
| return parseResult; |
| } |
| |
| } else if (PackageParser.RIGID_PARSER) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Bad element under <manifest>: " + parser.getName() |
| ); |
| |
| } else { |
| Slog.w(TAG, "Unknown element under <manifest>: " + parser.getName() |
| + " at " + parsingPackage.getBaseCodePath() + " " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } |
| } |
| |
| if (!foundApp) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY, |
| "<manifest> does not contain an <application>" |
| ); |
| } |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| /** |
| * Parse the {@code application} XML tree at the current parse location in a |
| * <em>split APK</em> manifest. |
| * <p> |
| * Note that split APKs have many more restrictions on what they're capable |
| * of doing, so many valid features of a base APK have been carefully |
| * omitted here. |
| */ |
| private static ParseResult parseSplitApplication( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser, |
| int flags, |
| int splitIndex, |
| String[] outError |
| ) throws XmlPullParserException, IOException { |
| TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication); |
| |
| parsingPackage.setSplitHasCode(splitIndex, sa.getBoolean( |
| R.styleable.AndroidManifestApplication_hasCode, true)); |
| |
| final String classLoaderName = sa.getString( |
| R.styleable.AndroidManifestApplication_classLoader); |
| if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) { |
| parsingPackage.setSplitClassLoaderName(splitIndex, classLoaderName); |
| } else { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Invalid class loader name: " + classLoaderName |
| ); |
| } |
| |
| final int innerDepth = parser.getDepth(); |
| int type; |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT |
| && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { |
| if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { |
| continue; |
| } |
| |
| ComponentParseUtils.ParsedComponent parsedComponent = null; |
| |
| String tagName = parser.getName(); |
| switch (tagName) { |
| case "activity": |
| ComponentParseUtils.ParsedActivity activity = |
| ComponentParseUtils.parseActivity(separateProcesses, |
| parsingPackage, |
| res, parser, flags, |
| outError, |
| false, |
| parsingPackage.isBaseHardwareAccelerated()); |
| if (activity == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addActivity(activity); |
| parsedComponent = activity; |
| break; |
| case "receiver": |
| activity = ComponentParseUtils.parseActivity( |
| separateProcesses, parsingPackage, |
| res, parser, flags, outError, |
| true, false); |
| if (activity == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addReceiver(activity); |
| parsedComponent = activity; |
| break; |
| case "service": |
| ComponentParseUtils.ParsedService s = ComponentParseUtils.parseService( |
| separateProcesses, |
| parsingPackage, |
| res, parser, flags, outError |
| ); |
| if (s == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addService(s); |
| parsedComponent = s; |
| break; |
| case "provider": |
| ComponentParseUtils.ParsedProvider p = ComponentParseUtils.parseProvider( |
| separateProcesses, |
| parsingPackage, |
| res, parser, flags, outError); |
| if (p == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addProvider(p); |
| parsedComponent = p; |
| break; |
| case "activity-alias": |
| activity = ComponentParseUtils.parseActivityAlias( |
| parsingPackage, |
| res, |
| parser, |
| outError |
| ); |
| if (activity == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addActivity(activity); |
| parsedComponent = activity; |
| break; |
| 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) |
| Bundle appMetaData = parseMetaData(parsingPackage, res, parser, |
| parsingPackage.getAppMetaData(), |
| outError); |
| if (appMetaData == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.setAppMetaData(appMetaData); |
| break; |
| case "uses-static-library": |
| ParseResult parseResult = parseUsesStaticLibrary(parseInput, parsingPackage, |
| res, parser); |
| if (!parseResult.isSuccess()) { |
| return parseResult; |
| } |
| |
| break; |
| case "uses-library": |
| sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesLibrary); |
| |
| // 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); |
| |
| sa.recycle(); |
| |
| if (lname != null) { |
| lname = lname.intern(); |
| if (req) { |
| // Upgrade to treat as stronger constraint |
| parsingPackage.addUsesLibrary(lname) |
| .removeUsesOptionalLibrary(lname); |
| } else { |
| // Ignore if someone already defined as required |
| if (!ArrayUtils.contains(parsingPackage.getUsesLibraries(), lname)) { |
| parsingPackage.addUsesOptionalLibrary(lname); |
| } |
| } |
| } |
| |
| XmlUtils.skipCurrentTag(parser); |
| break; |
| case "uses-package": |
| // Dependencies for app installers; we don't currently try to |
| // enforce this. |
| XmlUtils.skipCurrentTag(parser); |
| break; |
| default: |
| if (!PackageParser.RIGID_PARSER) { |
| Slog.w(TAG, "Unknown element under <application>: " + tagName |
| + " at " + parsingPackage.getBaseCodePath() + " " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } else { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Bad element under <application>: " + tagName |
| ); |
| } |
| } |
| |
| if (parsedComponent != null && parsedComponent.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. |
| parsedComponent.setSplitName(parsingPackage.getSplitNames()[splitIndex]); |
| } |
| } |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static ParseResult parseBaseApkTags( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| PackageParser.Callback callback, |
| ParsingPackage parsingPackage, |
| TypedArray manifestArray, |
| Resources res, |
| XmlResourceParser parser, |
| int flags |
| ) throws XmlPullParserException, IOException { |
| int type; |
| boolean foundApp = false; |
| |
| TypedArray sa = manifestArray; |
| |
| ParseResult sharedUserResult = parseSharedUser(parseInput, parsingPackage, sa); |
| if (!sharedUserResult.isSuccess()) { |
| return sharedUserResult; |
| } |
| |
| parseManifestAttributes(sa, parsingPackage, flags); |
| |
| int outerDepth = parser.getDepth(); |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT |
| && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { |
| if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { |
| continue; |
| } |
| |
| String tagName = parser.getName(); |
| |
| // All methods return a boolean, even if they can't fail. This can be enforced |
| // by making this final and not assigned, forcing the switch to assign success |
| // once in every branch. |
| final boolean success; |
| ParseResult parseResult = null; |
| |
| // TODO(b/135203078): Either use all booleans or all ParseResults |
| // TODO(b/135203078): Convert to instance methods to share variables |
| switch (tagName) { |
| case PackageParser.TAG_APPLICATION: |
| if (foundApp) { |
| if (PackageParser.RIGID_PARSER) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "<manifest> has more than one <application>" |
| ); |
| } else { |
| Slog.w(TAG, "<manifest> has more than one <application>"); |
| XmlUtils.skipCurrentTag(parser); |
| success = true; |
| } |
| } else { |
| foundApp = true; |
| parseResult = parseBaseApplication(parseInput, separateProcesses, |
| callback, |
| parsingPackage, res, parser, flags); |
| success = parseResult.isSuccess(); |
| } |
| break; |
| case PackageParser.TAG_OVERLAY: |
| parseResult = parseOverlay(parseInput, parsingPackage, res, parser); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_KEY_SETS: |
| parseResult = parseKeySets(parseInput, parsingPackage, res, parser); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_FEATURE: |
| parseResult = parseFeature(parseInput, parsingPackage, res, parser); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_PERMISSION_GROUP: |
| parseResult = parsePermissionGroup(parseInput, parsingPackage, res, |
| parser); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_PERMISSION: |
| parseResult = parsePermission(parseInput, parsingPackage, res, parser); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_PERMISSION_TREE: |
| parseResult = parsePermissionTree(parseInput, parsingPackage, res, parser); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_USES_PERMISSION: |
| case PackageParser.TAG_USES_PERMISSION_SDK_M: |
| case PackageParser.TAG_USES_PERMISSION_SDK_23: |
| parseResult = parseUsesPermission(parseInput, parsingPackage, res, parser, |
| callback); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_USES_CONFIGURATION: |
| success = parseUsesConfiguration(parsingPackage, res, parser); |
| break; |
| case PackageParser.TAG_USES_FEATURE: |
| success = parseUsesFeature(parsingPackage, res, parser); |
| break; |
| case PackageParser.TAG_FEATURE_GROUP: |
| success = parseFeatureGroup(parsingPackage, res, parser); |
| break; |
| case PackageParser.TAG_USES_SDK: |
| parseResult = parseUsesSdk(parseInput, parsingPackage, res, parser); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_SUPPORT_SCREENS: |
| success = parseSupportScreens(parsingPackage, res, parser); |
| break; |
| case PackageParser.TAG_PROTECTED_BROADCAST: |
| success = parseProtectedBroadcast(parsingPackage, res, parser); |
| break; |
| case PackageParser.TAG_INSTRUMENTATION: |
| parseResult = parseInstrumentation(parseInput, parsingPackage, res, |
| parser); |
| success = parseResult.isSuccess(); |
| break; |
| case PackageParser.TAG_ORIGINAL_PACKAGE: |
| success = parseOriginalPackage(parsingPackage, res, parser); |
| break; |
| case PackageParser.TAG_ADOPT_PERMISSIONS: |
| success = parseAdoptPermissions(parsingPackage, res, parser); |
| break; |
| case PackageParser.TAG_USES_GL_TEXTURE: |
| case PackageParser.TAG_COMPATIBLE_SCREENS: |
| case PackageParser.TAG_SUPPORTS_INPUT: |
| case PackageParser.TAG_EAT_COMMENT: |
| // Just skip this tag |
| XmlUtils.skipCurrentTag(parser); |
| success = true; |
| break; |
| case PackageParser.TAG_RESTRICT_UPDATE: |
| success = parseRestrictUpdateHash(flags, parsingPackage, res, parser); |
| break; |
| case PackageParser.TAG_QUERIES: |
| parseResult = parseQueries(parseInput, parsingPackage, res, parser); |
| success = parseResult.isSuccess(); |
| break; |
| default: |
| parseResult = parseUnknownTag(parseInput, parsingPackage, parser); |
| success = parseResult.isSuccess(); |
| break; |
| } |
| |
| if (parseResult != null && !parseResult.isSuccess()) { |
| return parseResult; |
| } |
| |
| if (!success) { |
| return parseResult; |
| } |
| } |
| |
| if (!foundApp && ArrayUtils.size(parsingPackage.getInstrumentations()) == 0) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY, |
| "<manifest> does not contain an <application> or <instrumentation>" |
| ); |
| } |
| |
| if (!ComponentParseUtils.ParsedFeature.isCombinationValid(parsingPackage.getFeatures())) { |
| return parseInput.error( |
| INSTALL_PARSE_FAILED_BAD_MANIFEST, |
| "Combination <feature> tags are not valid" |
| ); |
| } |
| |
| convertNewPermissions(parsingPackage); |
| |
| convertSplitPermissions(parsingPackage); |
| |
| // 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 (parsingPackage.usesCompatibilityMode()) { |
| adjustPackageToBeUnresizeableAndUnpipable(parsingPackage); |
| } |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static ParseResult parseUnknownTag( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| if (PackageParser.RIGID_PARSER) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Bad element under <manifest>: " + parser.getName() |
| ); |
| } else { |
| Slog.w(TAG, "Unknown element under <manifest>: " + parser.getName() |
| + " at " + parsingPackage.getBaseCodePath() + " " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| return parseInput.success(parsingPackage); |
| } |
| } |
| |
| private static ParseResult parseSharedUser( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| TypedArray manifestArray |
| ) { |
| String str = manifestArray.getNonConfigurationString( |
| R.styleable.AndroidManifest_sharedUserId, 0); |
| if (TextUtils.isEmpty(str)) { |
| return parseInput.success(parsingPackage); |
| } |
| |
| String nameError = validateName(str, true, true); |
| if (nameError != null && !"android".equals(parsingPackage.getPackageName())) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, |
| "<manifest> specifies bad sharedUserId name \"" + str + "\": " |
| + nameError |
| ); |
| } |
| |
| int sharedUserLabel = manifestArray.getResourceId( |
| R.styleable.AndroidManifest_sharedUserLabel, 0); |
| parsingPackage.setSharedUserId(str.intern()) |
| .setSharedUserLabel(sharedUserLabel); |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static void parseManifestAttributes( |
| TypedArray manifestArray, |
| ParsingPackage parsingPackage, |
| int flags |
| ) { |
| int installLocation = manifestArray.getInteger(R.styleable.AndroidManifest_installLocation, |
| PackageParser.PARSE_DEFAULT_INSTALL_LOCATION); |
| |
| final int targetSandboxVersion = manifestArray.getInteger( |
| R.styleable.AndroidManifest_targetSandboxVersion, |
| PackageParser.PARSE_DEFAULT_TARGET_SANDBOX); |
| |
| parsingPackage.setInstallLocation(installLocation) |
| .setTargetSandboxVersion(targetSandboxVersion); |
| |
| /* Set the global "on SD card" flag */ |
| parsingPackage.setExternalStorage((flags & PackageParser.PARSE_EXTERNAL_STORAGE) != 0); |
| |
| parsingPackage.setIsolatedSplitLoading(manifestArray.getBoolean( |
| R.styleable.AndroidManifest_isolatedSplits, false)); |
| } |
| |
| private static ParseResult parseKeySets( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws XmlPullParserException, IOException { |
| // we've encountered the 'key-sets' tag |
| // all the keys and keysets that we want must be defined here |
| // so we're going to iterate over the parser and pull out the things we want |
| int outerDepth = parser.getDepth(); |
| int currentKeySetDepth = -1; |
| int type; |
| String currentKeySet = null; |
| ArrayMap<String, PublicKey> publicKeys = new ArrayMap<>(); |
| ArraySet<String> upgradeKeySets = new ArraySet<>(); |
| ArrayMap<String, ArraySet<String>> definedKeySets = |
| new ArrayMap<>(); |
| ArraySet<String> improperKeySets = new ArraySet<>(); |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT |
| && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { |
| if (type == XmlPullParser.END_TAG) { |
| if (parser.getDepth() == currentKeySetDepth) { |
| currentKeySet = null; |
| currentKeySetDepth = -1; |
| } |
| continue; |
| } |
| String tagName = parser.getName(); |
| if (tagName.equals("key-set")) { |
| if (currentKeySet != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Improperly nested 'key-set' tag at " + parser.getPositionDescription() |
| ); |
| } |
| final TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestKeySet); |
| final String keysetName = sa.getNonResourceString( |
| R.styleable.AndroidManifestKeySet_name); |
| definedKeySets.put(keysetName, new ArraySet<>()); |
| currentKeySet = keysetName; |
| currentKeySetDepth = parser.getDepth(); |
| sa.recycle(); |
| } else if (tagName.equals("public-key")) { |
| if (currentKeySet == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Improperly nested 'key-set' tag at " + parser.getPositionDescription() |
| ); |
| } |
| final TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestPublicKey); |
| final String publicKeyName = sa.getNonResourceString( |
| R.styleable.AndroidManifestPublicKey_name); |
| final String encodedKey = sa.getNonResourceString( |
| R.styleable.AndroidManifestPublicKey_value); |
| if (encodedKey == null && publicKeys.get(publicKeyName) == null) { |
| sa.recycle(); |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "'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."); |
| sa.recycle(); |
| 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 { |
| sa.recycle(); |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Value of 'public-key' " + publicKeyName |
| + " conflicts with previously defined value at " |
| + parser.getPositionDescription() |
| ); |
| } |
| } |
| definedKeySets.get(currentKeySet).add(publicKeyName); |
| sa.recycle(); |
| XmlUtils.skipCurrentTag(parser); |
| } else if (tagName.equals("upgrade-key-set")) { |
| final TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestUpgradeKeySet); |
| String name = sa.getNonResourceString( |
| R.styleable.AndroidManifestUpgradeKeySet_name); |
| upgradeKeySets.add(name); |
| sa.recycle(); |
| XmlUtils.skipCurrentTag(parser); |
| } else if (PackageParser.RIGID_PARSER) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Bad element under <key-sets>: " + parser.getName() |
| + " at " + parsingPackage.getBaseCodePath() + " " |
| + parser.getPositionDescription() |
| ); |
| } else { |
| Slog.w(TAG, "Unknown element under <key-sets>: " + parser.getName() |
| + " at " + parsingPackage.getBaseCodePath() + " " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } |
| } |
| String packageName = parsingPackage.getPackageName(); |
| Set<String> publicKeyNames = publicKeys.keySet(); |
| if (publicKeyNames.removeAll(definedKeySets.keySet())) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Package" + packageName + " AndroidManifest.xml " |
| + "'key-set' and 'public-key' names must be distinct." |
| ); |
| } |
| |
| for (ArrayMap.Entry<String, ArraySet<String>> e : definedKeySets.entrySet()) { |
| final String keySetName = e.getKey(); |
| if (e.getValue().size() == 0) { |
| Slog.w(TAG, "Package" + packageName + " AndroidManifest.xml " |
| + "'key-set' " + keySetName + " has no valid associated 'public-key'." |
| + " Not including in package's defined key-sets."); |
| continue; |
| } else if (improperKeySets.contains(keySetName)) { |
| Slog.w(TAG, "Package" + packageName + " AndroidManifest.xml " |
| + "'key-set' " + keySetName + " contained improper 'public-key'" |
| + " tags. Not including in package's defined key-sets."); |
| continue; |
| } |
| |
| for (String s : e.getValue()) { |
| parsingPackage.addKeySet(keySetName, publicKeys.get(s)); |
| } |
| } |
| if (parsingPackage.getKeySetMapping().keySet().containsAll(upgradeKeySets)) { |
| parsingPackage.setUpgradeKeySets(upgradeKeySets); |
| } else { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Package" + packageName + " AndroidManifest.xml " |
| + "does not define all 'upgrade-key-set's ." |
| ); |
| } |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| public static boolean parsePackageItemInfo(String packageName, PackageItemInfo outInfo, |
| String[] outError, String tag, TypedArray sa, boolean nameRequired, |
| int nameRes, int labelRes, int iconRes, int roundIconRes, int logoRes, int bannerRes) { |
| // This case can only happen in unit tests where we sometimes need to create fakes |
| // of various package parser data structures. |
| if (sa == null) { |
| outError[0] = tag + " does not contain any attributes"; |
| return false; |
| } |
| |
| String name = sa.getNonConfigurationString(nameRes, 0); |
| if (name == null) { |
| if (nameRequired) { |
| outError[0] = tag + " does not specify android:name"; |
| return false; |
| } |
| } else { |
| String outInfoName = buildClassName(packageName, name); |
| if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(outInfoName)) { |
| outError[0] = tag + " invalid android:name"; |
| return false; |
| } |
| outInfo.name = outInfoName; |
| if (outInfoName == null) { |
| return false; |
| } |
| } |
| |
| int roundIconVal = PackageParser.sUseRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0; |
| if (roundIconVal != 0) { |
| outInfo.icon = roundIconVal; |
| outInfo.nonLocalizedLabel = null; |
| } else { |
| int iconVal = sa.getResourceId(iconRes, 0); |
| if (iconVal != 0) { |
| outInfo.icon = iconVal; |
| outInfo.nonLocalizedLabel = null; |
| } |
| } |
| |
| int logoVal = sa.getResourceId(logoRes, 0); |
| if (logoVal != 0) { |
| outInfo.logo = logoVal; |
| } |
| |
| int bannerVal = sa.getResourceId(bannerRes, 0); |
| if (bannerVal != 0) { |
| outInfo.banner = bannerVal; |
| } |
| |
| TypedValue v = sa.peekValue(labelRes); |
| if (v != null && (outInfo.labelRes = v.resourceId) == 0) { |
| outInfo.nonLocalizedLabel = v.coerceToString(); |
| } |
| |
| outInfo.packageName = packageName; |
| |
| return true; |
| } |
| |
| private static ParseResult parsePackageItemInfo( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| String tag, |
| TypedArray sa, |
| boolean nameRequired, |
| int nameRes, |
| int labelRes, |
| int iconRes, |
| int roundIconRes, |
| int logoRes, |
| int bannerRes |
| ) { |
| // 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 parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| tag + " does not contain any attributes" |
| ); |
| } |
| |
| String name = sa.getNonConfigurationString(nameRes, 0); |
| if (name == null) { |
| if (nameRequired) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| tag + " does not specify android:name" |
| ); |
| } |
| } else { |
| String packageName = parsingPackage.getPackageName(); |
| String outInfoName = buildClassName(packageName, name); |
| if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(outInfoName)) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| tag + " invalid android:name" |
| ); |
| } else if (outInfoName == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Empty class name in package " + packageName |
| ); |
| } |
| |
| parsingPackage.setName(outInfoName); |
| } |
| |
| int roundIconVal = PackageParser.sUseRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0; |
| if (roundIconVal != 0) { |
| parsingPackage.setIcon(roundIconVal) |
| .setNonLocalizedLabel(null); |
| } else { |
| int iconVal = sa.getResourceId(iconRes, 0); |
| if (iconVal != 0) { |
| parsingPackage.setIcon(iconVal) |
| .setNonLocalizedLabel(null); |
| } |
| } |
| |
| int logoVal = sa.getResourceId(logoRes, 0); |
| if (logoVal != 0) { |
| parsingPackage.setLogo(logoVal); |
| } |
| |
| int bannerVal = sa.getResourceId(bannerRes, 0); |
| if (bannerVal != 0) { |
| parsingPackage.setBanner(bannerVal); |
| } |
| |
| TypedValue v = sa.peekValue(labelRes); |
| if (v != null) { |
| parsingPackage.setLabelRes(v.resourceId); |
| if (v.resourceId == 0) { |
| parsingPackage.setNonLocalizedLabel(v.coerceToString()); |
| } |
| } |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static ParseResult parseFeature( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| // TODO(b/135203078): Remove, replace with ParseResult |
| String[] outError = new String[1]; |
| |
| ComponentParseUtils.ParsedFeature parsedFeature = |
| ComponentParseUtils.parseFeature(res, parser, outError); |
| |
| if (parsedFeature == null || outError[0] != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addFeature(parsedFeature); |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| |
| private static ParseResult parsePermissionGroup( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws XmlPullParserException, IOException { |
| // TODO(b/135203078): Remove, replace with ParseResult |
| String[] outError = new String[1]; |
| |
| ComponentParseUtils.ParsedPermissionGroup parsedPermissionGroup = |
| ComponentParseUtils.parsePermissionGroup(parsingPackage, |
| res, parser, outError); |
| |
| if (parsedPermissionGroup == null || outError[0] != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addPermissionGroup(parsedPermissionGroup); |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static ParseResult parsePermission( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws XmlPullParserException, IOException { |
| // TODO(b/135203078): Remove, replace with ParseResult |
| String[] outError = new String[1]; |
| |
| ComponentParseUtils.ParsedPermission parsedPermission = |
| ComponentParseUtils.parsePermission(parsingPackage, |
| res, parser, outError); |
| |
| if (parsedPermission == null || outError[0] != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addPermission(parsedPermission); |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static ParseResult parsePermissionTree( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws XmlPullParserException, IOException { |
| // TODO(b/135203078): Remove, replace with ParseResult |
| String[] outError = new String[1]; |
| |
| ComponentParseUtils.ParsedPermission parsedPermission = |
| ComponentParseUtils.parsePermissionTree(parsingPackage, |
| res, parser, outError); |
| |
| if (parsedPermission == null || outError[0] != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addPermission(parsedPermission); |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static ParseResult parseUsesPermission( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser, |
| PackageParser.Callback callback |
| ) |
| throws XmlPullParserException, IOException { |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestUsesPermission); |
| |
| // Note: don't allow this value to be a reference to a resource |
| // that may change. |
| String name = sa.getNonResourceString( |
| R.styleable.AndroidManifestUsesPermission_name); |
| |
| int maxSdkVersion = 0; |
| TypedValue val = sa.peekValue( |
| R.styleable.AndroidManifestUsesPermission_maxSdkVersion); |
| if (val != null) { |
| if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) { |
| maxSdkVersion = val.data; |
| } |
| } |
| |
| final String requiredFeature = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestUsesPermission_requiredFeature, 0); |
| |
| final String requiredNotfeature = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestUsesPermission_requiredNotFeature, |
| 0); |
| |
| sa.recycle(); |
| |
| XmlUtils.skipCurrentTag(parser); |
| |
| // Can only succeed from here on out |
| ParseResult success = parseInput.success(parsingPackage); |
| |
| if (name == null) { |
| return success; |
| } |
| |
| if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) { |
| return success; |
| } |
| |
| // Only allow requesting this permission if the platform supports the given feature. |
| if (requiredFeature != null && callback != null && !callback.hasFeature(requiredFeature)) { |
| return success; |
| } |
| |
| // Only allow requesting this permission if the platform doesn't support the given feature. |
| if (requiredNotfeature != null && callback != null |
| && callback.hasFeature(requiredNotfeature)) { |
| return success; |
| } |
| |
| if (!parsingPackage.getRequestedPermissions().contains(name)) { |
| parsingPackage.addRequestedPermission(name.intern()); |
| } else { |
| Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: " |
| + name + " in package: " + parsingPackage.getPackageName() + " at: " |
| + parser.getPositionDescription()); |
| } |
| |
| return success; |
| } |
| |
| private static boolean parseUsesConfiguration( |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| ConfigurationInfo cPref = new ConfigurationInfo(); |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestUsesConfiguration); |
| 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; |
| } |
| sa.recycle(); |
| parsingPackage.addConfigPreference(cPref); |
| |
| XmlUtils.skipCurrentTag(parser); |
| return true; |
| } |
| |
| private static boolean parseUsesFeature( |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| FeatureInfo fi = parseFeatureInfo(res, parser); |
| parsingPackage.addReqFeature(fi); |
| |
| if (fi.name == null) { |
| ConfigurationInfo cPref = new ConfigurationInfo(); |
| cPref.reqGlEsVersion = fi.reqGlEsVersion; |
| parsingPackage.addConfigPreference(cPref); |
| } |
| |
| XmlUtils.skipCurrentTag(parser); |
| return true; |
| } |
| |
| private static FeatureInfo parseFeatureInfo(Resources res, AttributeSet attrs) { |
| FeatureInfo fi = new FeatureInfo(); |
| TypedArray sa = res.obtainAttributes(attrs, |
| R.styleable.AndroidManifestUsesFeature); |
| // 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; |
| } |
| sa.recycle(); |
| return fi; |
| } |
| |
| private static boolean parseFeatureGroup( |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| FeatureGroupInfo group = new FeatureGroupInfo(); |
| ArrayList<FeatureInfo> features = null; |
| final int innerDepth = parser.getDepth(); |
| int type; |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT |
| && (type != XmlPullParser.END_TAG |
| || parser.getDepth() > innerDepth)) { |
| if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { |
| continue; |
| } |
| |
| final String innerTagName = parser.getName(); |
| if (innerTagName.equals("uses-feature")) { |
| FeatureInfo featureInfo = parseFeatureInfo(res, parser); |
| // FeatureGroups are stricter and mandate that |
| // any <uses-feature> declared are mandatory. |
| featureInfo.flags |= FeatureInfo.FLAG_REQUIRED; |
| features = ArrayUtils.add(features, featureInfo); |
| } else { |
| Slog.w(TAG, |
| "Unknown element under <feature-group>: " + innerTagName + |
| " at " + parsingPackage.getBaseCodePath() + " " + |
| parser.getPositionDescription()); |
| } |
| XmlUtils.skipCurrentTag(parser); |
| } |
| |
| if (features != null) { |
| group.features = new FeatureInfo[features.size()]; |
| group.features = features.toArray(group.features); |
| } |
| |
| parsingPackage.addFeatureGroup(group); |
| return true; |
| } |
| |
| private static ParseResult parseUsesSdk( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| if (PackageParser.SDK_VERSION > 0) { |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestUsesSdk); |
| |
| int minVers = 1; |
| String minCode = null; |
| int targetVers = 0; |
| String targetCode = null; |
| |
| TypedValue val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_minSdkVersion); |
| if (val != null) { |
| if (val.type == TypedValue.TYPE_STRING && val.string != null) { |
| minCode = val.string.toString(); |
| } else { |
| // If it's not a string, it's an integer. |
| minVers = val.data; |
| } |
| } |
| |
| val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_targetSdkVersion); |
| if (val != null) { |
| if (val.type == TypedValue.TYPE_STRING && val.string != null) { |
| targetCode = val.string.toString(); |
| if (minCode == null) { |
| minCode = targetCode; |
| } |
| } else { |
| // If it's not a string, it's an integer. |
| targetVers = val.data; |
| } |
| } else { |
| targetVers = minVers; |
| targetCode = minCode; |
| } |
| |
| sa.recycle(); |
| |
| // TODO(b/135203078): Remove, replace with ParseResult |
| String[] outError = new String[1]; |
| final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, |
| minCode, |
| PackageParser.SDK_VERSION, PackageParser.SDK_CODENAMES, outError); |
| if (minSdkVersion < 0) { |
| return parseInput.error( |
| PackageManager.INSTALL_FAILED_OLDER_SDK |
| ); |
| } |
| |
| final int targetSdkVersion = PackageParser.computeTargetSdkVersion( |
| targetVers, |
| targetCode, PackageParser.SDK_CODENAMES, outError); |
| if (targetSdkVersion < 0) { |
| return parseInput.error( |
| PackageManager.INSTALL_FAILED_OLDER_SDK |
| ); |
| } |
| |
| int type; |
| final int innerDepth = parser.getDepth(); |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT |
| && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { |
| if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { |
| continue; |
| } |
| if (parser.getName().equals("extension-sdk")) { |
| final ParseResult result = |
| parseExtensionSdk(parseInput, parsingPackage, res, parser); |
| if (!result.isSuccess()) { |
| return result; |
| } |
| } else { |
| Slog.w(TAG, "Unknown element under <uses-sdk>: " + parser.getName() |
| + " at " + parsingPackage.getBaseCodePath() + " " |
| + parser.getPositionDescription()); |
| } |
| XmlUtils.skipCurrentTag(parser); |
| } |
| |
| parsingPackage.setMinSdkVersion(minSdkVersion) |
| .setTargetSdkVersion(targetSdkVersion); |
| } |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static ParseResult parseExtensionSdk( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| TypedArray sa = res.obtainAttributes(parser, |
| com.android.internal.R.styleable.AndroidManifestExtensionSdk); |
| int sdkVersion = sa.getInt( |
| com.android.internal.R.styleable.AndroidManifestExtensionSdk_sdkVersion, -1); |
| int minVersion = sa.getInt( |
| com.android.internal.R.styleable.AndroidManifestExtensionSdk_minExtensionVersion, |
| -1); |
| sa.recycle(); |
| |
| if (sdkVersion < 0) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "<extension-sdk> must specify an sdkVersion >= 0"); |
| } |
| if (minVersion < 0) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "<extension-sdk> must specify minExtensionVersion >= 0"); |
| } |
| |
| try { |
| int version = SdkExtensions.getExtensionVersion(sdkVersion); |
| if (version < minVersion) { |
| return parseInput.error( |
| PackageManager.INSTALL_FAILED_OLDER_SDK, |
| "Package requires " + sdkVersion + " extension version " + minVersion |
| + " which exceeds device version " + version); |
| } |
| } catch (RuntimeException e) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Specified sdkVersion " + sdkVersion + " is not valid"); |
| } |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static boolean parseRestrictUpdateHash( |
| int flags, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| if ((flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) { |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestRestrictUpdate); |
| final String hash = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestRestrictUpdate_hash, |
| 0); |
| sa.recycle(); |
| |
| 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)); |
| } |
| parsingPackage.setRestrictUpdateHash(hashBytes); |
| } else { |
| parsingPackage.setRestrictUpdateHash(null); |
| } |
| } |
| |
| XmlUtils.skipCurrentTag(parser); |
| return true; |
| } |
| |
| private static ParseResult parseQueries( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| |
| 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; |
| } |
| if (parser.getName().equals("intent")) { |
| String[] outError = new String[1]; |
| ComponentParseUtils.ParsedQueriesIntentInfo intentInfo = |
| ComponentParseUtils.parsedParsedQueriesIntentInfo( |
| parsingPackage, res, parser, outError |
| ); |
| if (intentInfo == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| Uri data = null; |
| String dataType = null; |
| String host = ""; |
| 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)) { |
| outError[0] = "intent tags must contain either an action or data."; |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| if (numActions > 1) { |
| outError[0] = "intent tag may have at most one action."; |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| if (numTypes > 1) { |
| outError[0] = "intent tag may have at most one data type."; |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| if (numSchemes > 1) { |
| outError[0] = "intent tag may have at most one data scheme."; |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| if (numHosts > 1) { |
| outError[0] = "intent tag may have at most one data host."; |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| 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) |
| .build(); |
| } |
| if (numTypes == 1) { |
| dataType = intentInfo.getDataType(0); |
| } |
| intent.setDataAndType(data, dataType); |
| if (numActions == 1) { |
| intent.setAction(intentInfo.getAction(0)); |
| } |
| parsingPackage.addQueriesIntent(intent); |
| } else if (parser.getName().equals("package")) { |
| final TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestQueriesPackage); |
| final String packageName = |
| sa.getString(R.styleable.AndroidManifestQueriesPackage_name); |
| if (TextUtils.isEmpty(packageName)) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Package name is missing from package tag." |
| ); |
| } |
| parsingPackage.addQueriesPackage(packageName.intern()); |
| } |
| } |
| return parseInput.success(parsingPackage); |
| } |
| |
| /** |
| * Parse the {@code application} XML tree at the current parse location in a |
| * <em>base APK</em> manifest. |
| * <p> |
| * When adding new features, carefully consider if they should also be |
| * supported by split APKs. |
| * |
| * @hide |
| */ |
| public static ParseResult parseBaseApplication( |
| ParseInput parseInput, |
| String[] separateProcesses, |
| PackageParser.Callback callback, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser, |
| int flags |
| ) throws XmlPullParserException, IOException { |
| final String pkgName = parsingPackage.getPackageName(); |
| |
| // TODO(b/135203078): Remove, replace with ParseResult |
| String[] outError = new String[1]; |
| TypedArray sa = null; |
| |
| try { |
| sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestApplication); |
| |
| |
| parsingPackage |
| .setIconRes( |
| sa.getResourceId(R.styleable.AndroidManifestApplication_icon, 0)) |
| .setRoundIconRes( |
| sa.getResourceId(R.styleable.AndroidManifestApplication_roundIcon, 0)); |
| |
| ParseResult result = parsePackageItemInfo( |
| parseInput, |
| parsingPackage, |
| "<application>", |
| sa, false /*nameRequired*/, |
| R.styleable.AndroidManifestApplication_name, |
| R.styleable.AndroidManifestApplication_label, |
| R.styleable.AndroidManifestApplication_icon, |
| R.styleable.AndroidManifestApplication_roundIcon, |
| R.styleable.AndroidManifestApplication_logo, |
| R.styleable.AndroidManifestApplication_banner |
| ); |
| if (!result.isSuccess()) { |
| return result; |
| } |
| |
| String name = parsingPackage.getName(); |
| if (name != null) { |
| parsingPackage.setClassName(name); |
| } |
| |
| String manageSpaceActivity = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestApplication_manageSpaceActivity, |
| Configuration.NATIVE_CONFIG_VERSION); |
| if (manageSpaceActivity != null) { |
| String manageSpaceActivityName = buildClassName(pkgName, manageSpaceActivity); |
| |
| if (manageSpaceActivityName == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Empty class name in package " + pkgName |
| ); |
| } |
| |
| parsingPackage.setManageSpaceActivityName(manageSpaceActivityName); |
| } |
| |
| boolean allowBackup = sa.getBoolean( |
| R.styleable.AndroidManifestApplication_allowBackup, true); |
| parsingPackage.setAllowBackup(allowBackup); |
| |
| if (allowBackup) { |
| // backupAgent, killAfterRestore, fullBackupContent, backupInForeground, |
| // and restoreAnyVersion are only relevant if backup is possible for the |
| // given application. |
| String backupAgent = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestApplication_backupAgent, |
| Configuration.NATIVE_CONFIG_VERSION); |
| if (backupAgent != null) { |
| String backupAgentName = buildClassName(pkgName, backupAgent); |
| if (backupAgentName == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Empty class name in package " + pkgName |
| ); |
| } |
| |
| if (PackageParser.DEBUG_BACKUP) { |
| Slog.v(TAG, "android:backupAgent = " + backupAgentName |
| + " from " + pkgName + "+" + backupAgent); |
| } |
| |
| parsingPackage.setBackupAgentName(backupAgentName); |
| |
| parsingPackage.setKillAfterRestore(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_killAfterRestore, true)); |
| |
| parsingPackage.setRestoreAnyVersion(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_restoreAnyVersion, false)); |
| |
| parsingPackage.setFullBackupOnly(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_fullBackupOnly, false)); |
| |
| parsingPackage.setBackupInForeground(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_backupInForeground, |
| false)); |
| } |
| |
| TypedValue v = sa.peekValue( |
| R.styleable.AndroidManifestApplication_fullBackupContent); |
| int fullBackupContent = 0; |
| |
| if (v != null) { |
| fullBackupContent = v.resourceId; |
| |
| if (v.resourceId == 0) { |
| if (PackageParser.DEBUG_BACKUP) { |
| Slog.v(TAG, "fullBackupContent specified as boolean=" + |
| (v.data == 0 ? "false" : "true")); |
| } |
| // "false" => -1, "true" => 0 |
| fullBackupContent = v.data == 0 ? -1 : 0; |
| } |
| |
| parsingPackage.setFullBackupContent(fullBackupContent); |
| } |
| if (PackageParser.DEBUG_BACKUP) { |
| Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName); |
| } |
| } |
| |
| parsingPackage |
| .setTheme( |
| sa.getResourceId(R.styleable.AndroidManifestApplication_theme, 0)) |
| .setDescriptionRes( |
| sa.getResourceId(R.styleable.AndroidManifestApplication_description, |
| 0)); |
| |
| 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); |
| parsingPackage.setPersistent(requiredFeature == null |
| || callback.hasFeature(requiredFeature)); |
| } |
| |
| boolean requiredForAllUsers = sa.getBoolean( |
| R.styleable.AndroidManifestApplication_requiredForAllUsers, |
| false); |
| parsingPackage.setRequiredForAllUsers(requiredForAllUsers); |
| |
| String restrictedAccountType = sa.getString(R.styleable |
| .AndroidManifestApplication_restrictedAccountType); |
| if (restrictedAccountType != null && restrictedAccountType.length() > 0) { |
| parsingPackage.setRestrictedAccountType(restrictedAccountType); |
| } |
| |
| String requiredAccountType = sa.getString(R.styleable |
| .AndroidManifestApplication_requiredAccountType); |
| if (requiredAccountType != null && requiredAccountType.length() > 0) { |
| parsingPackage.setRequiredAccountType(requiredAccountType); |
| } |
| |
| parsingPackage.setForceQueryable( |
| sa.getBoolean(R.styleable.AndroidManifestApplication_forceQueryable, false) |
| ); |
| |
| boolean debuggable = sa.getBoolean( |
| R.styleable.AndroidManifestApplication_debuggable, |
| false |
| ); |
| |
| parsingPackage.setDebuggable(debuggable); |
| |
| if (debuggable) { |
| // Debuggable implies profileable |
| parsingPackage.setProfileableByShell(true); |
| } |
| |
| parsingPackage.setVmSafeMode(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_vmSafeMode, false)); |
| |
| boolean baseHardwareAccelerated = sa.getBoolean( |
| R.styleable.AndroidManifestApplication_hardwareAccelerated, |
| parsingPackage.getTargetSdkVersion() |
| >= Build.VERSION_CODES.ICE_CREAM_SANDWICH); |
| parsingPackage.setBaseHardwareAccelerated(baseHardwareAccelerated); |
| |
| parsingPackage.setHasCode(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_hasCode, true)); |
| |
| parsingPackage.setAllowTaskReparenting(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_allowTaskReparenting, false)); |
| |
| parsingPackage.setAllowClearUserData(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_allowClearUserData, true)); |
| |
| parsingPackage.setTestOnly(sa.getBoolean( |
| com.android.internal.R.styleable.AndroidManifestApplication_testOnly, |
| false)); |
| |
| parsingPackage.setLargeHeap(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_largeHeap, false)); |
| |
| parsingPackage.setUsesCleartextTraffic(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_usesCleartextTraffic, |
| parsingPackage.getTargetSdkVersion() < Build.VERSION_CODES.P)); |
| |
| parsingPackage.setSupportsRtl(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_supportsRtl, |
| false /* default is no RTL support*/)); |
| |
| parsingPackage.setMultiArch(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_multiArch, false)); |
| |
| parsingPackage.setExtractNativeLibs(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_extractNativeLibs, true)); |
| |
| parsingPackage.setUseEmbeddedDex(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_useEmbeddedDex, false)); |
| |
| parsingPackage.setDefaultToDeviceProtectedStorage(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_defaultToDeviceProtectedStorage, |
| false)); |
| |
| parsingPackage.setDirectBootAware(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_directBootAware, false)); |
| |
| if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) { |
| parsingPackage.setActivitiesResizeModeResizeable(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_resizeableActivity, true)); |
| } else { |
| parsingPackage.setActivitiesResizeModeResizeableViaSdkVersion( |
| parsingPackage.getTargetSdkVersion() >= Build.VERSION_CODES.N); |
| } |
| |
| parsingPackage.setAllowClearUserDataOnFailedRestore(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_allowClearUserDataOnFailedRestore, |
| true)); |
| |
| |
| parsingPackage.setAllowAudioPlaybackCapture(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, |
| parsingPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q)); |
| |
| parsingPackage.setRequestLegacyExternalStorage(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, |
| parsingPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q)); |
| |
| parsingPackage |
| .setMaxAspectRatio( |
| sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0)) |
| .setMinAspectRatio( |
| sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0)) |
| .setNetworkSecurityConfigRes(sa.getResourceId( |
| R.styleable.AndroidManifestApplication_networkSecurityConfig, 0)) |
| .setCategory(sa.getInt(R.styleable.AndroidManifestApplication_appCategory, |
| ApplicationInfo.CATEGORY_UNDEFINED)); |
| |
| String str; |
| str = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestApplication_permission, 0); |
| parsingPackage.setPermission((str != null && str.length() > 0) ? str.intern() : null); |
| |
| if (parsingPackage.getTargetSdkVersion() >= Build.VERSION_CODES.FROYO) { |
| str = 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. |
| str = sa.getNonResourceString( |
| R.styleable.AndroidManifestApplication_taskAffinity); |
| } |
| String packageName = parsingPackage.getPackageName(); |
| String taskAffinity = PackageParser.buildTaskAffinityName(packageName, |
| packageName, |
| str, outError); |
| |
| if (outError[0] != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.setTaskAffinity(taskAffinity); |
| String factory = sa.getNonResourceString( |
| R.styleable.AndroidManifestApplication_appComponentFactory); |
| if (factory != null) { |
| String appComponentFactory = buildClassName(packageName, factory); |
| if (appComponentFactory == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Empty class name in package " + pkgName |
| ); |
| } |
| |
| parsingPackage.setAppComponentFactory(appComponentFactory); |
| } |
| |
| parsingPackage.setUsesNonSdkApi(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_usesNonSdkApi, false)); |
| |
| parsingPackage.setHasFragileUserData(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_hasFragileUserData, false)); |
| |
| CharSequence pname; |
| if (parsingPackage.getTargetSdkVersion() >= 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); |
| } |
| String processName = PackageParser.buildProcessName(packageName, null, pname, flags, |
| separateProcesses, outError); |
| |
| if (outError[0] != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage |
| .setProcessName(processName) |
| .setEnabled( |
| sa.getBoolean(R.styleable.AndroidManifestApplication_enabled, |
| true)); |
| |
| parsingPackage.setCrossProfile( |
| sa.getBoolean(R.styleable.AndroidManifestApplication_crossProfile, false)); |
| |
| parsingPackage.setIsGame(sa.getBoolean( |
| R.styleable.AndroidManifestApplication_isGame, false)); |
| |
| boolean cantSaveState = sa.getBoolean( |
| R.styleable.AndroidManifestApplication_cantSaveState, false); |
| parsingPackage.setCantSaveState(cantSaveState); |
| if (cantSaveState) { |
| // 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(packageName)) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "cantSaveState applications can not use custom processes" |
| ); |
| } |
| } |
| |
| String classLoaderName = sa.getString( |
| R.styleable.AndroidManifestApplication_classLoader); |
| parsingPackage |
| .setUiOptions(sa.getInt(R.styleable.AndroidManifestApplication_uiOptions, 0)) |
| .setClassLoaderName(classLoaderName) |
| .setZygotePreloadName( |
| sa.getString(R.styleable.AndroidManifestApplication_zygotePreloadName)); |
| |
| if (classLoaderName != null |
| && !ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Invalid class loader name: " + classLoaderName |
| ); |
| } |
| } finally { |
| if (sa != null) { |
| sa.recycle(); |
| } |
| } |
| |
| final int innerDepth = parser.getDepth(); |
| int type; |
| boolean hasActivityOrder = false; |
| boolean hasReceiverOrder = false; |
| boolean hasServiceOrder = false; |
| |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT |
| && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { |
| if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { |
| continue; |
| } |
| |
| String tagName = parser.getName(); |
| switch (tagName) { |
| case "activity": |
| ComponentParseUtils.ParsedActivity activity = |
| ComponentParseUtils.parseActivity(separateProcesses, |
| parsingPackage, |
| res, parser, flags, |
| outError, false, |
| parsingPackage.isBaseHardwareAccelerated()); |
| if (activity == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| hasActivityOrder |= (activity.order != 0); |
| parsingPackage.addActivity(activity); |
| break; |
| case "receiver": |
| activity = ComponentParseUtils.parseActivity(separateProcesses, |
| parsingPackage, |
| res, parser, |
| flags, outError, |
| true, false); |
| if (activity == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| hasReceiverOrder |= (activity.order != 0); |
| parsingPackage.addReceiver(activity); |
| break; |
| case "service": |
| ComponentParseUtils.ParsedService s = ComponentParseUtils.parseService( |
| separateProcesses, |
| parsingPackage, |
| res, parser, flags, |
| outError); |
| if (s == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| hasServiceOrder |= (s.order != 0); |
| parsingPackage.addService(s); |
| break; |
| case "provider": |
| ComponentParseUtils.ParsedProvider p = ComponentParseUtils.parseProvider( |
| separateProcesses, |
| parsingPackage, |
| res, parser, flags, |
| outError |
| ); |
| if (p == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addProvider(p); |
| break; |
| case "activity-alias": |
| activity = ComponentParseUtils.parseActivityAlias( |
| parsingPackage, |
| res, |
| parser, |
| outError |
| ); |
| if (activity == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| hasActivityOrder |= (activity.order != 0); |
| parsingPackage.addActivity(activity); |
| break; |
| 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) |
| Bundle appMetaData = parseMetaData(parsingPackage, res, parser, |
| parsingPackage.getAppMetaData(), |
| outError); |
| if (appMetaData == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.setAppMetaData(appMetaData); |
| break; |
| case "static-library": |
| sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestStaticLibrary); |
| |
| // 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); |
| |
| sa.recycle(); |
| |
| // Since the app canot run without a static lib - fail if malformed |
| if (lname == null || version < 0) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Bad static-library declaration name: " + lname |
| + " version: " + version |
| ); |
| } |
| |
| if (parsingPackage.getSharedUserId() != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, |
| "sharedUserId not allowed in static shared library" |
| ); |
| } |
| |
| if (parsingPackage.getStaticSharedLibName() != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Multiple static-shared libs for package " + pkgName |
| ); |
| } |
| |
| parsingPackage.setStaticSharedLibName(lname.intern()); |
| if (version >= 0) { |
| parsingPackage.setStaticSharedLibVersion( |
| PackageInfo.composeLongVersionCode(versionMajor, version)); |
| } else { |
| parsingPackage.setStaticSharedLibVersion(version); |
| } |
| parsingPackage.setStaticSharedLibrary(true); |
| |
| XmlUtils.skipCurrentTag(parser); |
| |
| break; |
| case "library": |
| sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestLibrary); |
| |
| // Note: don't allow this value to be a reference to a resource |
| // that may change. |
| lname = sa.getNonResourceString( |
| R.styleable.AndroidManifestLibrary_name); |
| |
| sa.recycle(); |
| |
| if (lname != null) { |
| lname = lname.intern(); |
| if (!ArrayUtils.contains(parsingPackage.getLibraryNames(), lname)) { |
| parsingPackage.addLibraryName(lname); |
| } |
| } |
| |
| XmlUtils.skipCurrentTag(parser); |
| |
| break; |
| case "uses-static-library": |
| ParseResult parseResult = parseUsesStaticLibrary(parseInput, parsingPackage, |
| res, parser); |
| if (!parseResult.isSuccess()) { |
| return parseResult; |
| } |
| break; |
| case "uses-library": |
| sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestUsesLibrary); |
| |
| // Note: don't allow this value to be a reference to a resource |
| // that may change. |
| lname = sa.getNonResourceString( |
| R.styleable.AndroidManifestUsesLibrary_name); |
| boolean req = sa.getBoolean( |
| R.styleable.AndroidManifestUsesLibrary_required, |
| true); |
| |
| sa.recycle(); |
| |
| if (lname != null) { |
| lname = lname.intern(); |
| if (req) { |
| parsingPackage.addUsesLibrary(lname); |
| } else { |
| parsingPackage.addUsesOptionalLibrary(lname); |
| } |
| } |
| |
| XmlUtils.skipCurrentTag(parser); |
| |
| break; |
| case "processes": |
| ArrayMap<String, ComponentParseUtils.ParsedProcess> processes = |
| ComponentParseUtils.parseProcesses(separateProcesses, |
| parsingPackage, |
| res, parser, flags, |
| outError); |
| if (processes == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.setProcesses(processes); |
| break; |
| case "uses-package": |
| // Dependencies for app installers; we don't currently try to |
| // enforce this. |
| XmlUtils.skipCurrentTag(parser); |
| break; |
| case "profileable": |
| sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestProfileable); |
| if (sa.getBoolean( |
| R.styleable.AndroidManifestProfileable_shell, false)) { |
| parsingPackage.setProfileableByShell(true); |
| } |
| XmlUtils.skipCurrentTag(parser); |
| break; |
| default: |
| if (!PackageParser.RIGID_PARSER) { |
| Slog.w(TAG, "Unknown element under <application>: " + tagName |
| + " at " + parsingPackage.getBaseCodePath() + " " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } else { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Bad element under <application>: " + tagName |
| ); |
| } |
| } |
| } |
| |
| if (TextUtils.isEmpty(parsingPackage.getStaticSharedLibName())) { |
| // Add a hidden app detail activity to normal apps which forwards user to App Details |
| // page. |
| ComponentParseUtils.ParsedActivity a = generateAppDetailsHiddenActivity( |
| parsingPackage, |
| outError |
| ); |
| // Ignore errors here |
| parsingPackage.addActivity(a); |
| } |
| |
| if (hasActivityOrder) { |
| parsingPackage.sortActivities(); |
| } |
| if (hasReceiverOrder) { |
| parsingPackage.sortReceivers(); |
| } |
| if (hasServiceOrder) { |
| parsingPackage.sortServices(); |
| } |
| // Must be ran 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(parsingPackage); |
| setMinAspectRatio(parsingPackage, callback); |
| |
| parsingPackage.setHasDomainUrls(hasDomainURLs(parsingPackage)); |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static ParseResult parseUsesStaticLibrary( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws XmlPullParserException, IOException { |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestUsesStaticLibrary); |
| |
| // 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(com.android.internal.R.styleable |
| .AndroidManifestUsesStaticLibrary_certDigest); |
| sa.recycle(); |
| |
| // Since an APK providing a static shared lib can only provide the lib - fail if malformed |
| if (lname == null || version < 0 || certSha256Digest == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Bad uses-static-library declaration name: " + lname + " version: " |
| + version + " certDigest" + certSha256Digest |
| ); |
| } |
| |
| // Can depend only on one version of the same library |
| List<String> usesStaticLibraries = parsingPackage.getUsesStaticLibraries(); |
| if (usesStaticLibraries != null && usesStaticLibraries.contains(lname)) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "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 (parsingPackage.getTargetSdkVersion() >= Build.VERSION_CODES.O_MR1) { |
| // TODO(b/135203078): Remove, replace with ParseResult |
| String[] outError = new String[1]; |
| additionalCertSha256Digests = parseAdditionalCertificates(res, parser, outError); |
| if (additionalCertSha256Digests == null || outError[0] != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| } else { |
| XmlUtils.skipCurrentTag(parser); |
| } |
| |
| final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1]; |
| certSha256Digests[0] = certSha256Digest; |
| System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests, |
| 1, additionalCertSha256Digests.length); |
| |
| parsingPackage.addUsesStaticLibrary(lname) |
| .addUsesStaticLibraryVersion(version) |
| .addUsesStaticLibraryCertDigests(certSha256Digests); |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static String[] parseAdditionalCertificates( |
| Resources resources, |
| XmlResourceParser parser, |
| String[] outError |
| ) throws XmlPullParserException, IOException { |
| String[] certSha256Digests = EmptyArray.STRING; |
| |
| 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 String nodeName = parser.getName(); |
| if (nodeName.equals("additional-certificate")) { |
| final TypedArray sa = resources.obtainAttributes(parser, com.android.internal. |
| R.styleable.AndroidManifestAdditionalCertificate); |
| String certSha256Digest = sa.getNonResourceString(com.android.internal. |
| R.styleable.AndroidManifestAdditionalCertificate_certDigest); |
| sa.recycle(); |
| |
| if (TextUtils.isEmpty(certSha256Digest)) { |
| outError[0] = "Bad additional-certificate declaration with empty" |
| + " certDigest:" + certSha256Digest; |
| XmlUtils.skipCurrentTag(parser); |
| sa.recycle(); |
| return null; |
| } |
| |
| // 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); |
| } else { |
| XmlUtils.skipCurrentTag(parser); |
| } |
| } |
| |
| return 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. |
| * |
| * @hide |
| */ |
| @NonNull |
| private static ComponentParseUtils.ParsedActivity generateAppDetailsHiddenActivity( |
| ParsingPackage parsingPackage, |
| String[] outError |
| ) { |
| String packageName = parsingPackage.getPackageName(); |
| String processName = parsingPackage.getProcessName(); |
| boolean hardwareAccelerated = parsingPackage.isBaseHardwareAccelerated(); |
| int uiOptions = parsingPackage.getUiOptions(); |
| |
| // Build custom App Details activity info instead of parsing it from xml |
| ComponentParseUtils.ParsedActivity activity = new ComponentParseUtils.ParsedActivity(); |
| activity.setPackageName(packageName); |
| |
| activity.theme = android.R.style.Theme_NoDisplay; |
| activity.exported = true; |
| activity.className = PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME; |
| activity.setProcessName(processName, processName); |
| activity.uiOptions = uiOptions; |
| activity.taskAffinity = PackageParser.buildTaskAffinityName(packageName, |
| packageName, |
| ":app_details", outError); |
| activity.enabled = true; |
| activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; |
| activity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE; |
| activity.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic(); |
| activity.configChanges = PackageParser.getActivityConfigChanges(0, 0); |
| activity.softInputMode = 0; |
| activity.persistableMode = ActivityInfo.PERSIST_NEVER; |
| activity.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; |
| activity.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; |
| activity.lockTaskLaunchMode = 0; |
| activity.directBootAware = false; |
| activity.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; |
| activity.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; |
| activity.preferMinimalPostProcessing = ActivityInfo.MINIMAL_POST_PROCESSING_DEFAULT; |
| if (hardwareAccelerated) { |
| activity.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED; |
| } |
| |
| return activity; |
| } |
| |
| /** |
| * Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI |
| */ |
| private static boolean hasDomainURLs( |
| ParsingPackage parsingPackage) { |
| final List<ComponentParseUtils.ParsedActivity> activities = parsingPackage.getActivities(); |
| final int countActivities = activities.size(); |
| for (int n = 0; n < countActivities; n++) { |
| ComponentParseUtils.ParsedActivity activity = activities.get(n); |
| List<ComponentParseUtils.ParsedActivityIntentInfo> filters = activity.intents; |
| if (filters == null) continue; |
| final int countFilters = filters.size(); |
| for (int m = 0; m < countFilters; m++) { |
| ComponentParseUtils.ParsedActivityIntentInfo aii = filters.get(m); |
| 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 parsingPackage) { |
| // 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 = parsingPackage.getTargetSdkVersion() < O |
| ? PackageParser.DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0; |
| |
| float packageMaxAspectRatio = parsingPackage.getMaxAspectRatio(); |
| if (packageMaxAspectRatio != 0) { |
| // Use the application max aspect ration as default if set. |
| maxAspectRatio = packageMaxAspectRatio; |
| } else { |
| Bundle appMetaData = parsingPackage.getAppMetaData(); |
| if (appMetaData != null && appMetaData.containsKey( |
| PackageParser.METADATA_MAX_ASPECT_RATIO)) { |
| maxAspectRatio = appMetaData.getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO, |
| maxAspectRatio); |
| } |
| } |
| |
| if (parsingPackage.getActivities() != null) { |
| for (ComponentParseUtils.ParsedActivity activity : parsingPackage.getActivities()) { |
| // If the max aspect ratio for the activity has already been set, skip. |
| if (activity.hasMaxAspectRatio()) { |
| 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.metaData != null |
| ? activity.metaData.getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO, |
| maxAspectRatio) |
| : maxAspectRatio; |
| |
| activity.setMaxAspectRatio(activity.resizeMode, activityAspectRatio); |
| } |
| } |
| } |
| |
| /** |
| * Sets the min aspect ratio of every child activity that doesn't already have an aspect |
| * ratio set. |
| */ |
| private static void setMinAspectRatio( |
| ParsingPackage parsingPackage, |
| PackageParser.Callback callback |
| ) { |
| final float minAspectRatio; |
| float packageMinAspectRatio = parsingPackage.getMinAspectRatio(); |
| if (packageMinAspectRatio != 0) { |
| // Use the application max aspect ration as default if set. |
| minAspectRatio = packageMinAspectRatio; |
| } else { |
| // Default to (1.33) 4:3 aspect ratio for pre-Q apps and unset for Q and greater. |
| // NOTE: 4:3 was the min aspect ratio Android devices can support pre-Q per the CDD, |
| // except for watches which always supported 1:1. |
| minAspectRatio = parsingPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q |
| ? 0 |
| : (callback != null && callback.hasFeature(FEATURE_WATCH)) |
| ? PackageParser.DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH |
| : PackageParser.DEFAULT_PRE_Q_MIN_ASPECT_RATIO; |
| } |
| |
| if (parsingPackage.getActivities() != null) { |
| for (ComponentParseUtils.ParsedActivity activity : parsingPackage.getActivities()) { |
| if (activity.hasMinAspectRatio()) { |
| continue; |
| } |
| activity.setMinAspectRatio(activity.resizeMode, minAspectRatio); |
| } |
| } |
| } |
| |
| private static ParseResult parseOverlay( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| |
| TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestResourceOverlay); |
| String target = sa.getString( |
| R.styleable.AndroidManifestResourceOverlay_targetPackage); |
| String targetName = sa.getString( |
| R.styleable.AndroidManifestResourceOverlay_targetName); |
| String category = sa.getString( |
| R.styleable.AndroidManifestResourceOverlay_category); |
| int priority = sa.getInt(R.styleable.AndroidManifestResourceOverlay_priority, |
| 0); |
| boolean isStatic = sa.getBoolean( |
| R.styleable.AndroidManifestResourceOverlay_isStatic, false); |
| |
| if (target == null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "<overlay> does not specify a target package" |
| ); |
| } |
| |
| if (priority < 0 || priority > 9999) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "<overlay> priority must be between 0 and 9999" |
| ); |
| } |
| |
| // check to see if overlay should be excluded based on system property condition |
| String propName = sa.getString( |
| R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName); |
| String propValue = sa.getString( |
| R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue); |
| if (!checkOverlayRequiredSystemProperty(propName, propValue)) { |
| Slog.i(TAG, "Skipping target and overlay pair " + target + " and " |
| + parsingPackage.getBaseCodePath() |
| + ": overlay ignored due to required system property: " |
| + propName + " with value: " + propValue); |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| "Skipping target and overlay pair " + target + " and " |
| + parsingPackage.getBaseCodePath() |
| + ": overlay ignored due to required system property: " |
| + propName + " with value: " + propValue |
| ); |
| } |
| |
| parsingPackage |
| .setIsOverlay(true) |
| .setOverlayTarget(target) |
| .setOverlayTargetName(targetName) |
| .setOverlayCategory(category) |
| .setOverlayPriority(priority) |
| .setOverlayIsStatic(isStatic); |
| |
| sa.recycle(); |
| |
| XmlUtils.skipCurrentTag(parser); |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static boolean parseProtectedBroadcast( |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestProtectedBroadcast); |
| |
| // Note: don't allow this value to be a reference to a resource |
| // that may change. |
| String name = sa.getNonResourceString(R.styleable.AndroidManifestProtectedBroadcast_name); |
| |
| sa.recycle(); |
| |
| if (name != null) { |
| parsingPackage.addProtectedBroadcast(name); |
| } |
| |
| XmlUtils.skipCurrentTag(parser); |
| return true; |
| } |
| |
| private static boolean parseSupportScreens( |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestSupportsScreens); |
| |
| int requiresSmallestWidthDp = sa.getInteger( |
| R.styleable.AndroidManifestSupportsScreens_requiresSmallestWidthDp, |
| 0); |
| int compatibleWidthLimitDp = sa.getInteger( |
| R.styleable.AndroidManifestSupportsScreens_compatibleWidthLimitDp, |
| 0); |
| int largestWidthLimitDp = sa.getInteger( |
| R.styleable.AndroidManifestSupportsScreens_largestWidthLimitDp, |
| 0); |
| |
| // This is a trick to get a boolean and still able to detect |
| // if a value was actually set. |
| parsingPackage |
| .setSupportsSmallScreens( |
| sa.getInteger(R.styleable.AndroidManifestSupportsScreens_smallScreens, 1)) |
| .setSupportsNormalScreens( |
| sa.getInteger(R.styleable.AndroidManifestSupportsScreens_normalScreens, 1)) |
| .setSupportsLargeScreens( |
| sa.getInteger(R.styleable.AndroidManifestSupportsScreens_largeScreens, 1)) |
| .setSupportsXLargeScreens( |
| sa.getInteger(R.styleable.AndroidManifestSupportsScreens_xlargeScreens, 1)) |
| .setResizeable( |
| sa.getInteger(R.styleable.AndroidManifestSupportsScreens_resizeable, 1)) |
| .setAnyDensity( |
| sa.getInteger(R.styleable.AndroidManifestSupportsScreens_anyDensity, 1)) |
| .setRequiresSmallestWidthDp(requiresSmallestWidthDp) |
| .setCompatibleWidthLimitDp(compatibleWidthLimitDp) |
| .setLargestWidthLimitDp(largestWidthLimitDp); |
| |
| sa.recycle(); |
| |
| XmlUtils.skipCurrentTag(parser); |
| return true; |
| } |
| |
| private static ParseResult parseInstrumentation( |
| ParseInput parseInput, |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws XmlPullParserException, IOException { |
| // TODO(b/135203078): Remove, replace with ParseResult |
| String[] outError = new String[1]; |
| |
| ComponentParseUtils.ParsedInstrumentation parsedInstrumentation = |
| ComponentParseUtils.parseInstrumentation(parsingPackage, |
| res, parser, outError); |
| |
| if (parsedInstrumentation == null || outError[0] != null) { |
| return parseInput.error( |
| PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, |
| outError[0] |
| ); |
| } |
| |
| parsingPackage.addInstrumentation(parsedInstrumentation); |
| |
| return parseInput.success(parsingPackage); |
| } |
| |
| private static boolean parseOriginalPackage( |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestOriginalPackage); |
| |
| String orig = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestOriginalPackage_name, |
| 0); |
| if (!parsingPackage.getPackageName().equals(orig)) { |
| if (parsingPackage.getOriginalPackages() == null) { |
| parsingPackage.setRealPackage(parsingPackage.getPackageName()); |
| } |
| parsingPackage.addOriginalPackage(orig); |
| } |
| |
| sa.recycle(); |
| |
| XmlUtils.skipCurrentTag(parser); |
| return true; |
| } |
| |
| private static boolean parseAdoptPermissions( |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser |
| ) throws IOException, XmlPullParserException { |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestOriginalPackage); |
| |
| String name = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestOriginalPackage_name, |
| 0); |
| |
| sa.recycle(); |
| |
| if (name != null) { |
| parsingPackage.addAdoptPermission(name); |
| } |
| |
| XmlUtils.skipCurrentTag(parser); |
| return true; |
| } |
| |
| private static void convertNewPermissions( |
| ParsingPackage packageToParse) { |
| 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 (packageToParse.getTargetSdkVersion() >= npi.sdkVersion) { |
| break; |
| } |
| if (!packageToParse.getRequestedPermissions().contains(npi.name)) { |
| if (newPermsMsg == null) { |
| newPermsMsg = new StringBuilder(128); |
| newPermsMsg.append(packageToParse.getPackageName()); |
| newPermsMsg.append(": compat added "); |
| } else { |
| newPermsMsg.append(' '); |
| } |
| newPermsMsg.append(npi.name); |
| packageToParse.addRequestedPermission(npi.name); |
| packageToParse.addImplicitPermission(npi.name); |
| } |
| } |
| if (newPermsMsg != null) { |
| Slog.i(TAG, newPermsMsg.toString()); |
| } |
| } |
| |
| private static void convertSplitPermissions(ParsingPackage packageToParse) { |
| List<SplitPermissionInfoParcelable> splitPermissions; |
| |
| try { |
| splitPermissions = ActivityThread.getPermissionManager().getSplitPermissions(); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| |
| final int listSize = splitPermissions.size(); |
| for (int is = 0; is < listSize; is++) { |
| final SplitPermissionInfoParcelable spi = splitPermissions.get(is); |
| List<String> requestedPermissions = packageToParse.getRequestedPermissions(); |
| if (packageToParse.getTargetSdkVersion() >= spi.getTargetSdk() |
| || !requestedPermissions.contains(spi.getSplitPermission())) { |
| continue; |
| } |
| final List<String> newPerms = spi.getNewPermissions(); |
| for (int in = 0; in < newPerms.size(); in++) { |
| final String perm = newPerms.get(in); |
| if (!requestedPermissions.contains(perm)) { |
| packageToParse.addRequestedPermission(perm); |
| packageToParse.addImplicitPermission(perm); |
| } |
| } |
| } |
| } |
| |
| private static boolean checkOverlayRequiredSystemProperty(String propName, String propValue) { |
| if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) { |
| if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) { |
| // malformed condition - incomplete |
| Slog.w(TAG, "Disabling overlay - incomplete property :'" + propName |
| + "=" + propValue + "' - require both requiredSystemPropertyName" |
| + " AND requiredSystemPropertyValue to be specified."); |
| return false; |
| } |
| // no valid condition set - so no exclusion criteria, overlay will be included. |
| return true; |
| } |
| |
| // check property value - make sure it is both set and equal to expected value |
| final String currValue = SystemProperties.get(propName); |
| return (currValue != null && currValue.equals(propValue)); |
| } |
| |
| /** |
| * This is a pre-density application which will get scaled - instead of being pixel perfect. |
| * This type of application is not resizable. |
| * |
| * @param parsingPackage The package which needs to be marked as unresizable. |
| */ |
| private static void adjustPackageToBeUnresizeableAndUnpipable( |
| ParsingPackage parsingPackage) { |
| if (parsingPackage.getActivities() != null) { |
| for (ComponentParseUtils.ParsedActivity a : parsingPackage.getActivities()) { |
| a.resizeMode = RESIZE_MODE_UNRESIZEABLE; |
| a.flags &= ~FLAG_SUPPORTS_PICTURE_IN_PICTURE; |
| } |
| } |
| } |
| |
| private 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 && !FileUtils.isValidExtFilename(name)) { |
| return "Invalid filename"; |
| } |
| return hasSep || !requireSeparator |
| ? null : "must have at least one '.' separator"; |
| } |
| |
| public static Bundle parseMetaData( |
| ParsingPackage parsingPackage, |
| Resources res, |
| XmlResourceParser parser, Bundle data, String[] outError) |
| throws XmlPullParserException, IOException { |
| |
| TypedArray sa = res.obtainAttributes(parser, |
| R.styleable.AndroidManifestMetaData); |
| |
| if (data == null) { |
| data = new Bundle(); |
| } |
| |
| String name = sa.getNonConfigurationString( |
| R.styleable.AndroidManifestMetaData_name, 0); |
| if (name == null) { |
| outError[0] = "<meta-data> requires an android:name attribute"; |
| sa.recycle(); |
| return null; |
| } |
| |
| name = name.intern(); |
| |
| TypedValue v = sa.peekValue( |
| R.styleable.AndroidManifestMetaData_resource); |
| if (v != null && v.resourceId != 0) { |
| //Slog.i(TAG, "Meta data ref " + name + ": " + v); |
| data.putInt(name, v.resourceId); |
| } else { |
| v = sa.peekValue( |
| R.styleable.AndroidManifestMetaData_value); |
| //Slog.i(TAG, "Meta data " + name + ": " + v); |
| if (v != null) { |
| if (v.type == TypedValue.TYPE_STRING) { |
| CharSequence cs = v.coerceToString(); |
| data.putString(name, cs != null ? cs.toString() : null); |
| } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) { |
| data.putBoolean(name, v.data != 0); |
| } else if (v.type >= TypedValue.TYPE_FIRST_INT |
| && v.type <= TypedValue.TYPE_LAST_INT) { |
| data.putInt(name, v.data); |
| } else if (v.type == TypedValue.TYPE_FLOAT) { |
| data.putFloat(name, v.getFloat()); |
| } else { |
| if (!PackageParser.RIGID_PARSER) { |
| Slog.w(TAG, |
| "<meta-data> only supports string, integer, float, color, " |
| + "boolean, and resource reference types: " |
| + parser.getName() + " at " |
| + parsingPackage.getBaseCodePath() + " " |
| + parser.getPositionDescription()); |
| } else { |
| outError[0] = |
| "<meta-data> only supports string, integer, float, color, " |
| + "boolean, and resource reference types"; |
| data = null; |
| } |
| } |
| } else { |
| outError[0] = "<meta-data> requires an android:value or android:resource attribute"; |
| data = null; |
| } |
| } |
| |
| sa.recycle(); |
| |
| XmlUtils.skipCurrentTag(parser); |
| |
| return data; |
| } |
| |
| /** |
| * Collect certificates from all the APKs described in the given package, |
| * populating {@link AndroidPackageWrite#setSigningDetails(SigningDetails)}. Also asserts that |
| * all APK contents are signed correctly and consistently. |
| */ |
| public static void collectCertificates(AndroidPackage pkg, boolean skipVerify) |
| throws PackageParserException { |
| pkg.mutate().setSigningDetails(SigningDetails.UNKNOWN); |
| |
| Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); |
| try { |
| pkg.mutate().setSigningDetails(collectCertificates( |
| pkg.getBaseCodePath(), |
| skipVerify, |
| pkg.isStaticSharedLibrary(), |
| pkg.getSigningDetails() |
| )); |
| |
| String[] splitCodePaths = pkg.getSplitCodePaths(); |
| if (!ArrayUtils.isEmpty(splitCodePaths)) { |
| for (int i = 0; i < splitCodePaths.length; i++) { |
| pkg.mutate().setSigningDetails(collectCertificates( |
| splitCodePaths[i], |
| skipVerify, |
| pkg.isStaticSharedLibrary(), |
| pkg.getSigningDetails() |
| )); |
| } |
| } |
| } finally { |
| Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); |
| } |
| } |
| |
| public static SigningDetails collectCertificates( |
| String baseCodePath, |
| boolean skipVerify, |
| boolean isStaticSharedLibrary, |
| @NonNull SigningDetails existingSigningDetails |
| ) throws PackageParserException { |
| int minSignatureScheme = SigningDetails.SignatureSchemeVersion.JAR; |
| if (isStaticSharedLibrary) { |
| // must use v2 signing scheme |
| minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2; |
| } |
| SigningDetails verified; |
| if (skipVerify) { |
| // systemDir APKs are already trusted, save time by not verifying |
| verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification( |
| baseCodePath, minSignatureScheme); |
| } else { |
| verified = ApkSignatureVerifier.verify(baseCodePath, minSignatureScheme); |
| } |
| |
| // Verify that entries are signed consistently with the first pkg |
| // we encountered. Note that for splits, certificates may have |
| // already been populated during an earlier parse of a base APK. |
| if (existingSigningDetails == SigningDetails.UNKNOWN) { |
| return verified; |
| } else { |
| if (!Signature.areExactMatch(existingSigningDetails.signatures, verified.signatures)) { |
| throw new PackageParserException( |
| INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, |
| baseCodePath + " has mismatched certificates"); |
| } |
| |
| return existingSigningDetails; |
| } |
| } |
| |
| @Nullable |
| public static String buildClassName(String pkg, CharSequence clsSeq) { |
| if (clsSeq == null || clsSeq.length() <= 0) { |
| return null; |
| } |
| String cls = clsSeq.toString(); |
| char c = cls.charAt(0); |
| if (c == '.') { |
| return pkg + cls; |
| } |
| if (cls.indexOf('.') < 0) { |
| StringBuilder b = new StringBuilder(pkg); |
| b.append('.'); |
| b.append(cls); |
| return b.toString(); |
| } |
| return cls; |
| } |
| |
| public interface ParseInput { |
| ParseResult success(ParsingPackage result); |
| |
| ParseResult error(int parseError); |
| |
| ParseResult error(int parseError, String errorMessage); |
| } |
| |
| public static class ParseResult implements ParseInput { |
| |
| private static final boolean DEBUG_FILL_STACK_TRACE = false; |
| |
| private ParsingPackage result; |
| |
| private int parseError; |
| private String errorMessage; |
| |
| public ParseInput reset() { |
| this.result = null; |
| this.parseError = PackageManager.INSTALL_SUCCEEDED; |
| this.errorMessage = null; |
| return this; |
| } |
| |
| @Override |
| public ParseResult success(ParsingPackage result) { |
| if (parseError != PackageManager.INSTALL_SUCCEEDED || errorMessage != null) { |
| throw new IllegalStateException("Cannot set to success after set to error"); |
| } |
| this.result = result; |
| return this; |
| } |
| |
| @Override |
| public ParseResult error(int parseError) { |
| return error(parseError, null); |
| } |
| |
| @Override |
| public ParseResult error(int parseError, String errorMessage) { |
| this.parseError = parseError; |
| this.errorMessage = errorMessage; |
| |
| if (DEBUG_FILL_STACK_TRACE) { |
| this.errorMessage += Arrays.toString(new Exception().getStackTrace()); |
| } |
| |
| return this; |
| } |
| |
| public ParsingPackage getResultAndNull() { |
| ParsingPackage result = this.result; |
| this.result = null; |
| return result; |
| } |
| |
| public boolean isSuccess() { |
| return parseError == PackageManager.INSTALL_SUCCEEDED; |
| } |
| |
| public int getParseError() { |
| return parseError; |
| } |
| |
| public String getErrorMessage() { |
| return errorMessage; |
| } |
| } |
| } |