/* * Copyright (C) 2007 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; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; 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.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.apex.ApexInfo; import android.app.ActivityTaskManager; import android.app.ActivityThread; import android.app.ResourcesManager; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageParserCacheHelper.ReadHelper; import android.content.pm.PackageParserCacheHelper.WriteHelper; 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.os.Build; import android.os.Bundle; import android.os.FileUtils; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; import android.permission.PermissionManager; import android.system.ErrnoException; import android.system.OsConstants; import android.system.StructStat; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; import android.util.Log; import android.util.PackageUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureVerifier; import android.view.Display; import android.view.Gravity; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.ClassLoaderFactory; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import com.android.server.SystemConfig; import libcore.io.IoUtils; import libcore.util.EmptyArray; import libcore.util.HexEncoding; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; /** * Parser for package files (APKs) on disk. This supports apps packaged either * as a single "monolithic" APK, or apps packaged as a "cluster" of multiple * APKs in a single directory. *

* Apps packaged as multiple APKs always consist of a single "base" APK (with a * {@code null} split name) and zero or more "split" APKs (with unique split * names). Any subset of those split APKs are a valid install, as long as the * following constraints are met: *

* * @hide */ public class PackageParser { private static final boolean DEBUG_JAR = false; private static final boolean DEBUG_PARSER = false; private static final boolean DEBUG_BACKUP = false; private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE; private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100; private static final String PROPERTY_CHILD_PACKAGES_ENABLED = "persist.sys.child_packages_enabled"; private static final boolean MULTI_PACKAGE_APK_ENABLED = Build.IS_DEBUGGABLE && SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false); private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f; private static final float DEFAULT_PRE_Q_MIN_ASPECT_RATIO = 1.333f; private static final float DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH = 1f; private static final int DEFAULT_MIN_SDK_VERSION = 1; private static final int DEFAULT_TARGET_SDK_VERSION = 0; // TODO: switch outError users to PackageParserException // TODO: refactor "codePath" to "apkPath" /** File name in an APK for the Android manifest. */ public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; /** Path prefix for apps on expanded storage */ private static final String MNT_EXPAND = "/mnt/expand/"; private static final String TAG_MANIFEST = "manifest"; private static final String TAG_APPLICATION = "application"; private static final String TAG_PACKAGE_VERIFIER = "package-verifier"; private static final String TAG_OVERLAY = "overlay"; private static final String TAG_KEY_SETS = "key-sets"; private static final String TAG_PERMISSION_GROUP = "permission-group"; private static final String TAG_PERMISSION = "permission"; private static final String TAG_PERMISSION_TREE = "permission-tree"; private static final String TAG_USES_PERMISSION = "uses-permission"; private static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m"; private static final String TAG_USES_PERMISSION_SDK_23 = "uses-permission-sdk-23"; private static final String TAG_USES_CONFIGURATION = "uses-configuration"; private static final String TAG_USES_FEATURE = "uses-feature"; private static final String TAG_FEATURE_GROUP = "feature-group"; private static final String TAG_USES_SDK = "uses-sdk"; private static final String TAG_SUPPORT_SCREENS = "supports-screens"; private static final String TAG_PROTECTED_BROADCAST = "protected-broadcast"; private static final String TAG_INSTRUMENTATION = "instrumentation"; private static final String TAG_ORIGINAL_PACKAGE = "original-package"; private static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions"; private static final String TAG_USES_GL_TEXTURE = "uses-gl-texture"; private static final String TAG_COMPATIBLE_SCREENS = "compatible-screens"; private static final String TAG_SUPPORTS_INPUT = "supports-input"; private static final String TAG_EAT_COMMENT = "eat-comment"; private static final String TAG_PACKAGE = "package"; private static final String TAG_RESTRICT_UPDATE = "restrict-update"; private static final String TAG_USES_SPLIT = "uses-split"; private static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect"; /** * Bit mask of all the valid bits that can be set in recreateOnConfigChanges. * @hide */ private static final int RECREATE_ON_CONFIG_CHANGES_MASK = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC; // These are the tags supported by child packages private static final Set CHILD_PACKAGE_TAGS = new ArraySet<>(); static { CHILD_PACKAGE_TAGS.add(TAG_APPLICATION); CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION); CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION_SDK_M); CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION_SDK_23); CHILD_PACKAGE_TAGS.add(TAG_USES_CONFIGURATION); CHILD_PACKAGE_TAGS.add(TAG_USES_FEATURE); CHILD_PACKAGE_TAGS.add(TAG_FEATURE_GROUP); CHILD_PACKAGE_TAGS.add(TAG_USES_SDK); CHILD_PACKAGE_TAGS.add(TAG_SUPPORT_SCREENS); CHILD_PACKAGE_TAGS.add(TAG_INSTRUMENTATION); CHILD_PACKAGE_TAGS.add(TAG_USES_GL_TEXTURE); CHILD_PACKAGE_TAGS.add(TAG_COMPATIBLE_SCREENS); CHILD_PACKAGE_TAGS.add(TAG_SUPPORTS_INPUT); CHILD_PACKAGE_TAGS.add(TAG_EAT_COMMENT); } private static final boolean LOG_UNSAFE_BROADCASTS = false; /** * Total number of packages that were read from the cache. We use it only for logging. */ public static final AtomicInteger sCachedPackageReadCount = new AtomicInteger(); // Set of broadcast actions that are safe for manifest receivers private static final Set SAFE_BROADCASTS = new ArraySet<>(); static { SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED); } /** @hide */ public static final String APK_FILE_EXTENSION = ".apk"; /** @hide */ public static class NewPermissionInfo { @UnsupportedAppUsage public final String name; @UnsupportedAppUsage public final int sdkVersion; public final int fileVersion; public NewPermissionInfo(String name, int sdkVersion, int fileVersion) { this.name = name; this.sdkVersion = sdkVersion; this.fileVersion = fileVersion; } } /** * List of new permissions that have been added since 1.0. * NOTE: These must be declared in SDK version order, with permissions * added to older SDKs appearing before those added to newer SDKs. * If sdkVersion is 0, then this is not a permission that we want to * automatically add to older apps, but we do want to allow it to be * granted during a platform update. * @hide */ @UnsupportedAppUsage public static final PackageParser.NewPermissionInfo NEW_PERMISSIONS[] = new PackageParser.NewPermissionInfo[] { new PackageParser.NewPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.os.Build.VERSION_CODES.DONUT, 0), new PackageParser.NewPermissionInfo(android.Manifest.permission.READ_PHONE_STATE, android.os.Build.VERSION_CODES.DONUT, 0) }; /** * @deprecated callers should move to explicitly passing around source path. */ @Deprecated private String mArchiveSourcePath; private String[] mSeparateProcesses; private boolean mOnlyCoreApps; private DisplayMetrics mMetrics; @UnsupportedAppUsage private Callback mCallback; private File mCacheDir; private static final int SDK_VERSION = Build.VERSION.SDK_INT; private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES; private int mParseError = PackageManager.INSTALL_SUCCEEDED; private static boolean sCompatibilityModeEnabled = true; private static boolean sUseRoundIcon = false; private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; private static final int PARSE_DEFAULT_TARGET_SANDBOX = 1; static class ParsePackageItemArgs { final Package owner; final String[] outError; final int nameRes; final int labelRes; final int iconRes; final int roundIconRes; final int logoRes; final int bannerRes; String tag; TypedArray sa; ParsePackageItemArgs(Package _owner, String[] _outError, int _nameRes, int _labelRes, int _iconRes, int _roundIconRes, int _logoRes, int _bannerRes) { owner = _owner; outError = _outError; nameRes = _nameRes; labelRes = _labelRes; iconRes = _iconRes; logoRes = _logoRes; bannerRes = _bannerRes; roundIconRes = _roundIconRes; } } /** @hide */ @VisibleForTesting public static class ParseComponentArgs extends ParsePackageItemArgs { final String[] sepProcesses; final int processRes; final int descriptionRes; final int enabledRes; int flags; public ParseComponentArgs(Package _owner, String[] _outError, int _nameRes, int _labelRes, int _iconRes, int _roundIconRes, int _logoRes, int _bannerRes, String[] _sepProcesses, int _processRes, int _descriptionRes, int _enabledRes) { super(_owner, _outError, _nameRes, _labelRes, _iconRes, _roundIconRes, _logoRes, _bannerRes); sepProcesses = _sepProcesses; processRes = _processRes; descriptionRes = _descriptionRes; enabledRes = _enabledRes; } } /** * Lightweight parsed details about a single package. */ public static class PackageLite { @UnsupportedAppUsage public final String packageName; public final int versionCode; public final int versionCodeMajor; @UnsupportedAppUsage public final int installLocation; public final VerifierInfo[] verifiers; /** Names of any split APKs, ordered by parsed splitName */ public final String[] splitNames; /** Names of any split APKs that are features. Ordered by splitName */ public final boolean[] isFeatureSplits; /** Dependencies of any split APKs, ordered by parsed splitName */ public final String[] usesSplitNames; public final String[] configForSplit; /** * Path where this package was found on disk. For monolithic packages * this is path to single base APK file; for cluster packages this is * path to the cluster directory. */ public final String codePath; /** Path of base APK */ public final String baseCodePath; /** Paths of any split APKs, ordered by parsed splitName */ public final String[] splitCodePaths; /** Revision code of base APK */ public final int baseRevisionCode; /** Revision codes of any split APKs, ordered by parsed splitName */ public final int[] splitRevisionCodes; public final boolean coreApp; public final boolean debuggable; public final boolean multiArch; public final boolean use32bitAbi; public final boolean extractNativeLibs; public final boolean isolatedSplits; public PackageLite(String codePath, ApkLite baseApk, String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames, String[] configForSplit, String[] splitCodePaths, int[] splitRevisionCodes) { this.packageName = baseApk.packageName; this.versionCode = baseApk.versionCode; this.versionCodeMajor = baseApk.versionCodeMajor; this.installLocation = baseApk.installLocation; this.verifiers = baseApk.verifiers; this.splitNames = splitNames; this.isFeatureSplits = isFeatureSplits; this.usesSplitNames = usesSplitNames; this.configForSplit = configForSplit; this.codePath = codePath; this.baseCodePath = baseApk.codePath; this.splitCodePaths = splitCodePaths; this.baseRevisionCode = baseApk.revisionCode; this.splitRevisionCodes = splitRevisionCodes; this.coreApp = baseApk.coreApp; this.debuggable = baseApk.debuggable; this.multiArch = baseApk.multiArch; this.use32bitAbi = baseApk.use32bitAbi; this.extractNativeLibs = baseApk.extractNativeLibs; this.isolatedSplits = baseApk.isolatedSplits; } public List getAllCodePaths() { ArrayList paths = new ArrayList<>(); paths.add(baseCodePath); if (!ArrayUtils.isEmpty(splitCodePaths)) { Collections.addAll(paths, splitCodePaths); } return paths; } } /** * Lightweight parsed details about a single APK file. */ public static class ApkLite { public final String codePath; public final String packageName; public final String splitName; public boolean isFeatureSplit; public final String configForSplit; public final String usesSplitName; public final int versionCode; public final int versionCodeMajor; public final int revisionCode; public final int installLocation; public final int minSdkVersion; public final int targetSdkVersion; public final VerifierInfo[] verifiers; public final SigningDetails signingDetails; public final boolean coreApp; public final boolean debuggable; public final boolean multiArch; public final boolean use32bitAbi; public final boolean extractNativeLibs; public final boolean isolatedSplits; public final boolean isSplitRequired; public final boolean useEmbeddedDex; public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit, String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode, int versionCodeMajor, int revisionCode, int installLocation, List verifiers, SigningDetails signingDetails, boolean coreApp, boolean debuggable, boolean multiArch, boolean use32bitAbi, boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits, int minSdkVersion, int targetSdkVersion) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; this.isFeatureSplit = isFeatureSplit; this.configForSplit = configForSplit; this.usesSplitName = usesSplitName; this.versionCode = versionCode; this.versionCodeMajor = versionCodeMajor; this.revisionCode = revisionCode; this.installLocation = installLocation; this.signingDetails = signingDetails; this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); this.coreApp = coreApp; this.debuggable = debuggable; this.multiArch = multiArch; this.use32bitAbi = use32bitAbi; this.useEmbeddedDex = useEmbeddedDex; this.extractNativeLibs = extractNativeLibs; this.isolatedSplits = isolatedSplits; this.isSplitRequired = isSplitRequired; this.minSdkVersion = minSdkVersion; this.targetSdkVersion = targetSdkVersion; } public long getLongVersionCode() { return PackageInfo.composeLongVersionCode(versionCodeMajor, versionCode); } } /** * Cached parse state for new components. * * Allows reuse of the same parse argument records to avoid GC pressure. Lifetime is carefully * scoped to the parsing of a single application element. */ private static class CachedComponentArgs { ParseComponentArgs mActivityArgs; ParseComponentArgs mActivityAliasArgs; ParseComponentArgs mServiceArgs; ParseComponentArgs mProviderArgs; } /** * Cached state for parsing instrumentation to avoid GC pressure. * * Must be manually reset to null for each new manifest. */ private ParsePackageItemArgs mParseInstrumentationArgs; /** If set to true, we will only allow package files that exactly match * the DTD. Otherwise, we try to get as much from the package as we * can without failing. This should normally be set to false, to * support extensions to the DTD in future versions. */ private static final boolean RIGID_PARSER = false; private static final String TAG = "PackageParser"; @UnsupportedAppUsage public PackageParser() { mMetrics = new DisplayMetrics(); mMetrics.setToDefaults(); } @UnsupportedAppUsage public void setSeparateProcesses(String[] procs) { mSeparateProcesses = procs; } /** * Flag indicating this parser should only consider apps with * {@code coreApp} manifest attribute to be valid apps. This is useful when * creating a minimalist boot environment. */ public void setOnlyCoreApps(boolean onlyCoreApps) { mOnlyCoreApps = onlyCoreApps; } public void setDisplayMetrics(DisplayMetrics metrics) { mMetrics = metrics; } /** * Sets the cache directory for this package parser. */ public void setCacheDir(File cacheDir) { mCacheDir = cacheDir; } /** * Callback interface for retrieving information that may be needed while parsing * a package. */ public interface Callback { boolean hasFeature(String feature); String[] getOverlayPaths(String targetPackageName, String targetPath); String[] getOverlayApks(String targetPackageName); } /** * Standard implementation of {@link Callback} on top of the public {@link PackageManager} * class. */ public static final class CallbackImpl implements Callback { private final PackageManager mPm; public CallbackImpl(PackageManager pm) { mPm = pm; } @Override public boolean hasFeature(String feature) { return mPm.hasSystemFeature(feature); } @Override public String[] getOverlayPaths(String targetPackageName, String targetPath) { return null; } @Override public String[] getOverlayApks(String targetPackageName) { return null; } } /** * Set the {@link Callback} that can be used while parsing. */ public void setCallback(Callback cb) { mCallback = cb; } public static final boolean isApkFile(File file) { return isApkPath(file.getName()); } public static boolean isApkPath(String path) { return path.endsWith(APK_FILE_EXTENSION); } /** * Generate and return the {@link PackageInfo} for a parsed package. * * @param p the parsed package. * @param flags indicating which optional information is included. */ @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, Set grantedPermissions, PackageUserState state) { return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, UserHandle.getCallingUserId()); } /** * Returns true if the package is installed and not hidden, or if the caller * explicitly wanted all uninstalled and hidden packages as well. * @param appInfo The applicationInfo of the app being checked. */ private static boolean checkUseInstalledOrHidden(int flags, PackageUserState state, ApplicationInfo appInfo) { // Returns false if the package is hidden system app until installed. if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0 && !state.installed && appInfo != null && appInfo.hiddenUntilInstalled) { return false; } // If available for the target user, or trying to match uninstalled packages and it's // a system app. return state.isAvailable(flags) || (appInfo != null && appInfo.isSystemApp() && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0 || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0)); } public static boolean isAvailable(PackageUserState state) { return checkUseInstalledOrHidden(0, state, null); } @UnsupportedAppUsage public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, Set grantedPermissions, PackageUserState state, int userId) { if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) { return null; } PackageInfo pi = new PackageInfo(); pi.packageName = p.packageName; pi.splitNames = p.splitNames; pi.versionCode = p.mVersionCode; pi.versionCodeMajor = p.mVersionCodeMajor; pi.baseRevisionCode = p.baseRevisionCode; pi.splitRevisionCodes = p.splitRevisionCodes; pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; pi.sharedUserLabel = p.mSharedUserLabel; pi.applicationInfo = generateApplicationInfo(p, flags, state, userId); pi.installLocation = p.installLocation; pi.isStub = p.isStub; pi.coreApp = p.coreApp; if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0 || (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { pi.requiredForAllUsers = p.mRequiredForAllUsers; } pi.restrictedAccountType = p.mRestrictedAccountType; pi.requiredAccountType = p.mRequiredAccountType; pi.overlayTarget = p.mOverlayTarget; pi.targetOverlayableName = p.mOverlayTargetName; pi.overlayCategory = p.mOverlayCategory; pi.overlayPriority = p.mOverlayPriority; pi.mOverlayIsStatic = p.mOverlayIsStatic; pi.compileSdkVersion = p.mCompileSdkVersion; pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; if ((flags&PackageManager.GET_GIDS) != 0) { pi.gids = gids; } if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) { int N = p.configPreferences != null ? p.configPreferences.size() : 0; if (N > 0) { pi.configPreferences = new ConfigurationInfo[N]; p.configPreferences.toArray(pi.configPreferences); } N = p.reqFeatures != null ? p.reqFeatures.size() : 0; if (N > 0) { pi.reqFeatures = new FeatureInfo[N]; p.reqFeatures.toArray(pi.reqFeatures); } N = p.featureGroups != null ? p.featureGroups.size() : 0; if (N > 0) { pi.featureGroups = new FeatureGroupInfo[N]; p.featureGroups.toArray(pi.featureGroups); } } if ((flags & PackageManager.GET_ACTIVITIES) != 0) { final int N = p.activities.size(); if (N > 0) { int num = 0; final ActivityInfo[] res = new ActivityInfo[N]; for (int i = 0; i < N; i++) { final Activity a = p.activities.get(i); if (state.isMatch(a.info, flags)) { if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) { continue; } res[num++] = generateActivityInfo(a, flags, state, userId); } } pi.activities = ArrayUtils.trimToSize(res, num); } } if ((flags & PackageManager.GET_RECEIVERS) != 0) { final int N = p.receivers.size(); if (N > 0) { int num = 0; final ActivityInfo[] res = new ActivityInfo[N]; for (int i = 0; i < N; i++) { final Activity a = p.receivers.get(i); if (state.isMatch(a.info, flags)) { res[num++] = generateActivityInfo(a, flags, state, userId); } } pi.receivers = ArrayUtils.trimToSize(res, num); } } if ((flags & PackageManager.GET_SERVICES) != 0) { final int N = p.services.size(); if (N > 0) { int num = 0; final ServiceInfo[] res = new ServiceInfo[N]; for (int i = 0; i < N; i++) { final Service s = p.services.get(i); if (state.isMatch(s.info, flags)) { res[num++] = generateServiceInfo(s, flags, state, userId); } } pi.services = ArrayUtils.trimToSize(res, num); } } if ((flags & PackageManager.GET_PROVIDERS) != 0) { final int N = p.providers.size(); if (N > 0) { int num = 0; final ProviderInfo[] res = new ProviderInfo[N]; for (int i = 0; i < N; i++) { final Provider pr = p.providers.get(i); if (state.isMatch(pr.info, flags)) { res[num++] = generateProviderInfo(pr, flags, state, userId); } } pi.providers = ArrayUtils.trimToSize(res, num); } } if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) { int N = p.instrumentation.size(); if (N > 0) { pi.instrumentation = new InstrumentationInfo[N]; for (int i=0; i 0) { pi.permissions = new PermissionInfo[N]; for (int i=0; i 0) { pi.requestedPermissions = new String[N]; pi.requestedPermissionsFlags = new int[N]; for (int i=0; i sSplitNameComparator = new SplitNameComparator(); /** * Used to sort a set of APKs based on their split names, always placing the * base APK (with {@code null} split name) first. */ private static class SplitNameComparator implements Comparator { @Override public int compare(String lhs, String rhs) { if (lhs == null) { return -1; } else if (rhs == null) { return 1; } else { return lhs.compareTo(rhs); } } } /** * Parse only lightweight details about the package at the given location. * Automatically detects if the package is a monolithic style (single APK * file) or cluster style (directory of APKs). *

* 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. * * @see PackageParser#parsePackage(File, int) */ @UnsupportedAppUsage public static PackageLite parsePackageLite(File packageFile, int flags) throws PackageParserException { if (packageFile.isDirectory()) { return parseClusterPackageLite(packageFile, flags); } else { return parseMonolithicPackageLite(packageFile, flags); } } private static PackageLite parseMonolithicPackageLite(File packageFile, int flags) throws PackageParserException { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); final ApkLite baseApk = parseApkLite(packageFile, flags); final String packagePath = packageFile.getAbsolutePath(); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); return new PackageLite(packagePath, baseApk, null, null, null, null, null, null); } static PackageLite parseClusterPackageLite(File packageDir, int flags) throws PackageParserException { final File[] files = packageDir.listFiles(); if (ArrayUtils.isEmpty(files)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "No packages found in split"); } String packageName = null; int versionCode = 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); final ArrayMap apks = new ArrayMap<>(); for (File file : files) { if (isApkFile(file)) { final ApkLite lite = parseApkLite(file, flags); // Assert that all package names and version codes are // consistent with the first one we encounter. if (packageName == null) { packageName = lite.packageName; versionCode = lite.versionCode; } else { if (!packageName.equals(lite.packageName)) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Inconsistent package " + lite.packageName + " in " + file + "; expected " + packageName); } if (versionCode != lite.versionCode) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Inconsistent version " + lite.versionCode + " in " + file + "; expected " + versionCode); } } // Assert that each split is defined only once if (apks.put(lite.splitName, lite) != null) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Split name " + lite.splitName + " defined more than once; most recent was " + file); } } } Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); final ApkLite baseApk = apks.remove(null); if (baseApk == null) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Missing base APK in " + packageDir); } // Always apply deterministic ordering based on splitName final int size = apks.size(); String[] splitNames = null; boolean[] isFeatureSplits = null; String[] usesSplitNames = null; String[] configForSplits = null; String[] splitCodePaths = null; int[] splitRevisionCodes = null; String[] splitClassLoaderNames = null; if (size > 0) { splitNames = new String[size]; isFeatureSplits = new boolean[size]; usesSplitNames = new String[size]; configForSplits = new String[size]; splitCodePaths = new String[size]; splitRevisionCodes = new int[size]; splitNames = apks.keySet().toArray(splitNames); Arrays.sort(splitNames, sSplitNameComparator); for (int i = 0; i < size; i++) { final ApkLite apk = apks.get(splitNames[i]); usesSplitNames[i] = apk.usesSplitName; isFeatureSplits[i] = apk.isFeatureSplit; configForSplits[i] = apk.configForSplit; splitCodePaths[i] = apk.codePath; splitRevisionCodes[i] = apk.revisionCode; } } final String codePath = packageDir.getAbsolutePath(); return new PackageLite(codePath, baseApk, splitNames, isFeatureSplits, usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes); } /** * Parse the package at the given location. Automatically detects if the * package is a monolithic style (single APK file) or cluster style * (directory of APKs). *

* This performs sanity checking on cluster style packages, such as * requiring identical package name and version codes, a single base APK, * and unique split names. *

* Note that this does not perform signature verification; that * must be done separately in {@link #collectCertificates(Package, int)}. * * 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 #parsePackageLite(File, int) */ @UnsupportedAppUsage public Package parsePackage(File packageFile, int flags, boolean useCaches) throws PackageParserException { Package parsed = useCaches ? getCachedResult(packageFile, flags) : null; if (parsed != null) { return parsed; } long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; if (packageFile.isDirectory()) { parsed = parseClusterPackage(packageFile, flags); } else { parsed = parseMonolithicPackage(packageFile, flags); } long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0; cacheResult(packageFile, flags, parsed); if (LOG_PARSE_TIMINGS) { parseTime = cacheTime - parseTime; cacheTime = SystemClock.uptimeMillis() - cacheTime; if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) { Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime + "ms, update_cache=" + cacheTime + " ms"); } } return parsed; } /** * Equivalent to {@link #parsePackage(File, int, boolean)} with {@code useCaches == false}. */ @UnsupportedAppUsage public Package parsePackage(File packageFile, int flags) throws PackageParserException { return parsePackage(packageFile, flags, false /* useCaches */); } /** * Returns the cache key for a specificied {@code packageFile} and {@code flags}. */ private String getCacheKey(File packageFile, int flags) { StringBuilder sb = new StringBuilder(packageFile.getName()); sb.append('-'); sb.append(flags); return sb.toString(); } @VisibleForTesting protected Package fromCacheEntry(byte[] bytes) { return fromCacheEntryStatic(bytes); } /** static version of {@link #fromCacheEntry} for unit tests. */ @VisibleForTesting public static Package fromCacheEntryStatic(byte[] bytes) { final Parcel p = Parcel.obtain(); p.unmarshall(bytes, 0, bytes.length); p.setDataPosition(0); final ReadHelper helper = new ReadHelper(p); helper.startAndInstall(); PackageParser.Package pkg = new PackageParser.Package(p); p.recycle(); sCachedPackageReadCount.incrementAndGet(); return pkg; } @VisibleForTesting protected byte[] toCacheEntry(Package pkg) { return toCacheEntryStatic(pkg); } /** static version of {@link #toCacheEntry} for unit tests. */ @VisibleForTesting public static byte[] toCacheEntryStatic(Package pkg) { final Parcel p = Parcel.obtain(); final WriteHelper helper = new WriteHelper(p); pkg.writeToParcel(p, 0 /* flags */); helper.finishAndUninstall(); byte[] serialized = p.marshall(); p.recycle(); return serialized; } /** * Given a {@code packageFile} and a {@code cacheFile} returns whether the * cache file is up to date based on the mod-time of both files. */ private static boolean isCacheUpToDate(File packageFile, File cacheFile) { try { // NOTE: We don't use the File.lastModified API because it has the very // non-ideal failure mode of returning 0 with no excepions thrown. // The nio2 Files API is a little better but is considerably more expensive. final StructStat pkg = android.system.Os.stat(packageFile.getAbsolutePath()); final StructStat cache = android.system.Os.stat(cacheFile.getAbsolutePath()); return pkg.st_mtime < cache.st_mtime; } catch (ErrnoException ee) { // The most common reason why stat fails is that a given cache file doesn't // exist. We ignore that here. It's easy to reason that it's safe to say the // cache isn't up to date if we see any sort of exception here. // // (1) Exception while stating the package file : This should never happen, // and if it does, we do a full package parse (which is likely to throw the // same exception). // (2) Exception while stating the cache file : If the file doesn't exist, the // cache is obviously out of date. If the file *does* exist, we can't read it. // We will attempt to delete and recreate it after parsing the package. if (ee.errno != OsConstants.ENOENT) { Slog.w("Error while stating package cache : ", ee); } return false; } } /** * Returns the cached parse result for {@code packageFile} for parse flags {@code flags}, * or {@code null} if no cached result exists. */ private Package getCachedResult(File packageFile, int flags) { if (mCacheDir == null) { return null; } final String cacheKey = getCacheKey(packageFile, flags); final File cacheFile = new File(mCacheDir, cacheKey); try { // If the cache is not up to date, return null. if (!isCacheUpToDate(packageFile, cacheFile)) { return null; } final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath()); Package p = fromCacheEntry(bytes); if (mCallback != null) { String[] overlayApks = mCallback.getOverlayApks(p.packageName); if (overlayApks != null && overlayApks.length > 0) { for (String overlayApk : overlayApks) { // If a static RRO is updated, return null. if (!isCacheUpToDate(new File(overlayApk), cacheFile)) { return null; } } } } return p; } catch (Throwable e) { Slog.w(TAG, "Error reading package cache: ", e); // If something went wrong while reading the cache entry, delete the cache file // so that we regenerate it the next time. cacheFile.delete(); return null; } } /** * Caches the parse result for {@code packageFile} with flags {@code flags}. */ private void cacheResult(File packageFile, int flags, Package parsed) { if (mCacheDir == null) { return; } try { final String cacheKey = getCacheKey(packageFile, flags); final File cacheFile = new File(mCacheDir, cacheKey); if (cacheFile.exists()) { if (!cacheFile.delete()) { Slog.e(TAG, "Unable to delete cache file: " + cacheFile); } } final byte[] cacheEntry = toCacheEntry(parsed); if (cacheEntry == null) { return; } try (FileOutputStream fos = new FileOutputStream(cacheFile)) { fos.write(cacheEntry); } catch (IOException ioe) { Slog.w(TAG, "Error writing cache entry.", ioe); cacheFile.delete(); } } catch (Throwable e) { Slog.w(TAG, "Error saving package cache.", e); } } /** * 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. *

* Note that this does not perform signature verification; that * must be done separately in {@link #collectCertificates(Package, int)}. */ private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException { final PackageLite lite = parseClusterPackageLite(packageDir, 0); if (mOnlyCoreApps && !lite.coreApp) { throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "Not a coreApp: " + packageDir); } // Build the split dependency tree. SparseArray 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); final Package pkg = parseBaseApk(baseApk, assets, flags); if (pkg == null) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse base APK: " + baseApk); } if (!ArrayUtils.isEmpty(lite.splitNames)) { final int num = lite.splitNames.length; pkg.splitNames = lite.splitNames; pkg.splitCodePaths = lite.splitCodePaths; pkg.splitRevisionCodes = lite.splitRevisionCodes; pkg.splitFlags = new int[num]; pkg.splitPrivateFlags = new int[num]; pkg.applicationInfo.splitNames = pkg.splitNames; pkg.applicationInfo.splitDependencies = splitDependencies; pkg.applicationInfo.splitClassLoaderNames = new String[num]; for (int i = 0; i < num; i++) { final AssetManager splitAssets = assetLoader.getSplitAssetManager(i); parseSplitApk(pkg, i, splitAssets, flags); } } pkg.setCodePath(packageDir.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; } 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. *

* Note that this does not perform signature verification; that * must be done separately in {@link #collectCertificates(Package, int)}. * * @deprecated external callers should move to * {@link #parsePackage(File, int)}. Eventually this method will * be marked private. */ @Deprecated @UnsupportedAppUsage public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { final PackageLite lite = parseMonolithicPackageLite(apkFile, flags); if (mOnlyCoreApps) { if (!lite.coreApp) { throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "Not a coreApp: " + apkFile); } } final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags); pkg.setCodePath(apkFile.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; } catch (IOException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to get path: " + apkFile, e); } finally { IoUtils.closeQuietly(assetLoader); } } private Package parseBaseApk(File apkFile, AssetManager assets, int flags) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); String volumeUuid = null; if (apkPath.startsWith(MNT_EXPAND)) { final int end = apkPath.indexOf('/', MNT_EXPAND.length()); volumeUuid = apkPath.substring(MNT_EXPAND.length(), end); } mParseError = PackageManager.INSTALL_SUCCEEDED; mArchiveSourcePath = apkFile.getAbsolutePath(); if (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, ANDROID_MANIFEST_FILENAME); final Resources res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError); if (pkg == null) { throw new PackageParserException(mParseError, apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]); } pkg.setVolumeUuid(volumeUuid); pkg.setApplicationVolumeUuid(volumeUuid); pkg.setBaseCodePath(apkPath); pkg.setSigningDetails(SigningDetails.UNKNOWN); return pkg; } 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 void parseSplitApk(Package pkg, int splitIndex, AssetManager assets, int flags) throws PackageParserException { final String apkPath = pkg.splitCodePaths[splitIndex]; mParseError = PackageManager.INSTALL_SUCCEEDED; mArchiveSourcePath = apkPath; if (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, ANDROID_MANIFEST_FILENAME); res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError); if (pkg == null) { throw new PackageParserException(mParseError, apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]); } } 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 split APK. *

* Note that split APKs have many more restrictions on what they're capable * of doing, so many valid features of a base APK have been carefully * omitted here. */ private Package parseSplitApk(Package pkg, 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 parsePackageSplitNames(parser, attrs); mParseInstrumentationArgs = null; 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(TAG_APPLICATION)) { if (foundApp) { if (RIGID_PARSER) { outError[0] = " has more than one "; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } else { Slog.w(TAG, " has more than one "); XmlUtils.skipCurrentTag(parser); continue; } } foundApp = true; if (!parseSplitApplication(pkg, res, parser, flags, splitIndex, outError)) { return null; } } else if (RIGID_PARSER) { outError[0] = "Bad element under : " + parser.getName(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } else { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } } if (!foundApp) { outError[0] = " does not contain an "; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY; } return pkg; } /** Parses the public keys from the set of signatures. */ public static ArraySet toSigningKeys(Signature[] signatures) throws CertificateException { ArraySet keys = new ArraySet<>(signatures.length); for (int i = 0; i < signatures.length; i++) { keys.add(signatures[i].getPublicKey()); } return keys; } /** * Collect certificates from all the APKs described in the given package, * populating {@link Package#mSigningDetails}. Also asserts that all APK * contents are signed correctly and consistently. */ @UnsupportedAppUsage public static void collectCertificates(Package pkg, boolean skipVerify) throws PackageParserException { collectCertificatesInternal(pkg, skipVerify); final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { Package childPkg = pkg.childPackages.get(i); childPkg.mSigningDetails = pkg.mSigningDetails; } } private static void collectCertificatesInternal(Package pkg, boolean skipVerify) throws PackageParserException { pkg.mSigningDetails = SigningDetails.UNKNOWN; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { collectCertificates(pkg, new File(pkg.baseCodePath), skipVerify); if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) { for (int i = 0; i < pkg.splitCodePaths.length; i++) { collectCertificates(pkg, new File(pkg.splitCodePaths[i]), skipVerify); } } } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } @UnsupportedAppUsage private static void collectCertificates(Package pkg, File apkFile, boolean skipVerify) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); int minSignatureScheme = SigningDetails.SignatureSchemeVersion.JAR; if (pkg.applicationInfo.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( apkPath, minSignatureScheme); } else { verified = ApkSignatureVerifier.verify(apkPath, 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 (pkg.mSigningDetails == SigningDetails.UNKNOWN) { pkg.mSigningDetails = verified; } else { if (!Signature.areExactMatch(pkg.mSigningDetails.signatures, verified.signatures)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, apkPath + " has mismatched certificates"); } } } private static AssetManager newConfiguredAssetManager() { AssetManager assetManager = new AssetManager(); assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); return assetManager; } /** * Utility method that retrieves lightweight details about a single APK * file, including package name, split name, and install location. * * @param apkFile path to a single APK * @param flags optional parse flags, such as * {@link #PARSE_COLLECT_CERTIFICATES} */ public static ApkLite parseApkLite(File apkFile, int flags) throws PackageParserException { return parseApkLiteInner(apkFile, null, null, flags); } /** * Utility method that retrieves lightweight details about a single APK * file, including package name, split name, and install location. * * @param fd already open file descriptor of an apk file * @param debugPathName arbitrary text name for this file, for debug output * @param flags optional parse flags, such as * {@link #PARSE_COLLECT_CERTIFICATES} */ public static ApkLite parseApkLite(FileDescriptor fd, String debugPathName, int flags) throws PackageParserException { return parseApkLiteInner(null, fd, debugPathName, flags); } private static ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, String debugPathName, int flags) throws PackageParserException { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); XmlResourceParser parser = null; ApkAssets apkAssets = null; try { try { apkAssets = fd != null ? ApkAssets.loadFromFd(fd, debugPathName, false, false) : ApkAssets.loadFromPath(apkPath); } catch (IOException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse " + apkPath); } parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME); final SigningDetails signingDetails; if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { // TODO: factor signature related items out of Package object final Package tempPkg = new Package((String) null); final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { collectCertificates(tempPkg, apkFile, skipVerify); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } signingDetails = tempPkg.mSigningDetails; } else { signingDetails = SigningDetails.UNKNOWN; } final AttributeSet attrs = parser; return parseApkLite(apkPath, parser, attrs, signingDetails); } catch (XmlPullParserException | IOException | RuntimeException e) { Slog.w(TAG, "Failed to parse " + apkPath, e); throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to parse " + apkPath, e); } finally { IoUtils.closeQuietly(parser); if (apkAssets != null) { try { apkAssets.close(); } catch (Throwable ignored) { } } // TODO(b/72056911): Implement AutoCloseable on ApkAssets. } } private static String validateName(String name, boolean requireSeparator, boolean requireFilename) { final int N = name.length(); boolean hasSep = false; boolean front = true; for (int i=0; i= '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"; } private static Pair parsePackageSplitNames(XmlPullParser parser, AttributeSet attrs) throws IOException, XmlPullParserException, PackageParserException { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } if (type != XmlPullParser.START_TAG) { throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "No start tag found"); } if (!parser.getName().equals(TAG_MANIFEST)) { throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "No tag"); } final String packageName = attrs.getAttributeValue(null, "package"); if (!"android".equals(packageName)) { final String error = validateName(packageName, true, true); if (error != null) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, "Invalid manifest package: " + error); } } String splitName = attrs.getAttributeValue(null, "split"); if (splitName != null) { if (splitName.length() == 0) { splitName = null; } else { final String error = validateName(splitName, false, false); if (error != null) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, "Invalid manifest split: " + error); } } } return Pair.create(packageName.intern(), (splitName != null) ? splitName.intern() : splitName); } private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs, SigningDetails signingDetails) throws IOException, XmlPullParserException, PackageParserException { final Pair packageSplit = parsePackageSplitNames(parser, attrs); int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; int versionCode = 0; int versionCodeMajor = 0; int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION; int minSdkVersion = DEFAULT_MIN_SDK_VERSION; int revisionCode = 0; boolean coreApp = false; boolean debuggable = false; boolean multiArch = false; boolean use32bitAbi = false; boolean extractNativeLibs = true; boolean isolatedSplits = false; boolean isFeatureSplit = false; boolean isSplitRequired = false; boolean useEmbeddedDex = false; String configForSplit = null; String usesSplitName = null; for (int i = 0; i < attrs.getAttributeCount(); i++) { final String attr = attrs.getAttributeName(i); if (attr.equals("installLocation")) { installLocation = attrs.getAttributeIntValue(i, PARSE_DEFAULT_INSTALL_LOCATION); } else if (attr.equals("versionCode")) { versionCode = attrs.getAttributeIntValue(i, 0); } else if (attr.equals("versionCodeMajor")) { versionCodeMajor = attrs.getAttributeIntValue(i, 0); } else if (attr.equals("revisionCode")) { revisionCode = attrs.getAttributeIntValue(i, 0); } else if (attr.equals("coreApp")) { coreApp = attrs.getAttributeBooleanValue(i, false); } else if (attr.equals("isolatedSplits")) { isolatedSplits = attrs.getAttributeBooleanValue(i, false); } else if (attr.equals("configForSplit")) { configForSplit = attrs.getAttributeValue(i); } else if (attr.equals("isFeatureSplit")) { isFeatureSplit = attrs.getAttributeBooleanValue(i, false); } else if (attr.equals("isSplitRequired")) { isSplitRequired = attrs.getAttributeBooleanValue(i, false); } } // Only search the tree when the tag is the direct child of tag int type; final int searchDepth = parser.getDepth() + 1; final List verifiers = new ArrayList(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } if (parser.getDepth() != searchDepth) { continue; } if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) { final VerifierInfo verifier = parseVerifier(attrs); if (verifier != null) { verifiers.add(verifier); } } else if (TAG_APPLICATION.equals(parser.getName())) { for (int i = 0; i < attrs.getAttributeCount(); ++i) { final String attr = attrs.getAttributeName(i); if ("debuggable".equals(attr)) { debuggable = attrs.getAttributeBooleanValue(i, false); } if ("multiArch".equals(attr)) { multiArch = attrs.getAttributeBooleanValue(i, false); } if ("use32bitAbi".equals(attr)) { use32bitAbi = attrs.getAttributeBooleanValue(i, false); } if ("extractNativeLibs".equals(attr)) { extractNativeLibs = attrs.getAttributeBooleanValue(i, true); } if ("useEmbeddedDex".equals(attr)) { useEmbeddedDex = attrs.getAttributeBooleanValue(i, false); } } } else if (TAG_USES_SPLIT.equals(parser.getName())) { if (usesSplitName != null) { Slog.w(TAG, "Only one permitted. Ignoring others."); continue; } usesSplitName = attrs.getAttributeValue(ANDROID_RESOURCES, "name"); if (usesSplitName == null) { throw new PackageParserException( PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, " tag requires 'android:name' attribute"); } } else if (TAG_USES_SDK.equals(parser.getName())) { for (int i = 0; i < attrs.getAttributeCount(); ++i) { final String attr = attrs.getAttributeName(i); if ("targetSdkVersion".equals(attr)) { targetSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_TARGET_SDK_VERSION); } if ("minSdkVersion".equals(attr)) { minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION); } } } } return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits, minSdkVersion, targetSdkVersion); } /** * Parses a child package and adds it to the parent if successful. If you add * new tags that need to be supported by child packages make sure to add them * to {@link #CHILD_PACKAGE_TAGS}. * * @param parentPkg The parent that contains the child * @param res Resources against which to resolve values * @param parser Parser of the manifest * @param flags Flags about how to parse * @param outError Human readable error if parsing fails * @return True of parsing succeeded. * * @throws XmlPullParserException * @throws IOException */ private boolean parseBaseApkChild(Package parentPkg, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { // Make sure we have a valid child package name String childPackageName = parser.getAttributeValue(null, "package"); if (validateName(childPackageName, true, false) != null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; return false; } // Child packages must be unique if (childPackageName.equals(parentPkg.packageName)) { String message = "Child package name cannot be equal to parent package name: " + parentPkg.packageName; Slog.w(TAG, message); outError[0] = message; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } // Child packages must be unique if (parentPkg.hasChildPackage(childPackageName)) { String message = "Duplicate child package:" + childPackageName; Slog.w(TAG, message); outError[0] = message; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } // Go ahead and parse the child Package childPkg = new Package(childPackageName); // Child package inherits parent version code/name/target SDK childPkg.mVersionCode = parentPkg.mVersionCode; childPkg.baseRevisionCode = parentPkg.baseRevisionCode; childPkg.mVersionName = parentPkg.mVersionName; childPkg.applicationInfo.targetSdkVersion = parentPkg.applicationInfo.targetSdkVersion; childPkg.applicationInfo.minSdkVersion = parentPkg.applicationInfo.minSdkVersion; childPkg = parseBaseApkCommon(childPkg, CHILD_PACKAGE_TAGS, res, parser, flags, outError); if (childPkg == null) { // If we got null then error was set during child parsing return false; } // Set the parent-child relation if (parentPkg.childPackages == null) { parentPkg.childPackages = new ArrayList<>(); } parentPkg.childPackages.add(childPkg); childPkg.parentPackage = parentPkg; return true; } /** * Parse the manifest of a base APK. When adding new features you * need to consider whether they should be supported by split APKs and child * packages. * * @param apkPath The package apk file path * @param res The resources from which to resolve values * @param parser The manifest parser * @param flags Flags how to parse * @param outError Human readable error message * @return Parsed package or null on error. * * @throws XmlPullParserException * @throws IOException */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { final String splitName; final String pkgName; try { Pair packageSplit = parsePackageSplitNames(parser, parser); pkgName = packageSplit.first; splitName = packageSplit.second; if (!TextUtils.isEmpty(splitName)) { outError[0] = "Expected base APK, but found split " + splitName; mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; return null; } } catch (PackageParserException e) { mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; return null; } if (mCallback != null) { String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath); if (overlayPaths != null && overlayPaths.length > 0) { for (String overlayPath : overlayPaths) { res.getAssets().addOverlayPath(overlayPath); } } } final Package pkg = new Package(pkgName); TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifest); pkg.mVersionCode = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_versionCode, 0); pkg.mVersionCodeMajor = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0); pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode()); pkg.baseRevisionCode = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_revisionCode, 0); pkg.mVersionName = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifest_versionName, 0); if (pkg.mVersionName != null) { pkg.mVersionName = pkg.mVersionName.intern(); } pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false); pkg.mCompileSdkVersion = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0); pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion; pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0); if (pkg.mCompileSdkVersionCodename != null) { pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern(); } pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename; sa.recycle(); return parseBaseApkCommon(pkg, null, res, parser, flags, outError); } /** * This is the common parsing routing for handling parent and child * packages in a base APK. The difference between parent and child * parsing is that some tags are not supported by child packages as * well as some manifest attributes are ignored. The implementation * assumes the calling code has already handled the manifest tag if needed * (this applies to the parent only). * * @param pkg The package which to populate * @param acceptedTags Which tags to handle, null to handle all * @param res Resources against which to resolve values * @param parser Parser of the manifest * @param flags Flags about how to parse * @param outError Human readable error if parsing fails * @return The package if parsing succeeded or null. * * @throws XmlPullParserException * @throws IOException */ private Package parseBaseApkCommon(Package pkg, Set acceptedTags, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { mParseInstrumentationArgs = null; int type; boolean foundApp = false; TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifest); String str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0); if (str != null && str.length() > 0) { String nameError = validateName(str, true, true); if (nameError != null && !"android".equals(pkg.packageName)) { outError[0] = " specifies bad sharedUserId name \"" + str + "\": " + nameError; mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID; return null; } pkg.mSharedUserId = str.intern(); pkg.mSharedUserLabel = sa.getResourceId( com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0); } pkg.installLocation = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_installLocation, PARSE_DEFAULT_INSTALL_LOCATION); pkg.applicationInfo.installLocation = pkg.installLocation; final int targetSandboxVersion = sa.getInteger( com.android.internal.R.styleable.AndroidManifest_targetSandboxVersion, PARSE_DEFAULT_TARGET_SANDBOX); pkg.applicationInfo.targetSandboxVersion = targetSandboxVersion; /* Set the global "on SD card" flag */ if ((flags & PARSE_EXTERNAL_STORAGE) != 0) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; } if (sa.getBoolean(com.android.internal.R.styleable.AndroidManifest_isolatedSplits, false)) { pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING; } // Resource boolean are -1, so 1 means we don't know the value. int supportsSmallScreens = 1; int supportsNormalScreens = 1; int supportsLargeScreens = 1; int supportsXLargeScreens = 1; int resizeable = 1; int anyDensity = 1; 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 (acceptedTags != null && !acceptedTags.contains(tagName)) { Slog.w(TAG, "Skipping unsupported element under : " + tagName + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } if (tagName.equals(TAG_APPLICATION)) { if (foundApp) { if (RIGID_PARSER) { outError[0] = " has more than one "; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } else { Slog.w(TAG, " has more than one "); XmlUtils.skipCurrentTag(parser); continue; } } foundApp = true; if (!parseBaseApplication(pkg, res, parser, flags, outError)) { return null; } } else if (tagName.equals(TAG_OVERLAY)) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestResourceOverlay); pkg.mOverlayTarget = sa.getString( com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage); pkg.mOverlayTargetName = sa.getString( com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetName); pkg.mOverlayCategory = sa.getString( com.android.internal.R.styleable.AndroidManifestResourceOverlay_category); pkg.mOverlayPriority = sa.getInt( com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority, 0); pkg.mOverlayIsStatic = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic, false); final String propName = sa.getString( com.android.internal.R.styleable .AndroidManifestResourceOverlay_requiredSystemPropertyName); final String propValue = sa.getString( com.android.internal.R.styleable .AndroidManifestResourceOverlay_requiredSystemPropertyValue); sa.recycle(); if (pkg.mOverlayTarget == null) { outError[0] = " does not specify a target package"; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } if (pkg.mOverlayPriority < 0 || pkg.mOverlayPriority > 9999) { outError[0] = " priority must be between 0 and 9999"; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } // check to see if overlay should be excluded based on system property condition if (!checkOverlayRequiredSystemProperty(propName, propValue)) { Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and " + pkg.baseCodePath+ ": overlay ignored due to required system property: " + propName + " with value: " + propValue); return null; } pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY; XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_KEY_SETS)) { if (!parseKeySets(pkg, res, parser, outError)) { return null; } } else if (tagName.equals(TAG_PERMISSION_GROUP)) { if (!parsePermissionGroup(pkg, flags, res, parser, outError)) { return null; } } else if (tagName.equals(TAG_PERMISSION)) { if (!parsePermission(pkg, res, parser, outError)) { return null; } } else if (tagName.equals(TAG_PERMISSION_TREE)) { if (!parsePermissionTree(pkg, res, parser, outError)) { return null; } } else if (tagName.equals(TAG_USES_PERMISSION)) { if (!parseUsesPermission(pkg, res, parser)) { return null; } } else if (tagName.equals(TAG_USES_PERMISSION_SDK_M) || tagName.equals(TAG_USES_PERMISSION_SDK_23)) { if (!parseUsesPermission(pkg, res, parser)) { return null; } } else if (tagName.equals(TAG_USES_CONFIGURATION)) { ConfigurationInfo cPref = new ConfigurationInfo(); sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestUsesConfiguration); cPref.reqTouchScreen = sa.getInt( com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqTouchScreen, Configuration.TOUCHSCREEN_UNDEFINED); cPref.reqKeyboardType = sa.getInt( com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqKeyboardType, Configuration.KEYBOARD_UNDEFINED); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqHardKeyboard, false)) { cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD; } cPref.reqNavigation = sa.getInt( com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqNavigation, Configuration.NAVIGATION_UNDEFINED); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqFiveWayNav, false)) { cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV; } sa.recycle(); pkg.configPreferences = ArrayUtils.add(pkg.configPreferences, cPref); XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_USES_FEATURE)) { FeatureInfo fi = parseUsesFeature(res, parser); pkg.reqFeatures = ArrayUtils.add(pkg.reqFeatures, fi); if (fi.name == null) { ConfigurationInfo cPref = new ConfigurationInfo(); cPref.reqGlEsVersion = fi.reqGlEsVersion; pkg.configPreferences = ArrayUtils.add(pkg.configPreferences, cPref); } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_FEATURE_GROUP)) { FeatureGroupInfo group = new FeatureGroupInfo(); ArrayList features = null; 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; } final String innerTagName = parser.getName(); if (innerTagName.equals("uses-feature")) { FeatureInfo featureInfo = parseUsesFeature(res, parser); // FeatureGroups are stricter and mandate that // any declared are mandatory. featureInfo.flags |= FeatureInfo.FLAG_REQUIRED; features = ArrayUtils.add(features, featureInfo); } else { Slog.w(TAG, "Unknown element under : " + innerTagName + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); } XmlUtils.skipCurrentTag(parser); } if (features != null) { group.features = new FeatureInfo[features.size()]; group.features = features.toArray(group.features); } pkg.featureGroups = ArrayUtils.add(pkg.featureGroups, group); } else if (tagName.equals(TAG_USES_SDK)) { if (SDK_VERSION > 0) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestUsesSdk); int minVers = 1; String minCode = null; int targetVers = 0; String targetCode = null; TypedValue val = sa.peekValue( com.android.internal.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( com.android.internal.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(); final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode, SDK_VERSION, SDK_CODENAMES, outError); if (minSdkVersion < 0) { mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; return null; } final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, outError); if (targetSdkVersion < 0) { mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; return null; } pkg.applicationInfo.minSdkVersion = minSdkVersion; pkg.applicationInfo.targetSdkVersion = targetSdkVersion; } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_SUPPORT_SCREENS)) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestSupportsScreens); pkg.applicationInfo.requiresSmallestWidthDp = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_requiresSmallestWidthDp, 0); pkg.applicationInfo.compatibleWidthLimitDp = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_compatibleWidthLimitDp, 0); pkg.applicationInfo.largestWidthLimitDp = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_largestWidthLimitDp, 0); // This is a trick to get a boolean and still able to detect // if a value was actually set. supportsSmallScreens = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_smallScreens, supportsSmallScreens); supportsNormalScreens = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_normalScreens, supportsNormalScreens); supportsLargeScreens = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens, supportsLargeScreens); supportsXLargeScreens = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_xlargeScreens, supportsXLargeScreens); resizeable = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_resizeable, resizeable); anyDensity = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_anyDensity, anyDensity); sa.recycle(); XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_PROTECTED_BROADCAST)) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestProtectedBroadcast); // Note: don't allow this value to be a reference to a resource // that may change. String name = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name); sa.recycle(); if (name != null) { if (pkg.protectedBroadcasts == null) { pkg.protectedBroadcasts = new ArrayList(); } if (!pkg.protectedBroadcasts.contains(name)) { pkg.protectedBroadcasts.add(name.intern()); } } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_INSTRUMENTATION)) { if (parseInstrumentation(pkg, res, parser, outError) == null) { return null; } } else if (tagName.equals(TAG_ORIGINAL_PACKAGE)) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestOriginalPackage); String orig =sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0); if (!pkg.packageName.equals(orig)) { if (pkg.mOriginalPackages == null) { pkg.mOriginalPackages = new ArrayList(); pkg.mRealPackage = pkg.packageName; } pkg.mOriginalPackages.add(orig); } sa.recycle(); XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_ADOPT_PERMISSIONS)) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestOriginalPackage); String name = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0); sa.recycle(); if (name != null) { if (pkg.mAdoptPermissions == null) { pkg.mAdoptPermissions = new ArrayList(); } pkg.mAdoptPermissions.add(name); } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_USES_GL_TEXTURE)) { // Just skip this tag XmlUtils.skipCurrentTag(parser); continue; } else if (tagName.equals(TAG_COMPATIBLE_SCREENS)) { // Just skip this tag XmlUtils.skipCurrentTag(parser); continue; } else if (tagName.equals(TAG_SUPPORTS_INPUT)) {// XmlUtils.skipCurrentTag(parser); continue; } else if (tagName.equals(TAG_EAT_COMMENT)) { // Just skip this tag XmlUtils.skipCurrentTag(parser); continue; } else if (tagName.equals(TAG_PACKAGE)) { if (!MULTI_PACKAGE_APK_ENABLED) { XmlUtils.skipCurrentTag(parser); continue; } if (!parseBaseApkChild(pkg, res, parser, flags, outError)) { // If parsing a child failed the error is already set return null; } } else if (tagName.equals(TAG_RESTRICT_UPDATE)) { if ((flags & PARSE_IS_SYSTEM_DIR) != 0) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestRestrictUpdate); final String hash = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestRestrictUpdate_hash, 0); sa.recycle(); pkg.restrictUpdateHash = null; if (hash != null) { final int hashLength = hash.length(); final byte[] hashBytes = new byte[hashLength / 2]; for (int i = 0; i < hashLength; i += 2){ hashBytes[i/2] = (byte) ((Character.digit(hash.charAt(i), 16) << 4) + Character.digit(hash.charAt(i + 1), 16)); } pkg.restrictUpdateHash = hashBytes; } } XmlUtils.skipCurrentTag(parser); } else if (RIGID_PARSER) { outError[0] = "Bad element under : " + parser.getName(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } else { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } } if (!foundApp && pkg.instrumentation.size() == 0) { outError[0] = " does not contain an or "; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY; } final int NP = PackageParser.NEW_PERMISSIONS.length; StringBuilder newPermsMsg = null; for (int ip=0; ip= npi.sdkVersion) { break; } if (!pkg.requestedPermissions.contains(npi.name)) { if (newPermsMsg == null) { newPermsMsg = new StringBuilder(128); newPermsMsg.append(pkg.packageName); newPermsMsg.append(": compat added "); } else { newPermsMsg.append(' '); } newPermsMsg.append(npi.name); pkg.requestedPermissions.add(npi.name); pkg.implicitPermissions.add(npi.name); } } if (newPermsMsg != null) { Slog.i(TAG, newPermsMsg.toString()); } List splitPermissions = getSplitPermissions(); final int listSize = splitPermissions.size(); for (int is = 0; is < listSize; is++) { final SplitPermissionInfoParcelable spi = splitPermissions.get(is); if (pkg.applicationInfo.targetSdkVersion >= spi.getTargetSdk() || !pkg.requestedPermissions.contains(spi.getSplitPermission())) { continue; } final List newPerms = spi.getNewPermissions(); for (int in = 0; in < newPerms.size(); in++) { final String perm = newPerms.get(in); if (!pkg.requestedPermissions.contains(perm)) { pkg.requestedPermissions.add(perm); pkg.implicitPermissions.add(perm); } } } if (supportsSmallScreens < 0 || (supportsSmallScreens > 0 && pkg.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.DONUT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS; } if (supportsNormalScreens != 0) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS; } if (supportsLargeScreens < 0 || (supportsLargeScreens > 0 && pkg.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.DONUT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; } if (supportsXLargeScreens < 0 || (supportsXLargeScreens > 0 && pkg.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.GINGERBREAD)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS; } if (resizeable < 0 || (resizeable > 0 && pkg.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.DONUT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS; } if (anyDensity < 0 || (anyDensity > 0 && pkg.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.DONUT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES; } // At this point we can check if an application is not supporting densities and hence // cannot be windowed / resized. Note that an SDK version of 0 is common for // pre-Doughnut applications. if (pkg.applicationInfo.usesCompatibilityMode()) { adjustPackageToBeUnresizeableAndUnpipable(pkg); } return pkg; } private List getSplitPermissions() { // PackageManager runs this code during initialization prior to registering with // ServiceManager, so we can't use the PackageManager API. Instead, just read from // SystemConfig directly when in any SystemProcess and only use PackageManager when not in // one. if (ActivityThread.isSystem()) { return PermissionManager.splitPermissionInfoListToParcelableList( SystemConfig.getInstance().getSplitPermissions()); } else { try { return ActivityThread.getPackageManager().getSplitPermissions(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } private 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 pkg The package which needs to be marked as unresizable. */ private void adjustPackageToBeUnresizeableAndUnpipable(Package pkg) { for (Activity a : pkg.activities) { a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; a.info.flags &= ~FLAG_SUPPORTS_PICTURE_IN_PICTURE; } } /** /** * Matches a given {@code targetCode} against a set of release codeNames. Target codes can * either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form * {@code [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}). */ private static boolean matchTargetCode(@NonNull String[] codeNames, @NonNull String targetCode) { final String targetCodeName; final int targetCodeIdx = targetCode.indexOf('.'); if (targetCodeIdx == -1) { targetCodeName = targetCode; } else { targetCodeName = targetCode.substring(0, targetCodeIdx); } return ArrayUtils.contains(codeNames, targetCodeName); } /** * Computes the targetSdkVersion to use at runtime. If the package is not * compatible with this platform, populates {@code outError[0]} with an * error message. *

* If {@code targetCode} is not specified, e.g. the value is {@code null}, * then the {@code targetVers} will be returned unmodified. *

* Otherwise, the behavior varies based on whether the current platform * is a pre-release version, e.g. the {@code platformSdkCodenames} array * has length > 0: *

    *
  • If this is a pre-release platform and the value specified by * {@code targetCode} is contained within the array of allowed pre-release * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. *
  • If this is a released platform, this method will return -1 to * indicate that the package is not compatible with this platform. *
* * @param targetVers targetSdkVersion number, if specified in the * application manifest, or 0 otherwise * @param targetCode targetSdkVersion code, if specified in the application * manifest, or {@code null} otherwise * @param platformSdkCodenames array of allowed pre-release SDK codenames * for this platform * @param outError output array to populate with error, if applicable * @return the targetSdkVersion to use at runtime, or -1 if the package is * not compatible with this platform * @hide Exposed for unit testing only. */ @TestApi public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers, @Nullable String targetCode, @NonNull String[] platformSdkCodenames, @NonNull String[] outError) { // If it's a release SDK, return the version number unmodified. if (targetCode == null) { return targetVers; } // If it's a pre-release SDK and the codename matches this platform, it // definitely targets this SDK. if (matchTargetCode(platformSdkCodenames, targetCode)) { return Build.VERSION_CODES.CUR_DEVELOPMENT; } // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + targetCode + " (current platform is any of " + Arrays.toString(platformSdkCodenames) + ")"; } else { outError[0] = "Requires development platform " + targetCode + " but this is a release platform."; } return -1; } /** * Computes the minSdkVersion to use at runtime. If the package is not * compatible with this platform, populates {@code outError[0]} with an * error message. *

* If {@code minCode} is not specified, e.g. the value is {@code null}, * then behavior varies based on the {@code platformSdkVersion}: *

    *
  • If the platform SDK version is greater than or equal to the * {@code minVers}, returns the {@code mniVers} unmodified. *
  • Otherwise, returns -1 to indicate that the package is not * compatible with this platform. *
*

* Otherwise, the behavior varies based on whether the current platform * is a pre-release version, e.g. the {@code platformSdkCodenames} array * has length > 0: *

    *
  • If this is a pre-release platform and the value specified by * {@code targetCode} is contained within the array of allowed pre-release * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. *
  • If this is a released platform, this method will return -1 to * indicate that the package is not compatible with this platform. *
* * @param minVers minSdkVersion number, if specified in the application * manifest, or 1 otherwise * @param minCode minSdkVersion code, if specified in the application * manifest, or {@code null} otherwise * @param platformSdkVersion platform SDK version number, typically * Build.VERSION.SDK_INT * @param platformSdkCodenames array of allowed prerelease SDK codenames * for this platform * @param outError output array to populate with error, if applicable * @return the minSdkVersion to use at runtime, or -1 if the package is not * compatible with this platform * @hide Exposed for unit testing only. */ @TestApi public static int computeMinSdkVersion(@IntRange(from = 1) int minVers, @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion, @NonNull String[] platformSdkCodenames, @NonNull String[] outError) { // If it's a release SDK, make sure we meet the minimum SDK requirement. if (minCode == null) { if (minVers <= platformSdkVersion) { return minVers; } // We don't meet the minimum SDK requirement. outError[0] = "Requires newer sdk version #" + minVers + " (current version is #" + platformSdkVersion + ")"; return -1; } // If it's a pre-release SDK and the codename matches this platform, we // definitely meet the minimum SDK requirement. if (matchTargetCode(platformSdkCodenames, minCode)) { return Build.VERSION_CODES.CUR_DEVELOPMENT; } // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + minCode + " (current platform is any of " + Arrays.toString(platformSdkCodenames) + ")"; } else { outError[0] = "Requires development platform " + minCode + " but this is a release platform."; } return -1; } private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs) { FeatureInfo fi = new FeatureInfo(); TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestUsesFeature); // Note: don't allow this value to be a reference to a resource // that may change. fi.name = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestUsesFeature_name); fi.version = sa.getInt( com.android.internal.R.styleable.AndroidManifestUsesFeature_version, 0); if (fi.name == null) { fi.reqGlEsVersion = sa.getInt( com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion, FeatureInfo.GL_ES_VERSION_UNDEFINED); } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestUsesFeature_required, true)) { fi.flags |= FeatureInfo.FLAG_REQUIRED; } sa.recycle(); return fi; } private boolean parseUsesStaticLibrary(Package pkg, Resources res, XmlResourceParser parser, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary); // Note: don't allow this value to be a reference to a resource that may change. String lname = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestUsesLibrary_name); final int version = sa.getInt( com.android.internal.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) { outError[0] = "Bad uses-static-library declaration name: " + lname + " version: " + version + " certDigest" + certSha256Digest; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; XmlUtils.skipCurrentTag(parser); return false; } // Can depend only on one version of the same library if (pkg.usesStaticLibraries != null && pkg.usesStaticLibraries.contains(lname)) { outError[0] = "Depending on multiple versions of static library " + lname; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; XmlUtils.skipCurrentTag(parser); return false; } lname = lname.intern(); // We allow ":" delimiters in the SHA declaration as this is the format // emitted by the certtool making it easy for developers to copy/paste. certSha256Digest = certSha256Digest.replace(":", "").toLowerCase(); // Fot apps targeting O-MR1 we require explicit enumeration of all certs. String[] additionalCertSha256Digests = EmptyArray.STRING; if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O_MR1) { additionalCertSha256Digests = parseAdditionalCertificates(res, parser, outError); if (additionalCertSha256Digests == null) { return false; } } else { XmlUtils.skipCurrentTag(parser); } final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1]; certSha256Digests[0] = certSha256Digest; System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests, 1, additionalCertSha256Digests.length); pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname); pkg.usesStaticLibrariesVersions = ArrayUtils.appendLong( pkg.usesStaticLibrariesVersions, version, true); pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class, pkg.usesStaticLibrariesCertDigests, certSha256Digests, true); return true; } private 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; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; 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; } private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestUsesPermission); // Note: don't allow this value to be a reference to a resource // that may change. String name = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestUsesPermission_name); int maxSdkVersion = 0; TypedValue val = sa.peekValue( com.android.internal.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( com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature, 0); final String requiredNotfeature = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredNotFeature, 0); sa.recycle(); XmlUtils.skipCurrentTag(parser); if (name == null) { return true; } if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) { return true; } // Only allow requesting this permission if the platform supports the given feature. if (requiredFeature != null && mCallback != null && !mCallback.hasFeature(requiredFeature)) { return true; } // Only allow requesting this permission if the platform doesn't support the given feature. if (requiredNotfeature != null && mCallback != null && mCallback.hasFeature(requiredNotfeature)) { return true; } int index = pkg.requestedPermissions.indexOf(name); if (index == -1) { pkg.requestedPermissions.add(name.intern()); } else { Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: " + name + " in package: " + pkg.packageName + " at: " + parser.getPositionDescription()); } return true; } private static String buildClassName(String pkg, CharSequence clsSeq, String[] outError) { if (clsSeq == null || clsSeq.length() <= 0) { outError[0] = "Empty class name in package " + pkg; 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; } private static String buildCompoundName(String pkg, CharSequence procSeq, String type, String[] outError) { String proc = procSeq.toString(); char c = proc.charAt(0); if (pkg != null && c == ':') { if (proc.length() < 2) { outError[0] = "Bad " + type + " name " + proc + " in package " + pkg + ": must be at least two characters"; return null; } String subName = proc.substring(1); String nameError = validateName(subName, false, false); if (nameError != null) { outError[0] = "Invalid " + type + " name " + proc + " in package " + pkg + ": " + nameError; return null; } return pkg + proc; } String nameError = validateName(proc, true, false); if (nameError != null && !"system".equals(proc)) { outError[0] = "Invalid " + type + " name " + proc + " in package " + pkg + ": " + nameError; return null; } return proc; } private static String buildProcessName(String pkg, String defProc, CharSequence procSeq, int flags, String[] separateProcesses, String[] outError) { if ((flags&PARSE_IGNORE_PROCESSES) != 0 && !"system".equals(procSeq)) { return defProc != null ? defProc : pkg; } if (separateProcesses != null) { for (int i=separateProcesses.length-1; i>=0; i--) { String sp = separateProcesses[i]; if (sp.equals(pkg) || sp.equals(defProc) || sp.equals(procSeq)) { return pkg; } } } if (procSeq == null || procSeq.length() <= 0) { return defProc; } return TextUtils.safeIntern(buildCompoundName(pkg, procSeq, "process", outError)); } private static String buildTaskAffinityName(String pkg, String defProc, CharSequence procSeq, String[] outError) { if (procSeq == null) { return defProc; } if (procSeq.length() <= 0) { return null; } return buildCompoundName(pkg, procSeq, "taskAffinity", outError); } private boolean parseKeySets(Package owner, Resources res, XmlResourceParser parser, String[] outError) throws XmlPullParserException, IOException { // we've encountered the 'key-sets' tag // all the keys and keysets that we want must be defined here // so we're going to iterate over the parser and pull out the things we want int outerDepth = parser.getDepth(); int currentKeySetDepth = -1; int type; String currentKeySet = null; ArrayMap publicKeys = new ArrayMap(); ArraySet upgradeKeySets = new ArraySet(); ArrayMap> definedKeySets = new ArrayMap>(); ArraySet improperKeySets = new ArraySet(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG) { if (parser.getDepth() == currentKeySetDepth) { currentKeySet = null; currentKeySetDepth = -1; } continue; } String tagName = parser.getName(); if (tagName.equals("key-set")) { if (currentKeySet != null) { outError[0] = "Improperly nested 'key-set' tag at " + parser.getPositionDescription(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } final TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestKeySet); final String keysetName = sa.getNonResourceString( com.android.internal.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) { outError[0] = "Improperly nested 'key-set' tag at " + parser.getPositionDescription(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } final TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestPublicKey); final String publicKeyName = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestPublicKey_name); final String encodedKey = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestPublicKey_value); if (encodedKey == null && publicKeys.get(publicKeyName) == null) { outError[0] = "'public-key' " + publicKeyName + " must define a public-key value" + " on first use at " + parser.getPositionDescription(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; sa.recycle(); return false; } else if (encodedKey != null) { PublicKey currentKey = 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 { outError[0] = "Value of 'public-key' " + publicKeyName + " conflicts with previously defined value at " + parser.getPositionDescription(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; sa.recycle(); return false; } } definedKeySets.get(currentKeySet).add(publicKeyName); sa.recycle(); XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("upgrade-key-set")) { final TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestUpgradeKeySet); String name = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestUpgradeKeySet_name); upgradeKeySets.add(name); sa.recycle(); XmlUtils.skipCurrentTag(parser); } else if (RIGID_PARSER) { outError[0] = "Bad element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } else { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } } Set publicKeyNames = publicKeys.keySet(); if (publicKeyNames.removeAll(definedKeySets.keySet())) { outError[0] = "Package" + owner.packageName + " AndroidManifext.xml " + "'key-set' and 'public-key' names must be distinct."; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.mKeySetMapping = new ArrayMap>(); for (ArrayMap.Entry> e: definedKeySets.entrySet()) { final String keySetName = e.getKey(); if (e.getValue().size() == 0) { Slog.w(TAG, "Package" + owner.packageName + " AndroidManifext.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" + owner.packageName + " AndroidManifext.xml " + "'key-set' " + keySetName + " contained improper 'public-key'" + " tags. Not including in package's defined key-sets."); continue; } owner.mKeySetMapping.put(keySetName, new ArraySet()); for (String s : e.getValue()) { owner.mKeySetMapping.get(keySetName).add(publicKeys.get(s)); } } if (owner.mKeySetMapping.keySet().containsAll(upgradeKeySets)) { owner.mUpgradeKeySets = upgradeKeySets; } else { outError[0] ="Package" + owner.packageName + " AndroidManifext.xml " + "does not define all 'upgrade-key-set's ."; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } return true; } private boolean parsePermissionGroup(Package owner, int flags, Resources res, XmlResourceParser parser, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestPermissionGroup); int requestDetailResourceId = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestPermissionGroup_requestDetail, 0); int backgroundRequestResourceId = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0); int backgroundRequestDetailResourceId = sa.getResourceId( com.android.internal.R.styleable .AndroidManifestPermissionGroup_backgroundRequestDetail, 0); PermissionGroup perm = new PermissionGroup(owner, requestDetailResourceId, backgroundRequestResourceId, backgroundRequestDetailResourceId); if (!parsePackageItemInfo(owner, perm.info, outError, "", sa, true /*nameRequired*/, com.android.internal.R.styleable.AndroidManifestPermissionGroup_name, com.android.internal.R.styleable.AndroidManifestPermissionGroup_label, com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon, com.android.internal.R.styleable.AndroidManifestPermissionGroup_roundIcon, com.android.internal.R.styleable.AndroidManifestPermissionGroup_logo, com.android.internal.R.styleable.AndroidManifestPermissionGroup_banner)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } perm.info.descriptionRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestPermissionGroup_description, 0); perm.info.requestRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestPermissionGroup_request, 0); perm.info.flags = sa.getInt( com.android.internal.R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags, 0); perm.info.priority = sa.getInt( com.android.internal.R.styleable.AndroidManifestPermissionGroup_priority, 0); sa.recycle(); if (!parseAllMetaData(res, parser, "", perm, outError)) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.permissionGroups.add(perm); return true; } private boolean parsePermission(Package owner, Resources res, XmlResourceParser parser, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestPermission); String backgroundPermission = null; if (sa.hasValue( com.android.internal.R.styleable.AndroidManifestPermission_backgroundPermission)) { if ("android".equals(owner.packageName)) { backgroundPermission = sa.getNonResourceString( com.android.internal.R.styleable .AndroidManifestPermission_backgroundPermission); } else { Slog.w(TAG, owner.packageName + " defines a background permission. Only the " + "'android' package can do that."); } } Permission perm = new Permission(owner, backgroundPermission); if (!parsePackageItemInfo(owner, perm.info, outError, "", sa, true /*nameRequired*/, com.android.internal.R.styleable.AndroidManifestPermission_name, com.android.internal.R.styleable.AndroidManifestPermission_label, com.android.internal.R.styleable.AndroidManifestPermission_icon, com.android.internal.R.styleable.AndroidManifestPermission_roundIcon, com.android.internal.R.styleable.AndroidManifestPermission_logo, com.android.internal.R.styleable.AndroidManifestPermission_banner)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } // Note: don't allow this value to be a reference to a resource // that may change. perm.info.group = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestPermission_permissionGroup); if (perm.info.group != null) { perm.info.group = perm.info.group.intern(); } perm.info.descriptionRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestPermission_description, 0); perm.info.requestRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestPermission_request, 0); perm.info.protectionLevel = sa.getInt( com.android.internal.R.styleable.AndroidManifestPermission_protectionLevel, PermissionInfo.PROTECTION_NORMAL); perm.info.flags = sa.getInt( com.android.internal.R.styleable.AndroidManifestPermission_permissionFlags, 0); // For now only platform runtime permissions can be restricted if (!perm.info.isRuntime() || !"android".equals(perm.info.packageName)) { perm.info.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED; perm.info.flags &= ~PermissionInfo.FLAG_SOFT_RESTRICTED; } else { // The platform does not get to specify conflicting permissions if ((perm.info.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0 && (perm.info.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) { throw new IllegalStateException("Permission cannot be both soft and hard" + " restricted: " + perm.info.name); } } sa.recycle(); if (perm.info.protectionLevel == -1) { outError[0] = " does not specify protectionLevel"; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } perm.info.protectionLevel = PermissionInfo.fixProtectionLevel(perm.info.protectionLevel); if (perm.info.getProtectionFlags() != 0) { if ( (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_INSTANT) == 0 && (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0 && (perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) != PermissionInfo.PROTECTION_SIGNATURE) { outError[0] = " protectionLevel specifies a non-instant flag but is " + "not based on signature type"; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } } if (!parseAllMetaData(res, parser, "", perm, outError)) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.permissions.add(perm); return true; } private boolean parsePermissionTree(Package owner, Resources res, XmlResourceParser parser, String[] outError) throws XmlPullParserException, IOException { Permission perm = new Permission(owner, (String) null); TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestPermissionTree); if (!parsePackageItemInfo(owner, perm.info, outError, "", sa, true /*nameRequired*/, com.android.internal.R.styleable.AndroidManifestPermissionTree_name, com.android.internal.R.styleable.AndroidManifestPermissionTree_label, com.android.internal.R.styleable.AndroidManifestPermissionTree_icon, com.android.internal.R.styleable.AndroidManifestPermissionTree_roundIcon, com.android.internal.R.styleable.AndroidManifestPermissionTree_logo, com.android.internal.R.styleable.AndroidManifestPermissionTree_banner)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } sa.recycle(); int index = perm.info.name.indexOf('.'); if (index > 0) { index = perm.info.name.indexOf('.', index+1); } if (index < 0) { outError[0] = " name has less than three segments: " + perm.info.name; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } perm.info.descriptionRes = 0; perm.info.requestRes = 0; perm.info.protectionLevel = PermissionInfo.PROTECTION_NORMAL; perm.tree = true; if (!parseAllMetaData(res, parser, "", perm, outError)) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.permissions.add(perm); return true; } private Instrumentation parseInstrumentation(Package owner, Resources res, XmlResourceParser parser, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestInstrumentation); if (mParseInstrumentationArgs == null) { mParseInstrumentationArgs = new ParsePackageItemArgs(owner, outError, com.android.internal.R.styleable.AndroidManifestInstrumentation_name, com.android.internal.R.styleable.AndroidManifestInstrumentation_label, com.android.internal.R.styleable.AndroidManifestInstrumentation_icon, com.android.internal.R.styleable.AndroidManifestInstrumentation_roundIcon, com.android.internal.R.styleable.AndroidManifestInstrumentation_logo, com.android.internal.R.styleable.AndroidManifestInstrumentation_banner); mParseInstrumentationArgs.tag = ""; } mParseInstrumentationArgs.sa = sa; Instrumentation a = new Instrumentation(mParseInstrumentationArgs, new InstrumentationInfo()); if (outError[0] != null) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } String str; // Note: don't allow this value to be a reference to a resource // that may change. str = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestInstrumentation_targetPackage); a.info.targetPackage = str != null ? str.intern() : null; str = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestInstrumentation_targetProcesses); a.info.targetProcesses = str != null ? str.intern() : null; a.info.handleProfiling = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestInstrumentation_handleProfiling, false); a.info.functionalTest = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestInstrumentation_functionalTest, false); sa.recycle(); if (a.info.targetPackage == null) { outError[0] = " does not specify targetPackage"; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } if (!parseAllMetaData(res, parser, "", a, outError)) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } owner.instrumentation.add(a); return a; } /** * Parse the {@code application} XML tree at the current parse location in a * base APK manifest. *

* When adding new features, carefully consider if they should also be * supported by split APKs. */ @UnsupportedAppUsage private boolean parseBaseApplication(Package owner, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { final ApplicationInfo ai = owner.applicationInfo; final String pkgName = owner.applicationInfo.packageName; TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestApplication); ai.iconRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_icon, 0); ai.roundIconRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_roundIcon, 0); if (!parsePackageItemInfo(owner, ai, outError, "", sa, false /*nameRequired*/, com.android.internal.R.styleable.AndroidManifestApplication_name, com.android.internal.R.styleable.AndroidManifestApplication_label, com.android.internal.R.styleable.AndroidManifestApplication_icon, com.android.internal.R.styleable.AndroidManifestApplication_roundIcon, com.android.internal.R.styleable.AndroidManifestApplication_logo, com.android.internal.R.styleable.AndroidManifestApplication_banner)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } if (ai.name != null) { ai.className = ai.name; } String manageSpaceActivity = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestApplication_manageSpaceActivity, Configuration.NATIVE_CONFIG_VERSION); if (manageSpaceActivity != null) { ai.manageSpaceActivityName = buildClassName(pkgName, manageSpaceActivity, outError); } boolean allowBackup = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_allowBackup, true); if (allowBackup) { ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP; // backupAgent, killAfterRestore, fullBackupContent, backupInForeground, // and restoreAnyVersion are only relevant if backup is possible for the // given application. String backupAgent = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestApplication_backupAgent, Configuration.NATIVE_CONFIG_VERSION); if (backupAgent != null) { ai.backupAgentName = buildClassName(pkgName, backupAgent, outError); if (DEBUG_BACKUP) { Slog.v(TAG, "android:backupAgent = " + ai.backupAgentName + " from " + pkgName + "+" + backupAgent); } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_killAfterRestore, true)) { ai.flags |= ApplicationInfo.FLAG_KILL_AFTER_RESTORE; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_restoreAnyVersion, false)) { ai.flags |= ApplicationInfo.FLAG_RESTORE_ANY_VERSION; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_fullBackupOnly, false)) { ai.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_backupInForeground, false)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND; } } TypedValue v = sa.peekValue( com.android.internal.R.styleable.AndroidManifestApplication_fullBackupContent); if (v != null && (ai.fullBackupContent = v.resourceId) == 0) { if (DEBUG_BACKUP) { Slog.v(TAG, "fullBackupContent specified as boolean=" + (v.data == 0 ? "false" : "true")); } // "false" => -1, "true" => 0 ai.fullBackupContent = (v.data == 0 ? -1 : 0); } if (DEBUG_BACKUP) { Slog.v(TAG, "fullBackupContent=" + ai.fullBackupContent + " for " + pkgName); } } ai.theme = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_theme, 0); ai.descriptionRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_description, 0); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_persistent, false)) { // Check if persistence is based on a feature being present final String requiredFeature = sa.getNonResourceString(com.android.internal.R.styleable .AndroidManifestApplication_persistentWhenFeatureAvailable); if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) { ai.flags |= ApplicationInfo.FLAG_PERSISTENT; } } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers, false)) { owner.mRequiredForAllUsers = true; } String restrictedAccountType = sa.getString(com.android.internal.R.styleable .AndroidManifestApplication_restrictedAccountType); if (restrictedAccountType != null && restrictedAccountType.length() > 0) { owner.mRestrictedAccountType = restrictedAccountType; } String requiredAccountType = sa.getString(com.android.internal.R.styleable .AndroidManifestApplication_requiredAccountType); if (requiredAccountType != null && requiredAccountType.length() > 0) { owner.mRequiredAccountType = requiredAccountType; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_debuggable, false)) { ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE; // Debuggable implies profileable ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_vmSafeMode, false)) { ai.flags |= ApplicationInfo.FLAG_VM_SAFE_MODE; } owner.baseHardwareAccelerated = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated, owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH); if (owner.baseHardwareAccelerated) { ai.flags |= ApplicationInfo.FLAG_HARDWARE_ACCELERATED; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_hasCode, true)) { ai.flags |= ApplicationInfo.FLAG_HAS_CODE; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_allowTaskReparenting, false)) { ai.flags |= ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_allowClearUserData, true)) { ai.flags |= ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA; } // The parent package controls installation, hence specify test only installs. if (owner.parentPackage == null) { if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_testOnly, false)) { ai.flags |= ApplicationInfo.FLAG_TEST_ONLY; } } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_largeHeap, false)) { ai.flags |= ApplicationInfo.FLAG_LARGE_HEAP; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_usesCleartextTraffic, owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P)) { ai.flags |= ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_supportsRtl, false /* default is no RTL support*/)) { ai.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_multiArch, false)) { ai.flags |= ApplicationInfo.FLAG_MULTIARCH; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_extractNativeLibs, true)) { ai.flags |= ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS; } if (sa.getBoolean( R.styleable.AndroidManifestApplication_useEmbeddedDex, false)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_USE_EMBEDDED_DEX; } if (sa.getBoolean( R.styleable.AndroidManifestApplication_defaultToDeviceProtectedStorage, false)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE; } if (sa.getBoolean( R.styleable.AndroidManifestApplication_directBootAware, false)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE; } if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) { if (sa.getBoolean(R.styleable.AndroidManifestApplication_resizeableActivity, true)) { ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE; } else { ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE; } } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) { ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; } if (sa.getBoolean( com.android.internal.R.styleable .AndroidManifestApplication_allowClearUserDataOnFailedRestore, true)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE; } if (sa.getBoolean( R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE; } if (sa.getBoolean( R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE; } ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0); ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0); ai.networkSecurityConfigRes = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig, 0); ai.category = sa.getInt( com.android.internal.R.styleable.AndroidManifestApplication_appCategory, ApplicationInfo.CATEGORY_UNDEFINED); String str; str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestApplication_permission, 0); ai.permission = (str != null && str.length() > 0) ? str.intern() : null; if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) { str = sa.getNonConfigurationString( com.android.internal.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( com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity); } ai.taskAffinity = buildTaskAffinityName(ai.packageName, ai.packageName, str, outError); String factory = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestApplication_appComponentFactory); if (factory != null) { ai.appComponentFactory = buildClassName(ai.packageName, factory, outError); } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_usesNonSdkApi, false)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_hasFragileUserData, false)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA; } if (outError[0] == null) { CharSequence pname; if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) { pname = sa.getNonConfigurationString( com.android.internal.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( com.android.internal.R.styleable.AndroidManifestApplication_process); } ai.processName = buildProcessName(ai.packageName, null, pname, flags, mSeparateProcesses, outError); ai.enabled = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_enabled, true); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_isGame, false)) { ai.flags |= ApplicationInfo.FLAG_IS_GAME; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState, false)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE; // A heavy-weight application can not be in a custom process. // We can do direct compare because we intern all strings. if (ai.processName != null && !ai.processName.equals(ai.packageName)) { outError[0] = "cantSaveState applications can not use custom processes"; } } } ai.uiOptions = sa.getInt( com.android.internal.R.styleable.AndroidManifestApplication_uiOptions, 0); ai.classLoaderName = sa.getString( com.android.internal.R.styleable.AndroidManifestApplication_classLoader); if (ai.classLoaderName != null && !ClassLoaderFactory.isValidClassLoaderName(ai.classLoaderName)) { outError[0] = "Invalid class loader name: " + ai.classLoaderName; } ai.zygotePreloadName = sa.getString( com.android.internal.R.styleable.AndroidManifestApplication_zygotePreloadName); sa.recycle(); if (outError[0] != null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } final int innerDepth = parser.getDepth(); // IMPORTANT: These must only be cached for a single to avoid components // getting added to the wrong package. final CachedComponentArgs cachedArgs = new CachedComponentArgs(); 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(); if (tagName.equals("activity")) { Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false, owner.baseHardwareAccelerated); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } hasActivityOrder |= (a.order != 0); owner.activities.add(a); } else if (tagName.equals("receiver")) { Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, true, false); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } hasReceiverOrder |= (a.order != 0); owner.receivers.add(a); } else if (tagName.equals("service")) { Service s = parseService(owner, res, parser, flags, outError, cachedArgs); if (s == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } hasServiceOrder |= (s.order != 0); owner.services.add(s); } else if (tagName.equals("provider")) { Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs); if (p == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.providers.add(p); } else if (tagName.equals("activity-alias")) { Activity a = parseActivityAlias(owner, res, parser, flags, outError, cachedArgs); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } hasActivityOrder |= (a.order != 0); owner.activities.add(a); } else if (parser.getName().equals("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) if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData, outError)) == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } } else if (tagName.equals("static-library")) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestStaticLibrary); // Note: don't allow this value to be a reference to a resource // that may change. final String lname = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestStaticLibrary_name); final int version = sa.getInt( com.android.internal.R.styleable.AndroidManifestStaticLibrary_version, -1); final int versionMajor = sa.getInt( com.android.internal.R.styleable.AndroidManifestStaticLibrary_versionMajor, 0); sa.recycle(); // Since the app canot run without a static lib - fail if malformed if (lname == null || version < 0) { outError[0] = "Bad static-library declaration name: " + lname + " version: " + version; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; XmlUtils.skipCurrentTag(parser); return false; } if (owner.mSharedUserId != null) { outError[0] = "sharedUserId not allowed in static shared library"; mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID; XmlUtils.skipCurrentTag(parser); return false; } if (owner.staticSharedLibName != null) { outError[0] = "Multiple static-shared libs for package " + pkgName; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; XmlUtils.skipCurrentTag(parser); return false; } owner.staticSharedLibName = lname.intern(); if (version >= 0) { owner.staticSharedLibVersion = PackageInfo.composeLongVersionCode(versionMajor, version); } else { owner.staticSharedLibVersion = version; } ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY; XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("library")) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestLibrary); // Note: don't allow this value to be a reference to a resource // that may change. String lname = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestLibrary_name); sa.recycle(); if (lname != null) { lname = lname.intern(); if (!ArrayUtils.contains(owner.libraryNames, lname)) { owner.libraryNames = ArrayUtils.add( owner.libraryNames, lname); } } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("uses-static-library")) { if (!parseUsesStaticLibrary(owner, res, parser, outError)) { return false; } } else if (tagName.equals("uses-library")) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestUsesLibrary); // Note: don't allow this value to be a reference to a resource // that may change. String lname = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestUsesLibrary_name); boolean req = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestUsesLibrary_required, true); sa.recycle(); if (lname != null) { lname = lname.intern(); if (req) { owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname); } else { owner.usesOptionalLibraries = ArrayUtils.add( owner.usesOptionalLibraries, lname); } } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("uses-package")) { // Dependencies for app installers; we don't currently try to // enforce this. XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("profileable")) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestProfileable); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProfileable_shell, false)) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL; } XmlUtils.skipCurrentTag(parser); } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under : " + tagName + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } else { outError[0] = "Bad element under : " + tagName; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } } } if (TextUtils.isEmpty(owner.staticSharedLibName)) { // Add a hidden app detail activity to normal apps which forwards user to App Details // page. Activity a = generateAppDetailsHiddenActivity(owner, flags, outError, owner.baseHardwareAccelerated); owner.activities.add(a); } if (hasActivityOrder) { Collections.sort(owner.activities, (a1, a2) -> Integer.compare(a2.order, a1.order)); } if (hasReceiverOrder) { Collections.sort(owner.receivers, (r1, r2) -> Integer.compare(r2.order, r1.order)); } if (hasServiceOrder) { Collections.sort(owner.services, (s1, s2) -> Integer.compare(s2.order, s1.order)); } // 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(owner); setMinAspectRatio(owner); if (hasDomainURLs(owner)) { owner.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS; } else { owner.applicationInfo.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS; } return true; } /** * Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI */ private static boolean hasDomainURLs(Package pkg) { if (pkg == null || pkg.activities == null) return false; final ArrayList activities = pkg.activities; final int countActivities = activities.size(); for (int n=0; n filters = activity.intents; if (filters == null) continue; final int countFilters = filters.size(); for (int m=0; msplit APK manifest. *

* Note that split APKs have many more restrictions on what they're capable * of doing, so many valid features of a base APK have been carefully * omitted here. */ private boolean parseSplitApplication(Package owner, Resources res, XmlResourceParser parser, int flags, int splitIndex, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestApplication); if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_hasCode, true)) { owner.splitFlags[splitIndex] |= ApplicationInfo.FLAG_HAS_CODE; } final String classLoaderName = sa.getString( com.android.internal.R.styleable.AndroidManifestApplication_classLoader); if (classLoaderName == null || ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) { owner.applicationInfo.splitClassLoaderNames[splitIndex] = classLoaderName; } else { outError[0] = "Invalid class loader name: " + classLoaderName; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } 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; } ComponentInfo parsedComponent = null; // IMPORTANT: These must only be cached for a single to avoid components // getting added to the wrong package. final CachedComponentArgs cachedArgs = new CachedComponentArgs(); String tagName = parser.getName(); if (tagName.equals("activity")) { Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false, owner.baseHardwareAccelerated); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.activities.add(a); parsedComponent = a.info; } else if (tagName.equals("receiver")) { Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, true, false); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.receivers.add(a); parsedComponent = a.info; } else if (tagName.equals("service")) { Service s = parseService(owner, res, parser, flags, outError, cachedArgs); if (s == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.services.add(s); parsedComponent = s.info; } else if (tagName.equals("provider")) { Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs); if (p == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.providers.add(p); parsedComponent = p.info; } else if (tagName.equals("activity-alias")) { Activity a = parseActivityAlias(owner, res, parser, flags, outError, cachedArgs); if (a == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.activities.add(a); parsedComponent = a.info; } else if (parser.getName().equals("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) if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData, outError)) == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } } else if (tagName.equals("uses-static-library")) { if (!parseUsesStaticLibrary(owner, res, parser, outError)) { return false; } } else if (tagName.equals("uses-library")) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestUsesLibrary); // Note: don't allow this value to be a reference to a resource // that may change. String lname = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestUsesLibrary_name); boolean req = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestUsesLibrary_required, true); sa.recycle(); if (lname != null) { lname = lname.intern(); if (req) { // Upgrade to treat as stronger constraint owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname); owner.usesOptionalLibraries = ArrayUtils.remove( owner.usesOptionalLibraries, lname); } else { // Ignore if someone already defined as required if (!ArrayUtils.contains(owner.usesLibraries, lname)) { owner.usesOptionalLibraries = ArrayUtils.add( owner.usesOptionalLibraries, lname); } } } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("uses-package")) { // Dependencies for app installers; we don't currently try to // enforce this. XmlUtils.skipCurrentTag(parser); } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under : " + tagName + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } else { outError[0] = "Bad element under : " + tagName; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } } if (parsedComponent != null && parsedComponent.splitName == 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.splitName = owner.splitNames[splitIndex]; } } return true; } private static boolean parsePackageItemInfo(Package owner, 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(owner.applicationInfo.packageName, name, outError); 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 = 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 = owner.packageName; return true; } /** * 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. */ private @NonNull PackageParser.Activity generateAppDetailsHiddenActivity( PackageParser.Package owner, int flags, String[] outError, boolean hardwareAccelerated) { // Build custom App Details activity info instead of parsing it from xml Activity a = new Activity(owner, PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME, new ActivityInfo()); a.owner = owner; a.setPackageName(owner.packageName); a.info.theme = android.R.style.Theme_NoDisplay; a.info.exported = true; a.info.name = PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME; a.info.processName = owner.applicationInfo.processName; a.info.uiOptions = a.info.applicationInfo.uiOptions; a.info.taskAffinity = buildTaskAffinityName(owner.packageName, owner.packageName, ":app_details", outError); a.info.enabled = true; a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE; a.info.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE; a.info.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic(); a.info.configChanges = getActivityConfigChanges(0, 0); a.info.softInputMode = 0; a.info.persistableMode = ActivityInfo.PERSIST_NEVER; a.info.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; a.info.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; a.info.lockTaskLaunchMode = 0; a.info.encryptionAware = a.info.directBootAware = false; a.info.rotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; a.info.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; if (hardwareAccelerated) { a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED; } return a; } private Activity parseActivity(Package owner, Resources res, XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs, boolean receiver, boolean hardwareAccelerated) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity); if (cachedArgs.mActivityArgs == null) { cachedArgs.mActivityArgs = new ParseComponentArgs(owner, outError, R.styleable.AndroidManifestActivity_name, R.styleable.AndroidManifestActivity_label, R.styleable.AndroidManifestActivity_icon, R.styleable.AndroidManifestActivity_roundIcon, R.styleable.AndroidManifestActivity_logo, R.styleable.AndroidManifestActivity_banner, mSeparateProcesses, R.styleable.AndroidManifestActivity_process, R.styleable.AndroidManifestActivity_description, R.styleable.AndroidManifestActivity_enabled); } cachedArgs.mActivityArgs.tag = receiver ? "" : ""; cachedArgs.mActivityArgs.sa = sa; cachedArgs.mActivityArgs.flags = flags; Activity a = new Activity(cachedArgs.mActivityArgs, new ActivityInfo()); if (outError[0] != null) { sa.recycle(); return null; } boolean setExported = sa.hasValue(R.styleable.AndroidManifestActivity_exported); if (setExported) { a.info.exported = sa.getBoolean(R.styleable.AndroidManifestActivity_exported, false); } a.info.theme = sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0); a.info.uiOptions = sa.getInt(R.styleable.AndroidManifestActivity_uiOptions, a.info.applicationInfo.uiOptions); String parentName = sa.getNonConfigurationString( R.styleable.AndroidManifestActivity_parentActivityName, Configuration.NATIVE_CONFIG_VERSION); if (parentName != null) { String parentClassName = buildClassName(a.info.packageName, parentName, outError); if (outError[0] == null) { a.info.parentActivityName = parentClassName; } else { Log.e(TAG, "Activity " + a.info.name + " specified invalid parentActivityName " + parentName); outError[0] = null; } } String str; str = sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_permission, 0); if (str == null) { a.info.permission = owner.applicationInfo.permission; } else { a.info.permission = str.length() > 0 ? str.toString().intern() : null; } str = sa.getNonConfigurationString( R.styleable.AndroidManifestActivity_taskAffinity, Configuration.NATIVE_CONFIG_VERSION); a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName, owner.applicationInfo.taskAffinity, str, outError); a.info.splitName = sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_splitName, 0); a.info.flags = 0; if (sa.getBoolean( R.styleable.AndroidManifestActivity_multiprocess, false)) { a.info.flags |= ActivityInfo.FLAG_MULTIPROCESS; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnTaskLaunch, false)) { a.info.flags |= ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_clearTaskOnLaunch, false)) { a.info.flags |= ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_noHistory, false)) { a.info.flags |= ActivityInfo.FLAG_NO_HISTORY; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysRetainTaskState, false)) { a.info.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_stateNotNeeded, false)) { a.info.flags |= ActivityInfo.FLAG_STATE_NOT_NEEDED; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_excludeFromRecents, false)) { a.info.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowTaskReparenting, (owner.applicationInfo.flags&ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING) != 0)) { a.info.flags |= ActivityInfo.FLAG_ALLOW_TASK_REPARENTING; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, false)) { a.info.flags |= ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_showOnLockScreen, false) || sa.getBoolean(R.styleable.AndroidManifestActivity_showForAllUsers, false)) { a.info.flags |= ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_immersive, false)) { a.info.flags |= ActivityInfo.FLAG_IMMERSIVE; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_systemUserOnly, false)) { a.info.flags |= ActivityInfo.FLAG_SYSTEM_USER_ONLY; } if (!receiver) { if (sa.getBoolean(R.styleable.AndroidManifestActivity_hardwareAccelerated, hardwareAccelerated)) { a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED; } a.info.launchMode = sa.getInt( R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE); a.info.documentLaunchMode = sa.getInt( R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE); a.info.maxRecents = sa.getInt( R.styleable.AndroidManifestActivity_maxRecents, ActivityTaskManager.getDefaultAppRecentsLimitStatic()); a.info.configChanges = getActivityConfigChanges( sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0), sa.getInt(R.styleable.AndroidManifestActivity_recreateOnConfigChanges, 0)); a.info.softInputMode = sa.getInt( R.styleable.AndroidManifestActivity_windowSoftInputMode, 0); a.info.persistableMode = sa.getInteger( R.styleable.AndroidManifestActivity_persistableMode, ActivityInfo.PERSIST_ROOT_ONLY); if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowEmbedded, false)) { a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_autoRemoveFromRecents, false)) { a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_relinquishTaskIdentity, false)) { a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_resumeWhilePausing, false)) { a.info.flags |= ActivityInfo.FLAG_RESUME_WHILE_PAUSING; } a.info.screenOrientation = sa.getInt( R.styleable.AndroidManifestActivity_screenOrientation, SCREEN_ORIENTATION_UNSPECIFIED); setActivityResizeMode(a.info, sa, owner); if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, false)) { a.info.flags |= FLAG_SUPPORTS_PICTURE_IN_PICTURE; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysFocusable, false)) { a.info.flags |= FLAG_ALWAYS_FOCUSABLE; } if (sa.hasValue(R.styleable.AndroidManifestActivity_maxAspectRatio) && sa.getType(R.styleable.AndroidManifestActivity_maxAspectRatio) == TypedValue.TYPE_FLOAT) { a.setMaxAspectRatio(sa.getFloat(R.styleable.AndroidManifestActivity_maxAspectRatio, 0 /*default*/)); } if (sa.hasValue(R.styleable.AndroidManifestActivity_minAspectRatio) && sa.getType(R.styleable.AndroidManifestActivity_minAspectRatio) == TypedValue.TYPE_FLOAT) { a.setMinAspectRatio(sa.getFloat(R.styleable.AndroidManifestActivity_minAspectRatio, 0 /*default*/)); } a.info.lockTaskLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0); a.info.encryptionAware = a.info.directBootAware = sa.getBoolean( R.styleable.AndroidManifestActivity_directBootAware, false); a.info.requestedVrComponent = sa.getString(R.styleable.AndroidManifestActivity_enableVrMode); a.info.rotationAnimation = sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, ROTATION_ANIMATION_UNSPECIFIED); a.info.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT); if (sa.getBoolean(R.styleable.AndroidManifestActivity_showWhenLocked, false)) { a.info.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_turnScreenOn, false)) { a.info.flags |= ActivityInfo.FLAG_TURN_SCREEN_ON; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_inheritShowWhenLocked, false)) { a.info.privateFlags |= ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED; } } else { a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE; a.info.configChanges = 0; if (sa.getBoolean(R.styleable.AndroidManifestActivity_singleUser, false)) { a.info.flags |= ActivityInfo.FLAG_SINGLE_USER; } a.info.encryptionAware = a.info.directBootAware = sa.getBoolean( R.styleable.AndroidManifestActivity_directBootAware, false); } if (a.info.directBootAware) { owner.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } // can't make this final; we may set it later via meta-data boolean visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false); if (visibleToEphemeral) { a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; owner.visibleToInstantApps = true; } sa.recycle(); if (receiver && (owner.applicationInfo.privateFlags &ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { // A heavy-weight application can not have receives in its main process // We can do direct compare because we intern all strings. if (a.info.processName == owner.packageName) { outError[0] = "Heavy-weight applications can not have receivers in main process"; } } if (outError[0] != null) { return null; } 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-filter")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); if (!parseIntent(res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/, intent, outError)) { return null; } if (intent.countActions() == 0) { Slog.w(TAG, "No actions in intent filter at " + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { a.order = Math.max(intent.getOrder(), a.order); a.intents.add(intent); } // adjust activity flags when we implicitly expose it via a browsable filter final int visibility = visibleToEphemeral ? IntentFilter.VISIBILITY_EXPLICIT : !receiver && isImplicitlyExposedIntent(intent) ? IntentFilter.VISIBILITY_IMPLICIT : IntentFilter.VISIBILITY_NONE; intent.setVisibilityToInstantApp(visibility); if (intent.isVisibleToInstantApp()) { a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; } if (intent.isImplicitlyVisibleToInstantApp()) { a.info.flags |= ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP; } if (LOG_UNSAFE_BROADCASTS && receiver && (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O)) { for (int i = 0; i < intent.countActions(); i++) { final String action = intent.getAction(i); if (action == null || !action.startsWith("android.")) continue; if (!SAFE_BROADCASTS.contains(action)) { Slog.w(TAG, "Broadcast " + action + " may never be delivered to " + owner.packageName + " as requested at: " + parser.getPositionDescription()); } } } } else if (!receiver && parser.getName().equals("preferred")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); if (!parseIntent(res, parser, false /*allowGlobs*/, false /*allowAutoVerify*/, intent, outError)) { return null; } if (intent.countActions() == 0) { Slog.w(TAG, "No actions in preferred at " + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { if (owner.preferredActivityFilters == null) { owner.preferredActivityFilters = new ArrayList(); } owner.preferredActivityFilters.add(intent); } // adjust activity flags when we implicitly expose it via a browsable filter final int visibility = visibleToEphemeral ? IntentFilter.VISIBILITY_EXPLICIT : !receiver && isImplicitlyExposedIntent(intent) ? IntentFilter.VISIBILITY_IMPLICIT : IntentFilter.VISIBILITY_NONE; intent.setVisibilityToInstantApp(visibility); if (intent.isVisibleToInstantApp()) { a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; } if (intent.isImplicitlyVisibleToInstantApp()) { a.info.flags |= ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP; } } else if (parser.getName().equals("meta-data")) { if ((a.metaData = parseMetaData(res, parser, a.metaData, outError)) == null) { return null; } } else if (!receiver && parser.getName().equals("layout")) { parseLayout(res, parser, a); } else { if (!RIGID_PARSER) { Slog.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); if (receiver) { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); } XmlUtils.skipCurrentTag(parser); continue; } else { if (receiver) { outError[0] = "Bad element under : " + parser.getName(); } else { outError[0] = "Bad element under : " + parser.getName(); } return null; } } } if (!setExported) { a.info.exported = a.intents.size() > 0; } return a; } private void setActivityResizeMode(ActivityInfo aInfo, TypedArray sa, Package owner) { final boolean appExplicitDefault = (owner.applicationInfo.privateFlags & (PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE | PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE)) != 0; if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity) || appExplicitDefault) { // Activity or app explicitly set if it is resizeable or not; final boolean appResizeable = (owner.applicationInfo.privateFlags & PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE) != 0; if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity, appResizeable)) { aInfo.resizeMode = RESIZE_MODE_RESIZEABLE; } else { aInfo.resizeMode = RESIZE_MODE_UNRESIZEABLE; } return; } if ((owner.applicationInfo.privateFlags & PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) != 0) { // The activity or app didn't explicitly set the resizing option, however we want to // make it resize due to the sdk version it is targeting. aInfo.resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; return; } // resize preference isn't set and target sdk version doesn't support resizing apps by // default. For the app to be resizeable if it isn't fixed orientation or immersive. if (aInfo.isFixedOrientationPortrait()) { aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; } else if (aInfo.isFixedOrientationLandscape()) { aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; } else if (aInfo.isFixedOrientation()) { aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; } else { aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE; } } /** * Sets every the max aspect ratio of every child activity that doesn't already have an aspect * ratio set. */ private void setMaxAspectRatio(Package owner) { // 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 = owner.applicationInfo.targetSdkVersion < O ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0; if (owner.applicationInfo.maxAspectRatio != 0) { // Use the application max aspect ration as default if set. maxAspectRatio = owner.applicationInfo.maxAspectRatio; } else if (owner.mAppMetaData != null && owner.mAppMetaData.containsKey(METADATA_MAX_ASPECT_RATIO)) { maxAspectRatio = owner.mAppMetaData.getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio); } for (Activity activity : owner.activities) { // 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(METADATA_MAX_ASPECT_RATIO, maxAspectRatio) : maxAspectRatio; activity.setMaxAspectRatio(activityAspectRatio); } } /** * Sets every the min aspect ratio of every child activity that doesn't already have an aspect * ratio set. */ private void setMinAspectRatio(Package owner) { final float minAspectRatio; if (owner.applicationInfo.minAspectRatio != 0) { // Use the application max aspect ration as default if set. minAspectRatio = owner.applicationInfo.minAspectRatio; } 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 = owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q ? 0 : (mCallback != null && mCallback.hasFeature(FEATURE_WATCH)) ? DEFAULT_PRE_Q_MIN_ASPECT_RATIO_WATCH : DEFAULT_PRE_Q_MIN_ASPECT_RATIO; } for (Activity activity : owner.activities) { if (activity.hasMinAspectRatio()) { continue; } activity.setMinAspectRatio(minAspectRatio); } } /** * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml. * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from * AndroidManifest.xml. * @hide Exposed for unit testing only. */ @TestApi public static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) { return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK); } private void parseLayout(Resources res, AttributeSet attrs, Activity a) { TypedArray sw = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestLayout); int width = -1; float widthFraction = -1f; int height = -1; float heightFraction = -1f; final int widthType = sw.getType( com.android.internal.R.styleable.AndroidManifestLayout_defaultWidth); if (widthType == TypedValue.TYPE_FRACTION) { widthFraction = sw.getFraction( com.android.internal.R.styleable.AndroidManifestLayout_defaultWidth, 1, 1, -1); } else if (widthType == TypedValue.TYPE_DIMENSION) { width = sw.getDimensionPixelSize( com.android.internal.R.styleable.AndroidManifestLayout_defaultWidth, -1); } final int heightType = sw.getType( com.android.internal.R.styleable.AndroidManifestLayout_defaultHeight); if (heightType == TypedValue.TYPE_FRACTION) { heightFraction = sw.getFraction( com.android.internal.R.styleable.AndroidManifestLayout_defaultHeight, 1, 1, -1); } else if (heightType == TypedValue.TYPE_DIMENSION) { height = sw.getDimensionPixelSize( com.android.internal.R.styleable.AndroidManifestLayout_defaultHeight, -1); } int gravity = sw.getInt( com.android.internal.R.styleable.AndroidManifestLayout_gravity, Gravity.CENTER); int minWidth = sw.getDimensionPixelSize( com.android.internal.R.styleable.AndroidManifestLayout_minWidth, -1); int minHeight = sw.getDimensionPixelSize( com.android.internal.R.styleable.AndroidManifestLayout_minHeight, -1); sw.recycle(); a.info.windowLayout = new ActivityInfo.WindowLayout(width, widthFraction, height, heightFraction, gravity, minWidth, minHeight); } private Activity parseActivityAlias(Package owner, Resources res, XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestActivityAlias); String targetActivity = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity, Configuration.NATIVE_CONFIG_VERSION); if (targetActivity == null) { outError[0] = " does not specify android:targetActivity"; sa.recycle(); return null; } targetActivity = buildClassName(owner.applicationInfo.packageName, targetActivity, outError); if (targetActivity == null) { sa.recycle(); return null; } if (cachedArgs.mActivityAliasArgs == null) { cachedArgs.mActivityAliasArgs = new ParseComponentArgs(owner, outError, com.android.internal.R.styleable.AndroidManifestActivityAlias_name, com.android.internal.R.styleable.AndroidManifestActivityAlias_label, com.android.internal.R.styleable.AndroidManifestActivityAlias_icon, com.android.internal.R.styleable.AndroidManifestActivityAlias_roundIcon, com.android.internal.R.styleable.AndroidManifestActivityAlias_logo, com.android.internal.R.styleable.AndroidManifestActivityAlias_banner, mSeparateProcesses, 0, com.android.internal.R.styleable.AndroidManifestActivityAlias_description, com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled); cachedArgs.mActivityAliasArgs.tag = ""; } cachedArgs.mActivityAliasArgs.sa = sa; cachedArgs.mActivityAliasArgs.flags = flags; Activity target = null; final int NA = owner.activities.size(); for (int i=0; i 0 ? str.toString().intern() : null; } String parentName = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestActivityAlias_parentActivityName, Configuration.NATIVE_CONFIG_VERSION); if (parentName != null) { String parentClassName = buildClassName(a.info.packageName, parentName, outError); if (outError[0] == null) { a.info.parentActivityName = parentClassName; } else { Log.e(TAG, "Activity alias " + a.info.name + " specified invalid parentActivityName " + parentName); outError[0] = null; } } // TODO add visibleToInstantApps attribute to activity alias final boolean visibleToEphemeral = ((a.info.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0); sa.recycle(); if (outError[0] != null) { return null; } 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-filter")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); if (!parseIntent(res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/, intent, outError)) { return null; } if (intent.countActions() == 0) { Slog.w(TAG, "No actions in intent filter at " + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { a.order = Math.max(intent.getOrder(), a.order); a.intents.add(intent); } // adjust activity flags when we implicitly expose it via a browsable filter final int visibility = visibleToEphemeral ? IntentFilter.VISIBILITY_EXPLICIT : isImplicitlyExposedIntent(intent) ? IntentFilter.VISIBILITY_IMPLICIT : IntentFilter.VISIBILITY_NONE; intent.setVisibilityToInstantApp(visibility); if (intent.isVisibleToInstantApp()) { a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP; } if (intent.isImplicitlyVisibleToInstantApp()) { a.info.flags |= ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP; } } else if (parser.getName().equals("meta-data")) { if ((a.metaData=parseMetaData(res, parser, a.metaData, outError)) == null) { return null; } } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } else { outError[0] = "Bad element under : " + parser.getName(); return null; } } } if (!setExported) { a.info.exported = a.intents.size() > 0; } return a; } private Provider parseProvider(Package owner, Resources res, XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestProvider); if (cachedArgs.mProviderArgs == null) { cachedArgs.mProviderArgs = new ParseComponentArgs(owner, outError, com.android.internal.R.styleable.AndroidManifestProvider_name, com.android.internal.R.styleable.AndroidManifestProvider_label, com.android.internal.R.styleable.AndroidManifestProvider_icon, com.android.internal.R.styleable.AndroidManifestProvider_roundIcon, com.android.internal.R.styleable.AndroidManifestProvider_logo, com.android.internal.R.styleable.AndroidManifestProvider_banner, mSeparateProcesses, com.android.internal.R.styleable.AndroidManifestProvider_process, com.android.internal.R.styleable.AndroidManifestProvider_description, com.android.internal.R.styleable.AndroidManifestProvider_enabled); cachedArgs.mProviderArgs.tag = ""; } cachedArgs.mProviderArgs.sa = sa; cachedArgs.mProviderArgs.flags = flags; Provider p = new Provider(cachedArgs.mProviderArgs, new ProviderInfo()); if (outError[0] != null) { sa.recycle(); return null; } boolean providerExportedDefault = false; if (owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) { // For compatibility, applications targeting API level 16 or lower // should have their content providers exported by default, unless they // specify otherwise. providerExportedDefault = true; } p.info.exported = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProvider_exported, providerExportedDefault); String cpname = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestProvider_authorities, 0); p.info.isSyncable = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProvider_syncable, false); String permission = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestProvider_permission, 0); String str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestProvider_readPermission, 0); if (str == null) { str = permission; } if (str == null) { p.info.readPermission = owner.applicationInfo.permission; } else { p.info.readPermission = str.length() > 0 ? str.toString().intern() : null; } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestProvider_writePermission, 0); if (str == null) { str = permission; } if (str == null) { p.info.writePermission = owner.applicationInfo.permission; } else { p.info.writePermission = str.length() > 0 ? str.toString().intern() : null; } p.info.grantUriPermissions = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProvider_grantUriPermissions, false); p.info.forceUriPermissions = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProvider_forceUriPermissions, false); p.info.multiprocess = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProvider_multiprocess, false); p.info.initOrder = sa.getInt( com.android.internal.R.styleable.AndroidManifestProvider_initOrder, 0); p.info.splitName = sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_splitName, 0); p.info.flags = 0; if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProvider_singleUser, false)) { p.info.flags |= ProviderInfo.FLAG_SINGLE_USER; } p.info.encryptionAware = p.info.directBootAware = sa.getBoolean( R.styleable.AndroidManifestProvider_directBootAware, false); if (p.info.directBootAware) { owner.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } final boolean visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToInstantApps, false); if (visibleToEphemeral) { p.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP; owner.visibleToInstantApps = true; } sa.recycle(); if ((owner.applicationInfo.privateFlags&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { // A heavy-weight application can not have providers in its main process // We can do direct compare because we intern all strings. if (p.info.processName == owner.packageName) { outError[0] = "Heavy-weight applications can not have providers in main process"; return null; } } if (cpname == null) { outError[0] = " does not include authorities attribute"; return null; } if (cpname.length() <= 0) { outError[0] = " has empty authorities attribute"; return null; } p.info.authority = cpname.intern(); if (!parseProviderTags( res, parser, visibleToEphemeral, p, outError)) { return null; } return p; } private boolean parseProviderTags(Resources res, XmlResourceParser parser, boolean visibleToEphemeral, Provider outInfo, String[] outError) throws XmlPullParserException, IOException { 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-filter")) { ProviderIntentInfo intent = new ProviderIntentInfo(outInfo); if (!parseIntent(res, parser, true /*allowGlobs*/, false /*allowAutoVerify*/, intent, outError)) { return false; } if (visibleToEphemeral) { intent.setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP; } outInfo.order = Math.max(intent.getOrder(), outInfo.order); outInfo.intents.add(intent); } else if (parser.getName().equals("meta-data")) { if ((outInfo.metaData=parseMetaData(res, parser, outInfo.metaData, outError)) == null) { return false; } } else if (parser.getName().equals("grant-uri-permission")) { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestGrantUriPermission); PatternMatcher pa = null; String str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestGrantUriPermission_path, 0); if (str != null) { pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 0); if (str != null) { pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPattern, 0); if (str != null) { pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB); } sa.recycle(); if (pa != null) { if (outInfo.info.uriPermissionPatterns == null) { outInfo.info.uriPermissionPatterns = new PatternMatcher[1]; outInfo.info.uriPermissionPatterns[0] = pa; } else { final int N = outInfo.info.uriPermissionPatterns.length; PatternMatcher[] newp = new PatternMatcher[N+1]; System.arraycopy(outInfo.info.uriPermissionPatterns, 0, newp, 0, N); newp[N] = pa; outInfo.info.uriPermissionPatterns = newp; } outInfo.info.grantUriPermissions = true; } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } else { outError[0] = "No path, pathPrefix, or pathPattern for "; return false; } } XmlUtils.skipCurrentTag(parser); } else if (parser.getName().equals("path-permission")) { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestPathPermission); PathPermission pa = null; String permission = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestPathPermission_permission, 0); String readPermission = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission, 0); if (readPermission == null) { readPermission = permission; } String writePermission = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission, 0); if (writePermission == null) { writePermission = permission; } boolean havePerm = false; if (readPermission != null) { readPermission = readPermission.intern(); havePerm = true; } if (writePermission != null) { writePermission = writePermission.intern(); havePerm = true; } if (!havePerm) { if (!RIGID_PARSER) { Slog.w(TAG, "No readPermission or writePermssion for : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } else { outError[0] = "No readPermission or writePermssion for "; return false; } } String path = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestPathPermission_path, 0); if (path != null) { pa = new PathPermission(path, PatternMatcher.PATTERN_LITERAL, readPermission, writePermission); } path = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix, 0); if (path != null) { pa = new PathPermission(path, PatternMatcher.PATTERN_PREFIX, readPermission, writePermission); } path = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern, 0); if (path != null) { pa = new PathPermission(path, PatternMatcher.PATTERN_SIMPLE_GLOB, readPermission, writePermission); } path = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestPathPermission_pathAdvancedPattern, 0); if (path != null) { pa = new PathPermission(path, PatternMatcher.PATTERN_ADVANCED_GLOB, readPermission, writePermission); } sa.recycle(); if (pa != null) { if (outInfo.info.pathPermissions == null) { outInfo.info.pathPermissions = new PathPermission[1]; outInfo.info.pathPermissions[0] = pa; } else { final int N = outInfo.info.pathPermissions.length; PathPermission[] newp = new PathPermission[N+1]; System.arraycopy(outInfo.info.pathPermissions, 0, newp, 0, N); newp[N] = pa; outInfo.info.pathPermissions = newp; } } else { if (!RIGID_PARSER) { Slog.w(TAG, "No path, pathPrefix, or pathPattern for : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } outError[0] = "No path, pathPrefix, or pathPattern for "; return false; } XmlUtils.skipCurrentTag(parser); } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } else { outError[0] = "Bad element under : " + parser.getName(); return false; } } } return true; } private Service parseService(Package owner, Resources res, XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestService); if (cachedArgs.mServiceArgs == null) { cachedArgs.mServiceArgs = new ParseComponentArgs(owner, outError, com.android.internal.R.styleable.AndroidManifestService_name, com.android.internal.R.styleable.AndroidManifestService_label, com.android.internal.R.styleable.AndroidManifestService_icon, com.android.internal.R.styleable.AndroidManifestService_roundIcon, com.android.internal.R.styleable.AndroidManifestService_logo, com.android.internal.R.styleable.AndroidManifestService_banner, mSeparateProcesses, com.android.internal.R.styleable.AndroidManifestService_process, com.android.internal.R.styleable.AndroidManifestService_description, com.android.internal.R.styleable.AndroidManifestService_enabled); cachedArgs.mServiceArgs.tag = ""; } cachedArgs.mServiceArgs.sa = sa; cachedArgs.mServiceArgs.flags = flags; Service s = new Service(cachedArgs.mServiceArgs, new ServiceInfo()); if (outError[0] != null) { sa.recycle(); return null; } boolean setExported = sa.hasValue( com.android.internal.R.styleable.AndroidManifestService_exported); if (setExported) { s.info.exported = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestService_exported, false); } String str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestService_permission, 0); if (str == null) { s.info.permission = owner.applicationInfo.permission; } else { s.info.permission = str.length() > 0 ? str.toString().intern() : null; } s.info.splitName = sa.getNonConfigurationString(R.styleable.AndroidManifestService_splitName, 0); s.info.mForegroundServiceType = sa.getInt( com.android.internal.R.styleable.AndroidManifestService_foregroundServiceType, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE); s.info.flags = 0; if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestService_stopWithTask, false)) { s.info.flags |= ServiceInfo.FLAG_STOP_WITH_TASK; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestService_isolatedProcess, false)) { s.info.flags |= ServiceInfo.FLAG_ISOLATED_PROCESS; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestService_externalService, false)) { s.info.flags |= ServiceInfo.FLAG_EXTERNAL_SERVICE; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestService_useAppZygote, false)) { s.info.flags |= ServiceInfo.FLAG_USE_APP_ZYGOTE; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestService_singleUser, false)) { s.info.flags |= ServiceInfo.FLAG_SINGLE_USER; } s.info.encryptionAware = s.info.directBootAware = sa.getBoolean( R.styleable.AndroidManifestService_directBootAware, false); if (s.info.directBootAware) { owner.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } boolean visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestService_visibleToInstantApps, false); if (visibleToEphemeral) { s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP; owner.visibleToInstantApps = true; } sa.recycle(); if ((owner.applicationInfo.privateFlags&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { // A heavy-weight application can not have services in its main process // We can do direct compare because we intern all strings. if (s.info.processName == owner.packageName) { outError[0] = "Heavy-weight applications can not have services in main process"; return null; } } 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-filter")) { ServiceIntentInfo intent = new ServiceIntentInfo(s); if (!parseIntent(res, parser, true /*allowGlobs*/, false /*allowAutoVerify*/, intent, outError)) { return null; } if (visibleToEphemeral) { intent.setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT); s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP; } s.order = Math.max(intent.getOrder(), s.order); s.intents.add(intent); } else if (parser.getName().equals("meta-data")) { if ((s.metaData=parseMetaData(res, parser, s.metaData, outError)) == null) { return null; } } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } else { outError[0] = "Bad element under : " + parser.getName(); return null; } } } if (!setExported) { s.info.exported = s.intents.size() > 0; } return s; } private boolean isImplicitlyExposedIntent(IntentInfo intent) { return intent.hasCategory(Intent.CATEGORY_BROWSABLE) || intent.hasAction(Intent.ACTION_SEND) || intent.hasAction(Intent.ACTION_SENDTO) || intent.hasAction(Intent.ACTION_SEND_MULTIPLE); } private boolean parseAllMetaData(Resources res, XmlResourceParser parser, String tag, Component outInfo, String[] outError) throws XmlPullParserException, IOException { 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("meta-data")) { if ((outInfo.metaData=parseMetaData(res, parser, outInfo.metaData, outError)) == null) { return false; } } else { if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under " + tag + ": " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } else { outError[0] = "Bad element under " + tag + ": " + parser.getName(); return false; } } } return true; } private Bundle parseMetaData(Resources res, XmlResourceParser parser, Bundle data, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestMetaData); if (data == null) { data = new Bundle(); } String name = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestMetaData_name, 0); if (name == null) { outError[0] = " requires an android:name attribute"; sa.recycle(); return null; } name = name.intern(); TypedValue v = sa.peekValue( com.android.internal.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( com.android.internal.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 (!RIGID_PARSER) { Slog.w(TAG, " only supports string, integer, float, color, boolean, and resource reference types: " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); } else { outError[0] = " only supports string, integer, float, color, boolean, and resource reference types"; data = null; } } } else { outError[0] = " requires an android:value or android:resource attribute"; data = null; } } sa.recycle(); XmlUtils.skipCurrentTag(parser); return data; } private static VerifierInfo parseVerifier(AttributeSet attrs) { String packageName = null; String encodedPublicKey = null; final int attrCount = attrs.getAttributeCount(); for (int i = 0; i < attrCount; i++) { final int attrResId = attrs.getAttributeNameResource(i); switch (attrResId) { case com.android.internal.R.attr.name: packageName = attrs.getAttributeValue(i); break; case com.android.internal.R.attr.publicKey: encodedPublicKey = attrs.getAttributeValue(i); break; } } if (packageName == null || packageName.length() == 0) { Slog.i(TAG, "verifier package name was null; skipping"); return null; } final PublicKey publicKey = parsePublicKey(encodedPublicKey); if (publicKey == null) { Slog.i(TAG, "Unable to parse verifier public key for " + packageName); return null; } return new VerifierInfo(packageName, publicKey); } public static final PublicKey parsePublicKey(final String encodedPublicKey) { if (encodedPublicKey == null) { Slog.w(TAG, "Could not parse null public key"); return null; } EncodedKeySpec keySpec; try { final byte[] encoded = Base64.decode(encodedPublicKey, Base64.DEFAULT); keySpec = new X509EncodedKeySpec(encoded); } catch (IllegalArgumentException e) { Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); return null; } /* First try the key as an RSA key. */ try { final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException e) { Slog.wtf(TAG, "Could not parse public key: RSA KeyFactory not included in build"); } catch (InvalidKeySpecException e) { // Not a RSA public key. } /* Now try it as a ECDSA key. */ try { final KeyFactory keyFactory = KeyFactory.getInstance("EC"); return keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException e) { Slog.wtf(TAG, "Could not parse public key: EC KeyFactory not included in build"); } catch (InvalidKeySpecException e) { // Not a ECDSA public key. } /* Now try it as a DSA key. */ try { final KeyFactory keyFactory = KeyFactory.getInstance("DSA"); return keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException e) { Slog.wtf(TAG, "Could not parse public key: DSA KeyFactory not included in build"); } catch (InvalidKeySpecException e) { // Not a DSA public key. } /* Not a supported key type */ return null; } private static final String ANDROID_RESOURCES = "http://schemas.android.com/apk/res/android"; private boolean parseIntent(Resources res, XmlResourceParser parser, boolean allowGlobs, boolean allowAutoVerify, IntentInfo outInfo, String[] outError) throws XmlPullParserException, IOException { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestIntentFilter); int priority = sa.getInt( com.android.internal.R.styleable.AndroidManifestIntentFilter_priority, 0); outInfo.setPriority(priority); int order = sa.getInt( com.android.internal.R.styleable.AndroidManifestIntentFilter_order, 0); outInfo.setOrder(order); TypedValue v = sa.peekValue( com.android.internal.R.styleable.AndroidManifestIntentFilter_label); if (v != null && (outInfo.labelRes=v.resourceId) == 0) { outInfo.nonLocalizedLabel = v.coerceToString(); } int roundIconVal = sUseRoundIcon ? sa.getResourceId( com.android.internal.R.styleable.AndroidManifestIntentFilter_roundIcon, 0) : 0; if (roundIconVal != 0) { outInfo.icon = roundIconVal; } else { outInfo.icon = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0); } outInfo.logo = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0); outInfo.banner = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestIntentFilter_banner, 0); if (allowAutoVerify) { outInfo.setAutoVerify(sa.getBoolean( com.android.internal.R.styleable.AndroidManifestIntentFilter_autoVerify, false)); } sa.recycle(); 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; } String nodeName = parser.getName(); if (nodeName.equals("action")) { String value = parser.getAttributeValue( ANDROID_RESOURCES, "name"); if (value == null || value == "") { outError[0] = "No value supplied for "; return false; } XmlUtils.skipCurrentTag(parser); outInfo.addAction(value); } else if (nodeName.equals("category")) { String value = parser.getAttributeValue( ANDROID_RESOURCES, "name"); if (value == null || value == "") { outError[0] = "No value supplied for "; return false; } XmlUtils.skipCurrentTag(parser); outInfo.addCategory(value); } else if (nodeName.equals("data")) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestData); String str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_mimeType, 0); if (str != null) { try { outInfo.addDataType(str); } catch (IntentFilter.MalformedMimeTypeException e) { outError[0] = e.toString(); sa.recycle(); return false; } } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_scheme, 0); if (str != null) { outInfo.addDataScheme(str); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_ssp, 0); if (str != null) { outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_LITERAL); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_sspPrefix, 0); if (str != null) { outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_PREFIX); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_sspPattern, 0); if (str != null) { if (!allowGlobs) { outError[0] = "sspPattern not allowed here; ssp must be literal"; return false; } outInfo.addDataSchemeSpecificPart(str, PatternMatcher.PATTERN_SIMPLE_GLOB); } String host = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_host, 0); String port = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_port, 0); if (host != null) { outInfo.addDataAuthority(host, port); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_path, 0); if (str != null) { outInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_pathPrefix, 0); if (str != null) { outInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_pathPattern, 0); if (str != null) { if (!allowGlobs) { outError[0] = "pathPattern not allowed here; path must be literal"; return false; } outInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB); } str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestData_pathAdvancedPattern, 0); if (str != null) { if (!allowGlobs) { outError[0] = "pathAdvancedPattern not allowed here; path must be literal"; return false; } outInfo.addDataPath(str, PatternMatcher.PATTERN_ADVANCED_GLOB); } sa.recycle(); XmlUtils.skipCurrentTag(parser); } else if (!RIGID_PARSER) { Slog.w(TAG, "Unknown element under : " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); } else { outError[0] = "Bad element under : " + parser.getName(); return false; } } outInfo.hasDefault = outInfo.hasCategory(Intent.CATEGORY_DEFAULT); if (DEBUG_PARSER) { final StringBuilder cats = new StringBuilder("Intent d="); cats.append(outInfo.hasDefault); cats.append(", cat="); final Iterator it = outInfo.categoriesIterator(); if (it != null) { while (it.hasNext()) { cats.append(' '); cats.append(it.next()); } } Slog.d(TAG, cats.toString()); } return true; } /** * A container for signing-related data of an application package. * @hide */ public static final class SigningDetails implements Parcelable { @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN, SigningDetails.SignatureSchemeVersion.JAR, SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2, SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3}) public @interface SignatureSchemeVersion { int UNKNOWN = 0; int JAR = 1; int SIGNING_BLOCK_V2 = 2; int SIGNING_BLOCK_V3 = 3; } @Nullable @UnsupportedAppUsage public final Signature[] signatures; @SignatureSchemeVersion public final int signatureSchemeVersion; @Nullable public final ArraySet publicKeys; /** * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that * contains two pieces of information: * 1) the past signing certificates * 2) the flags that APK wants to assign to each of the past signing certificates. * * This collection of {@code Signature} objects, each of which is formed from a former * signing certificate of this APK before it was changed by signing certificate rotation, * represents the first piece of information. It is the APK saying to the rest of the * world: "hey if you trust the old cert, you can trust me!" This is useful, if for * instance, the platform would like to determine whether or not to allow this APK to do * something it would've allowed it to do under the old cert (like upgrade). */ @Nullable public final Signature[] pastSigningCertificates; /** special value used to see if cert is in package - not exposed to callers */ private static final int PAST_CERT_EXISTS = 0; @IntDef( flag = true, value = {CertCapabilities.INSTALLED_DATA, CertCapabilities.SHARED_USER_ID, CertCapabilities.PERMISSION, CertCapabilities.ROLLBACK}) public @interface CertCapabilities { /** accept data from already installed pkg with this cert */ int INSTALLED_DATA = 1; /** accept sharedUserId with pkg with this cert */ int SHARED_USER_ID = 2; /** grant SIGNATURE permissions to pkgs with this cert */ int PERMISSION = 4; /** allow pkg to update to one signed by this certificate */ int ROLLBACK = 8; /** allow pkg to continue to have auth access gated by this cert */ int AUTH = 16; } /** A representation of unknown signing details. Use instead of null. */ public static final SigningDetails UNKNOWN = new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null, null); @VisibleForTesting public SigningDetails(Signature[] signatures, @SignatureSchemeVersion int signatureSchemeVersion, ArraySet keys, Signature[] pastSigningCertificates) { this.signatures = signatures; this.signatureSchemeVersion = signatureSchemeVersion; this.publicKeys = keys; this.pastSigningCertificates = pastSigningCertificates; } public SigningDetails(Signature[] signatures, @SignatureSchemeVersion int signatureSchemeVersion, Signature[] pastSigningCertificates) throws CertificateException { this(signatures, signatureSchemeVersion, toSigningKeys(signatures), pastSigningCertificates); } public SigningDetails(Signature[] signatures, @SignatureSchemeVersion int signatureSchemeVersion) throws CertificateException { this(signatures, signatureSchemeVersion, null); } public SigningDetails(SigningDetails orig) { if (orig != null) { if (orig.signatures != null) { this.signatures = orig.signatures.clone(); } else { this.signatures = null; } this.signatureSchemeVersion = orig.signatureSchemeVersion; this.publicKeys = new ArraySet<>(orig.publicKeys); if (orig.pastSigningCertificates != null) { this.pastSigningCertificates = orig.pastSigningCertificates.clone(); } else { this.pastSigningCertificates = null; } } else { this.signatures = null; this.signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN; this.publicKeys = null; this.pastSigningCertificates = null; } } /** Returns true if the signing details have one or more signatures. */ public boolean hasSignatures() { return signatures != null && signatures.length > 0; } /** Returns true if the signing details have past signing certificates. */ public boolean hasPastSigningCertificates() { return pastSigningCertificates != null && pastSigningCertificates.length > 0; } /** * Determines if the provided {@code oldDetails} is an ancestor of or the same as this one. * If the {@code oldDetails} signing certificate appears in our pastSigningCertificates, * then that means it has authorized a signing certificate rotation, which eventually leads * to our certificate, and thus can be trusted. If this method evaluates to true, this * SigningDetails object should be trusted if the previous one is. */ public boolean hasAncestorOrSelf(SigningDetails oldDetails) { if (this == UNKNOWN || oldDetails == UNKNOWN) { return false; } if (oldDetails.signatures.length > 1) { // multiple-signer packages cannot rotate signing certs, so we just compare current // signers for an exact match return signaturesMatchExactly(oldDetails); } else { // we may have signing certificate rotation history, check to see if the oldDetails // was one of our old signing certificates return hasCertificate(oldDetails.signatures[0]); } } /** * Similar to {@code hasAncestorOrSelf}. Returns true only if this {@code SigningDetails} * is a descendant of {@code oldDetails}, not if they're the same. This is used to * determine if this object is newer than the provided one. */ public boolean hasAncestor(SigningDetails oldDetails) { if (this == UNKNOWN || oldDetails == UNKNOWN) { return false; } if (this.hasPastSigningCertificates() && oldDetails.signatures.length == 1) { // the last entry in pastSigningCertificates is the current signer, ignore it for (int i = 0; i < pastSigningCertificates.length - 1; i++) { if (pastSigningCertificates[i].equals(oldDetails.signatures[i])) { return true; } } } return false; } /** * Determines if the provided {@code oldDetails} is an ancestor of this one, and whether or * not this one grants it the provided capability, represented by the {@code flags} * parameter. In the event of signing certificate rotation, a package may still interact * with entities signed by its old signing certificate and not want to break previously * functioning behavior. The {@code flags} value determines which capabilities the app * signed by the newer signing certificate would like to continue to give to its previous * signing certificate(s). */ public boolean checkCapability(SigningDetails oldDetails, @CertCapabilities int flags) { if (this == UNKNOWN || oldDetails == UNKNOWN) { return false; } if (oldDetails.signatures.length > 1) { // multiple-signer packages cannot rotate signing certs, so we must have an exact // match, which also means all capabilities are granted return signaturesMatchExactly(oldDetails); } else { // we may have signing certificate rotation history, check to see if the oldDetails // was one of our old signing certificates, and if we grant it the capability it's // requesting return hasCertificate(oldDetails.signatures[0], flags); } } /** * A special case of {@code checkCapability} which re-encodes both sets of signing * certificates to counteract a previous re-encoding. */ public boolean checkCapabilityRecover(SigningDetails oldDetails, @CertCapabilities int flags) throws CertificateException { if (oldDetails == UNKNOWN || this == UNKNOWN) { return false; } if (hasPastSigningCertificates() && oldDetails.signatures.length == 1) { // signing certificates may have rotated, check entire history for effective match for (int i = 0; i < pastSigningCertificates.length; i++) { if (Signature.areEffectiveMatch( oldDetails.signatures[0], pastSigningCertificates[i]) && pastSigningCertificates[i].getFlags() == flags) { return true; } } } else { return Signature.areEffectiveMatch(oldDetails.signatures, signatures); } return false; } /** * Determine if {@code signature} is in this SigningDetails' signing certificate history, * including the current signer. Automatically returns false if this object has multiple * signing certificates, since rotation is only supported for single-signers; this is * enforced by {@code hasCertificateInternal}. */ public boolean hasCertificate(Signature signature) { return hasCertificateInternal(signature, PAST_CERT_EXISTS); } /** * Determine if {@code signature} is in this SigningDetails' signing certificate history, * including the current signer, and whether or not it has the given permission. * Certificates which match our current signer automatically get all capabilities. * Automatically returns false if this object has multiple signing certificates, since * rotation is only supported for single-signers. */ public boolean hasCertificate(Signature signature, @CertCapabilities int flags) { return hasCertificateInternal(signature, flags); } /** Convenient wrapper for calling {@code hasCertificate} with certificate's raw bytes. */ public boolean hasCertificate(byte[] certificate) { Signature signature = new Signature(certificate); return hasCertificate(signature); } private boolean hasCertificateInternal(Signature signature, int flags) { if (this == UNKNOWN) { return false; } // only single-signed apps can have pastSigningCertificates if (hasPastSigningCertificates()) { // check all past certs, except for the current one, which automatically gets all // capabilities, since it is the same as the current signature for (int i = 0; i < pastSigningCertificates.length - 1; i++) { if (pastSigningCertificates[i].equals(signature)) { if (flags == PAST_CERT_EXISTS || (flags & pastSigningCertificates[i].getFlags()) == flags) { return true; } } } } // not in previous certs signing history, just check the current signer and make sure // we are singly-signed return signatures.length == 1 && signatures[0].equals(signature); } /** * Determines if the provided {@code sha256String} is an ancestor of this one, and whether * or not this one grants it the provided capability, represented by the {@code flags} * parameter. In the event of signing certificate rotation, a package may still interact * with entities signed by its old signing certificate and not want to break previously * functioning behavior. The {@code flags} value determines which capabilities the app * signed by the newer signing certificate would like to continue to give to its previous * signing certificate(s). * * @param sha256String A hex-encoded representation of a sha256 digest. In the case of an * app with multiple signers, this represents the hex-encoded sha256 * digest of the combined hex-encoded sha256 digests of each individual * signing certificate according to {@link * PackageUtils#computeSignaturesSha256Digest(Signature[])} */ public boolean checkCapability(String sha256String, @CertCapabilities int flags) { if (this == UNKNOWN) { return false; } // first see if the hash represents a single-signer in our signing history byte[] sha256Bytes = sha256String == null ? null : HexEncoding.decode(sha256String, false /* allowSingleChar */); if (hasSha256Certificate(sha256Bytes, flags)) { return true; } // Not in signing history, either represents multiple signatures or not a match. // Multiple signers can't rotate, so no need to check flags, just see if the SHAs match. // We already check the single-signer case above as part of hasSha256Certificate, so no // need to verify we have multiple signers, just run the old check // just consider current signing certs final String[] mSignaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(signatures); final String mSignaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(mSignaturesSha256Digests); return mSignaturesSha256Digest.equals(sha256String); } /** * Determine if the {@code sha256Certificate} is in this SigningDetails' signing certificate * history, including the current signer. Automatically returns false if this object has * multiple signing certificates, since rotation is only supported for single-signers. */ public boolean hasSha256Certificate(byte[] sha256Certificate) { return hasSha256CertificateInternal(sha256Certificate, PAST_CERT_EXISTS); } /** * Determine if the {@code sha256Certificate} certificate hash corresponds to a signing * certificate in this SigningDetails' signing certificate history, including the current * signer, and whether or not it has the given permission. Certificates which match our * current signer automatically get all capabilities. Automatically returns false if this * object has multiple signing certificates, since rotation is only supported for * single-signers. */ public boolean hasSha256Certificate(byte[] sha256Certificate, @CertCapabilities int flags) { return hasSha256CertificateInternal(sha256Certificate, flags); } private boolean hasSha256CertificateInternal(byte[] sha256Certificate, int flags) { if (this == UNKNOWN) { return false; } if (hasPastSigningCertificates()) { // check all past certs, except for the last one, which automatically gets all // capabilities, since it is the same as the current signature, and is checked below for (int i = 0; i < pastSigningCertificates.length - 1; i++) { byte[] digest = PackageUtils.computeSha256DigestBytes( pastSigningCertificates[i].toByteArray()); if (Arrays.equals(sha256Certificate, digest)) { if (flags == PAST_CERT_EXISTS || (flags & pastSigningCertificates[i].getFlags()) == flags) { return true; } } } } // not in previous certs signing history, just check the current signer if (signatures.length == 1) { byte[] digest = PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray()); return Arrays.equals(sha256Certificate, digest); } return false; } /** Returns true if the signatures in this and other match exactly. */ public boolean signaturesMatchExactly(SigningDetails other) { return Signature.areExactMatch(this.signatures, other.signatures); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { boolean isUnknown = UNKNOWN == this; dest.writeBoolean(isUnknown); if (isUnknown) { return; } dest.writeTypedArray(this.signatures, flags); dest.writeInt(this.signatureSchemeVersion); dest.writeArraySet(this.publicKeys); dest.writeTypedArray(this.pastSigningCertificates, flags); } protected SigningDetails(Parcel in) { final ClassLoader boot = Object.class.getClassLoader(); this.signatures = in.createTypedArray(Signature.CREATOR); this.signatureSchemeVersion = in.readInt(); this.publicKeys = (ArraySet) in.readArraySet(boot); this.pastSigningCertificates = in.createTypedArray(Signature.CREATOR); } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public SigningDetails createFromParcel(Parcel source) { if (source.readBoolean()) { return UNKNOWN; } return new SigningDetails(source); } @Override public SigningDetails[] newArray(int size) { return new SigningDetails[size]; } }; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SigningDetails)) return false; SigningDetails that = (SigningDetails) o; if (signatureSchemeVersion != that.signatureSchemeVersion) return false; if (!Signature.areExactMatch(signatures, that.signatures)) return false; if (publicKeys != null) { if (!publicKeys.equals((that.publicKeys))) { return false; } } else if (that.publicKeys != null) { return false; } // can't use Signature.areExactMatch() because order matters with the past signing certs if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) { return false; } return true; } @Override public int hashCode() { int result = +Arrays.hashCode(signatures); result = 31 * result + signatureSchemeVersion; result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0); result = 31 * result + Arrays.hashCode(pastSigningCertificates); return result; } /** * Builder of {@code SigningDetails} instances. */ public static class Builder { private Signature[] mSignatures; private int mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN; private Signature[] mPastSigningCertificates; @UnsupportedAppUsage public Builder() { } /** get signing certificates used to sign the current APK */ @UnsupportedAppUsage public Builder setSignatures(Signature[] signatures) { mSignatures = signatures; return this; } /** set the signature scheme version used to sign the APK */ @UnsupportedAppUsage public Builder setSignatureSchemeVersion(int signatureSchemeVersion) { mSignatureSchemeVersion = signatureSchemeVersion; return this; } /** set the signing certificates by which the APK proved it can be authenticated */ @UnsupportedAppUsage public Builder setPastSigningCertificates(Signature[] pastSigningCertificates) { mPastSigningCertificates = pastSigningCertificates; return this; } private void checkInvariants() { // must have signatures and scheme version set if (mSignatures == null) { throw new IllegalStateException("SigningDetails requires the current signing" + " certificates."); } } /** build a {@code SigningDetails} object */ @UnsupportedAppUsage public SigningDetails build() throws CertificateException { checkInvariants(); return new SigningDetails(mSignatures, mSignatureSchemeVersion, mPastSigningCertificates); } } } /** * Representation of a full package parsed from APK files on disk. A package * consists of a single base APK, and zero or more split APKs. */ public final static class Package implements Parcelable { @UnsupportedAppUsage public String packageName; // The package name declared in the manifest as the package can be // renamed, for example static shared libs use synthetic package names. public String manifestPackageName; /** Names of any split APKs, ordered by parsed splitName */ public String[] splitNames; // TODO: work towards making these paths invariant public String volumeUuid; /** * Path where this package was found on disk. For monolithic packages * this is path to single base APK file; for cluster packages this is * path to the cluster directory. */ public String codePath; /** Path of base APK */ public String baseCodePath; /** Paths of any split APKs, ordered by parsed splitName */ public String[] splitCodePaths; /** Revision code of base APK */ public int baseRevisionCode; /** Revision codes of any split APKs, ordered by parsed splitName */ public int[] splitRevisionCodes; /** Flags of any split APKs; ordered by parsed splitName */ public int[] splitFlags; /** * Private flags of any split APKs; ordered by parsed splitName. * * {@hide} */ public int[] splitPrivateFlags; public boolean baseHardwareAccelerated; // For now we only support one application per package. @UnsupportedAppUsage public ApplicationInfo applicationInfo = new ApplicationInfo(); @UnsupportedAppUsage public final ArrayList permissions = new ArrayList(0); @UnsupportedAppUsage public final ArrayList permissionGroups = new ArrayList(0); @UnsupportedAppUsage public final ArrayList activities = new ArrayList(0); @UnsupportedAppUsage public final ArrayList receivers = new ArrayList(0); @UnsupportedAppUsage public final ArrayList providers = new ArrayList(0); @UnsupportedAppUsage public final ArrayList services = new ArrayList(0); @UnsupportedAppUsage public final ArrayList instrumentation = new ArrayList(0); @UnsupportedAppUsage public final ArrayList requestedPermissions = new ArrayList(); /** Permissions requested but not in the manifest. */ public final ArrayList implicitPermissions = new ArrayList<>(); @UnsupportedAppUsage public ArrayList protectedBroadcasts; public Package parentPackage; public ArrayList childPackages; public String staticSharedLibName = null; public long staticSharedLibVersion = 0; public ArrayList libraryNames = null; @UnsupportedAppUsage public ArrayList usesLibraries = null; public ArrayList usesStaticLibraries = null; public long[] usesStaticLibrariesVersions = null; public String[][] usesStaticLibrariesCertDigests = null; @UnsupportedAppUsage public ArrayList usesOptionalLibraries = null; @UnsupportedAppUsage public String[] usesLibraryFiles = null; public ArrayList usesLibraryInfos = null; public ArrayList preferredActivityFilters = null; public ArrayList mOriginalPackages = null; public String mRealPackage = null; public ArrayList mAdoptPermissions = null; // We store the application meta-data independently to avoid multiple unwanted references @UnsupportedAppUsage public Bundle mAppMetaData = null; // The version code declared for this package. @UnsupportedAppUsage public int mVersionCode; // The major version code declared for this package. public int mVersionCodeMajor; // Return long containing mVersionCode and mVersionCodeMajor. public long getLongVersionCode() { return PackageInfo.composeLongVersionCode(mVersionCodeMajor, mVersionCode); } // The version name declared for this package. @UnsupportedAppUsage public String mVersionName; // The shared user id that this package wants to use. @UnsupportedAppUsage public String mSharedUserId; // The shared user label that this package wants to use. @UnsupportedAppUsage public int mSharedUserLabel; // Signatures that were read from the package. @UnsupportedAppUsage @NonNull public SigningDetails mSigningDetails = SigningDetails.UNKNOWN; // For use by package manager service for quick lookup of // preferred up order. @UnsupportedAppUsage public int mPreferredOrder = 0; // For use by package manager to keep track of when a package was last used. public long[] mLastPackageUsageTimeInMills = new long[PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT]; // // User set enabled state. // public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; // // // Whether the package has been stopped. // public boolean mSetStopped = false; // Additional data supplied by callers. @UnsupportedAppUsage public Object mExtras; // Applications hardware preferences @UnsupportedAppUsage public ArrayList configPreferences = null; // Applications requested features @UnsupportedAppUsage public ArrayList reqFeatures = null; // Applications requested feature groups public ArrayList featureGroups = null; @UnsupportedAppUsage public int installLocation; public boolean coreApp; /* An app that's required for all users and cannot be uninstalled for a user */ public boolean mRequiredForAllUsers; /* The restricted account authenticator type that is used by this application */ public String mRestrictedAccountType; /* The required account type without which this application will not function */ public String mRequiredAccountType; public String mOverlayTarget; public String mOverlayTargetName; public String mOverlayCategory; public int mOverlayPriority; public boolean mOverlayIsStatic; public int mCompileSdkVersion; public String mCompileSdkVersionCodename; /** * Data used to feed the KeySetManagerService */ @UnsupportedAppUsage public ArraySet mUpgradeKeySets; @UnsupportedAppUsage public ArrayMap> mKeySetMapping; /** * The install time abi override for this package, if any. * * TODO: This seems like a horrible place to put the abiOverride because * this isn't something the packageParser parsers. However, this fits in with * the rest of the PackageManager where package scanning randomly pushes * and prods fields out of {@code this.applicationInfo}. */ public String cpuAbiOverride; /** * The install time abi override to choose 32bit abi's when multiple abi's * are present. This is only meaningfull for multiarch applications. * The use32bitAbi attribute is ignored if cpuAbiOverride is also set. */ public boolean use32bitAbi; public byte[] restrictUpdateHash; /** Set if the app or any of its components are visible to instant applications. */ public boolean visibleToInstantApps; /** Whether or not the package is a stub and must be replaced by the full version. */ public boolean isStub; @UnsupportedAppUsage public Package(String packageName) { this.packageName = packageName; this.manifestPackageName = packageName; applicationInfo.packageName = packageName; applicationInfo.uid = -1; } public void setApplicationVolumeUuid(String volumeUuid) { final UUID storageUuid = StorageManager.convert(volumeUuid); this.applicationInfo.volumeUuid = volumeUuid; this.applicationInfo.storageUuid = storageUuid; if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).applicationInfo.volumeUuid = volumeUuid; childPackages.get(i).applicationInfo.storageUuid = storageUuid; } } } public void setApplicationInfoCodePath(String codePath) { this.applicationInfo.setCodePath(codePath); if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).applicationInfo.setCodePath(codePath); } } } /** @deprecated Forward locked apps no longer supported. Resource path not needed. */ @Deprecated public void setApplicationInfoResourcePath(String resourcePath) { this.applicationInfo.setResourcePath(resourcePath); if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).applicationInfo.setResourcePath(resourcePath); } } } /** @deprecated Forward locked apps no longer supported. Resource path not needed. */ @Deprecated public void setApplicationInfoBaseResourcePath(String resourcePath) { this.applicationInfo.setBaseResourcePath(resourcePath); if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).applicationInfo.setBaseResourcePath(resourcePath); } } } public void setApplicationInfoBaseCodePath(String baseCodePath) { this.applicationInfo.setBaseCodePath(baseCodePath); if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).applicationInfo.setBaseCodePath(baseCodePath); } } } public List getChildPackageNames() { if (childPackages == null) { return null; } final int childCount = childPackages.size(); final List childPackageNames = new ArrayList<>(childCount); for (int i = 0; i < childCount; i++) { String childPackageName = childPackages.get(i).packageName; childPackageNames.add(childPackageName); } return childPackageNames; } public boolean hasChildPackage(String packageName) { final int childCount = (childPackages != null) ? childPackages.size() : 0; for (int i = 0; i < childCount; i++) { if (childPackages.get(i).packageName.equals(packageName)) { return true; } } return false; } public void setApplicationInfoSplitCodePaths(String[] splitCodePaths) { this.applicationInfo.setSplitCodePaths(splitCodePaths); // Children have no splits } /** @deprecated Forward locked apps no longer supported. Resource path not needed. */ @Deprecated public void setApplicationInfoSplitResourcePaths(String[] resroucePaths) { this.applicationInfo.setSplitResourcePaths(resroucePaths); // Children have no splits } public void setSplitCodePaths(String[] codePaths) { this.splitCodePaths = codePaths; } public void setCodePath(String codePath) { this.codePath = codePath; if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).codePath = codePath; } } } public void setBaseCodePath(String baseCodePath) { this.baseCodePath = baseCodePath; if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).baseCodePath = baseCodePath; } } } /** Sets signing details on the package and any of its children. */ public void setSigningDetails(@NonNull SigningDetails signingDetails) { mSigningDetails = signingDetails; if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).mSigningDetails = signingDetails; } } } public void setVolumeUuid(String volumeUuid) { this.volumeUuid = volumeUuid; if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).volumeUuid = volumeUuid; } } } public void setApplicationInfoFlags(int mask, int flags) { applicationInfo.flags = (applicationInfo.flags & ~mask) | (mask & flags); if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).applicationInfo.flags = (applicationInfo.flags & ~mask) | (mask & flags); } } } public void setUse32bitAbi(boolean use32bitAbi) { this.use32bitAbi = use32bitAbi; if (childPackages != null) { final int packageCount = childPackages.size(); for (int i = 0; i < packageCount; i++) { childPackages.get(i).use32bitAbi = use32bitAbi; } } } public boolean isLibrary() { return staticSharedLibName != null || !ArrayUtils.isEmpty(libraryNames); } public List getAllCodePaths() { ArrayList paths = new ArrayList<>(); paths.add(baseCodePath); if (!ArrayUtils.isEmpty(splitCodePaths)) { Collections.addAll(paths, splitCodePaths); } return paths; } /** * Filtered set of {@link #getAllCodePaths()} that excludes * resource-only APKs. */ public List getAllCodePathsExcludingResourceOnly() { ArrayList paths = new ArrayList<>(); if ((applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) { paths.add(baseCodePath); } if (!ArrayUtils.isEmpty(splitCodePaths)) { for (int i = 0; i < splitCodePaths.length; i++) { if ((splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) { paths.add(splitCodePaths[i]); } } } return paths; } @UnsupportedAppUsage public void setPackageName(String newName) { packageName = newName; applicationInfo.packageName = newName; for (int i=permissions.size()-1; i>=0; i--) { permissions.get(i).setPackageName(newName); } for (int i=permissionGroups.size()-1; i>=0; i--) { permissionGroups.get(i).setPackageName(newName); } for (int i=activities.size()-1; i>=0; i--) { activities.get(i).setPackageName(newName); } for (int i=receivers.size()-1; i>=0; i--) { receivers.get(i).setPackageName(newName); } for (int i=providers.size()-1; i>=0; i--) { providers.get(i).setPackageName(newName); } for (int i=services.size()-1; i>=0; i--) { services.get(i).setPackageName(newName); } for (int i=instrumentation.size()-1; i>=0; i--) { instrumentation.get(i).setPackageName(newName); } } public boolean hasComponentClassName(String name) { for (int i=activities.size()-1; i>=0; i--) { if (name.equals(activities.get(i).className)) { return true; } } for (int i=receivers.size()-1; i>=0; i--) { if (name.equals(receivers.get(i).className)) { return true; } } for (int i=providers.size()-1; i>=0; i--) { if (name.equals(providers.get(i).className)) { return true; } } for (int i=services.size()-1; i>=0; i--) { if (name.equals(services.get(i).className)) { return true; } } for (int i=instrumentation.size()-1; i>=0; i--) { if (name.equals(instrumentation.get(i).className)) { return true; } } return false; } /** @hide */ public boolean isExternal() { return applicationInfo.isExternal(); } /** @hide */ public boolean isForwardLocked() { return false; } /** @hide */ public boolean isOem() { return applicationInfo.isOem(); } /** @hide */ public boolean isVendor() { return applicationInfo.isVendor(); } /** @hide */ public boolean isProduct() { return applicationInfo.isProduct(); } /** @hide */ public boolean isSystemExt() { return applicationInfo.isSystemExt(); } /** @hide */ public boolean isOdm() { return applicationInfo.isOdm(); } /** @hide */ public boolean isPrivileged() { return applicationInfo.isPrivilegedApp(); } /** @hide */ public boolean isSystem() { return applicationInfo.isSystemApp(); } /** @hide */ public boolean isUpdatedSystemApp() { return applicationInfo.isUpdatedSystemApp(); } /** @hide */ public boolean canHaveOatDir() { // The following app types CANNOT have oat directory // - non-updated system apps return !isSystem() || isUpdatedSystemApp(); } public boolean isMatch(int flags) { if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { return isSystem(); } return true; } public long getLatestPackageUseTimeInMills() { long latestUse = 0L; for (long use : mLastPackageUsageTimeInMills) { latestUse = Math.max(latestUse, use); } return latestUse; } public long getLatestForegroundPackageUseTimeInMills() { int[] foregroundReasons = { PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY, PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE }; long latestUse = 0L; for (int reason : foregroundReasons) { latestUse = Math.max(latestUse, mLastPackageUsageTimeInMills[reason]); } return latestUse; } public String toString() { return "Package{" + Integer.toHexString(System.identityHashCode(this)) + " " + packageName + "}"; } @Override public int describeContents() { return 0; } public Package(Parcel dest) { // We use the boot classloader for all classes that we load. final ClassLoader boot = Object.class.getClassLoader(); packageName = dest.readString().intern(); manifestPackageName = dest.readString(); splitNames = dest.readStringArray(); volumeUuid = dest.readString(); codePath = dest.readString(); baseCodePath = dest.readString(); splitCodePaths = dest.readStringArray(); baseRevisionCode = dest.readInt(); splitRevisionCodes = dest.createIntArray(); splitFlags = dest.createIntArray(); splitPrivateFlags = dest.createIntArray(); baseHardwareAccelerated = (dest.readInt() == 1); applicationInfo = dest.readParcelable(boot); if (applicationInfo.permission != null) { applicationInfo.permission = applicationInfo.permission.intern(); } // We don't serialize the "owner" package and the application info object for each of // these components, in order to save space and to avoid circular dependencies while // serialization. We need to fix them all up here. dest.readParcelableList(permissions, boot); fixupOwner(permissions); dest.readParcelableList(permissionGroups, boot); fixupOwner(permissionGroups); dest.readParcelableList(activities, boot); fixupOwner(activities); dest.readParcelableList(receivers, boot); fixupOwner(receivers); dest.readParcelableList(providers, boot); fixupOwner(providers); dest.readParcelableList(services, boot); fixupOwner(services); dest.readParcelableList(instrumentation, boot); fixupOwner(instrumentation); dest.readStringList(requestedPermissions); internStringArrayList(requestedPermissions); dest.readStringList(implicitPermissions); internStringArrayList(implicitPermissions); protectedBroadcasts = dest.createStringArrayList(); internStringArrayList(protectedBroadcasts); parentPackage = dest.readParcelable(boot); childPackages = new ArrayList<>(); dest.readParcelableList(childPackages, boot); if (childPackages.size() == 0) { childPackages = null; } staticSharedLibName = dest.readString(); if (staticSharedLibName != null) { staticSharedLibName = staticSharedLibName.intern(); } staticSharedLibVersion = dest.readLong(); libraryNames = dest.createStringArrayList(); internStringArrayList(libraryNames); usesLibraries = dest.createStringArrayList(); internStringArrayList(usesLibraries); usesOptionalLibraries = dest.createStringArrayList(); internStringArrayList(usesOptionalLibraries); usesLibraryFiles = dest.readStringArray(); usesLibraryInfos = dest.createTypedArrayList(SharedLibraryInfo.CREATOR); final int libCount = dest.readInt(); if (libCount > 0) { usesStaticLibraries = new ArrayList<>(libCount); dest.readStringList(usesStaticLibraries); internStringArrayList(usesStaticLibraries); usesStaticLibrariesVersions = new long[libCount]; dest.readLongArray(usesStaticLibrariesVersions); usesStaticLibrariesCertDigests = new String[libCount][]; for (int i = 0; i < libCount; i++) { usesStaticLibrariesCertDigests[i] = dest.createStringArray(); } } preferredActivityFilters = new ArrayList<>(); dest.readParcelableList(preferredActivityFilters, boot); if (preferredActivityFilters.size() == 0) { preferredActivityFilters = null; } mOriginalPackages = dest.createStringArrayList(); mRealPackage = dest.readString(); mAdoptPermissions = dest.createStringArrayList(); mAppMetaData = dest.readBundle(); mVersionCode = dest.readInt(); mVersionCodeMajor = dest.readInt(); mVersionName = dest.readString(); if (mVersionName != null) { mVersionName = mVersionName.intern(); } mSharedUserId = dest.readString(); if (mSharedUserId != null) { mSharedUserId = mSharedUserId.intern(); } mSharedUserLabel = dest.readInt(); mSigningDetails = dest.readParcelable(boot); mPreferredOrder = dest.readInt(); // long[] packageUsageTimeMillis is not persisted because it isn't information that // is parsed from the APK. // Object mExtras is not persisted because it is not information that is read from // the APK, rather, it is supplied by callers. configPreferences = new ArrayList<>(); dest.readParcelableList(configPreferences, boot); if (configPreferences.size() == 0) { configPreferences = null; } reqFeatures = new ArrayList<>(); dest.readParcelableList(reqFeatures, boot); if (reqFeatures.size() == 0) { reqFeatures = null; } featureGroups = new ArrayList<>(); dest.readParcelableList(featureGroups, boot); if (featureGroups.size() == 0) { featureGroups = null; } installLocation = dest.readInt(); coreApp = (dest.readInt() == 1); mRequiredForAllUsers = (dest.readInt() == 1); mRestrictedAccountType = dest.readString(); mRequiredAccountType = dest.readString(); mOverlayTarget = dest.readString(); mOverlayTargetName = dest.readString(); mOverlayCategory = dest.readString(); mOverlayPriority = dest.readInt(); mOverlayIsStatic = (dest.readInt() == 1); mCompileSdkVersion = dest.readInt(); mCompileSdkVersionCodename = dest.readString(); mUpgradeKeySets = (ArraySet) dest.readArraySet(boot); mKeySetMapping = readKeySetMapping(dest); cpuAbiOverride = dest.readString(); use32bitAbi = (dest.readInt() == 1); restrictUpdateHash = dest.createByteArray(); visibleToInstantApps = dest.readInt() == 1; } private static void internStringArrayList(List list) { if (list != null) { final int N = list.size(); for (int i = 0; i < N; ++i) { list.set(i, list.get(i).intern()); } } } /** * Sets the package owner and the the {@code applicationInfo} for every component * owner by this package. */ private void fixupOwner(List> list) { if (list != null) { for (Component c : list) { c.owner = this; if (c instanceof Activity) { ((Activity) c).info.applicationInfo = this.applicationInfo; } else if (c instanceof Service) { ((Service) c).info.applicationInfo = this.applicationInfo; } else if (c instanceof Provider) { ((Provider) c).info.applicationInfo = this.applicationInfo; } } } } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(packageName); dest.writeString(manifestPackageName); dest.writeStringArray(splitNames); dest.writeString(volumeUuid); dest.writeString(codePath); dest.writeString(baseCodePath); dest.writeStringArray(splitCodePaths); dest.writeInt(baseRevisionCode); dest.writeIntArray(splitRevisionCodes); dest.writeIntArray(splitFlags); dest.writeIntArray(splitPrivateFlags); dest.writeInt(baseHardwareAccelerated ? 1 : 0); dest.writeParcelable(applicationInfo, flags); dest.writeParcelableList(permissions, flags); dest.writeParcelableList(permissionGroups, flags); dest.writeParcelableList(activities, flags); dest.writeParcelableList(receivers, flags); dest.writeParcelableList(providers, flags); dest.writeParcelableList(services, flags); dest.writeParcelableList(instrumentation, flags); dest.writeStringList(requestedPermissions); dest.writeStringList(implicitPermissions); dest.writeStringList(protectedBroadcasts); // TODO: This doesn't work: b/64295061 dest.writeParcelable(parentPackage, flags); dest.writeParcelableList(childPackages, flags); dest.writeString(staticSharedLibName); dest.writeLong(staticSharedLibVersion); dest.writeStringList(libraryNames); dest.writeStringList(usesLibraries); dest.writeStringList(usesOptionalLibraries); dest.writeStringArray(usesLibraryFiles); dest.writeTypedList(usesLibraryInfos); if (ArrayUtils.isEmpty(usesStaticLibraries)) { dest.writeInt(-1); } else { dest.writeInt(usesStaticLibraries.size()); dest.writeStringList(usesStaticLibraries); dest.writeLongArray(usesStaticLibrariesVersions); for (String[] usesStaticLibrariesCertDigest : usesStaticLibrariesCertDigests) { dest.writeStringArray(usesStaticLibrariesCertDigest); } } dest.writeParcelableList(preferredActivityFilters, flags); dest.writeStringList(mOriginalPackages); dest.writeString(mRealPackage); dest.writeStringList(mAdoptPermissions); dest.writeBundle(mAppMetaData); dest.writeInt(mVersionCode); dest.writeInt(mVersionCodeMajor); dest.writeString(mVersionName); dest.writeString(mSharedUserId); dest.writeInt(mSharedUserLabel); dest.writeParcelable(mSigningDetails, flags); dest.writeInt(mPreferredOrder); // long[] packageUsageTimeMillis is not persisted because it isn't information that // is parsed from the APK. // Object mExtras is not persisted because it is not information that is read from // the APK, rather, it is supplied by callers. dest.writeParcelableList(configPreferences, flags); dest.writeParcelableList(reqFeatures, flags); dest.writeParcelableList(featureGroups, flags); dest.writeInt(installLocation); dest.writeInt(coreApp ? 1 : 0); dest.writeInt(mRequiredForAllUsers ? 1 : 0); dest.writeString(mRestrictedAccountType); dest.writeString(mRequiredAccountType); dest.writeString(mOverlayTarget); dest.writeString(mOverlayTargetName); dest.writeString(mOverlayCategory); dest.writeInt(mOverlayPriority); dest.writeInt(mOverlayIsStatic ? 1 : 0); dest.writeInt(mCompileSdkVersion); dest.writeString(mCompileSdkVersionCodename); dest.writeArraySet(mUpgradeKeySets); writeKeySetMapping(dest, mKeySetMapping); dest.writeString(cpuAbiOverride); dest.writeInt(use32bitAbi ? 1 : 0); dest.writeByteArray(restrictUpdateHash); dest.writeInt(visibleToInstantApps ? 1 : 0); } /** * Writes the keyset mapping to the provided package. {@code null} mappings are permitted. */ private static void writeKeySetMapping( Parcel dest, ArrayMap> keySetMapping) { if (keySetMapping == null) { dest.writeInt(-1); return; } final int N = keySetMapping.size(); dest.writeInt(N); for (int i = 0; i < N; i++) { dest.writeString(keySetMapping.keyAt(i)); ArraySet keys = keySetMapping.valueAt(i); if (keys == null) { dest.writeInt(-1); continue; } final int M = keys.size(); dest.writeInt(M); for (int j = 0; j < M; j++) { dest.writeSerializable(keys.valueAt(j)); } } } /** * Reads a keyset mapping from the given parcel at the given data position. May return * {@code null} if the serialized mapping was {@code null}. */ private static ArrayMap> readKeySetMapping(Parcel in) { final int N = in.readInt(); if (N == -1) { return null; } ArrayMap> keySetMapping = new ArrayMap<>(); for (int i = 0; i < N; ++i) { String key = in.readString(); final int M = in.readInt(); if (M == -1) { keySetMapping.put(key, null); continue; } ArraySet keys = new ArraySet<>(M); for (int j = 0; j < M; ++j) { PublicKey pk = (PublicKey) in.readSerializable(); keys.add(pk); } keySetMapping.put(key, keys); } return keySetMapping; } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Package createFromParcel(Parcel in) { return new Package(in); } public Package[] newArray(int size) { return new Package[size]; } }; } public static abstract class Component { @UnsupportedAppUsage public final ArrayList intents; @UnsupportedAppUsage public final String className; @UnsupportedAppUsage public Bundle metaData; @UnsupportedAppUsage public Package owner; /** The order of this component in relation to its peers */ public int order; ComponentName componentName; String componentShortName; public Component(Package owner, ArrayList intents, String className) { this.owner = owner; this.intents = intents; this.className = className; } public Component(Package owner) { this.owner = owner; this.intents = null; this.className = null; } public Component(final ParsePackageItemArgs args, final PackageItemInfo outInfo) { owner = args.owner; intents = new ArrayList(0); if (parsePackageItemInfo(args.owner, outInfo, args.outError, args.tag, args.sa, true /*nameRequired*/, args.nameRes, args.labelRes, args.iconRes, args.roundIconRes, args.logoRes, args.bannerRes)) { className = outInfo.name; } else { className = null; } } public Component(final ParseComponentArgs args, final ComponentInfo outInfo) { this(args, (PackageItemInfo)outInfo); if (args.outError[0] != null) { return; } if (args.processRes != 0) { CharSequence pname; if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) { pname = args.sa.getNonConfigurationString(args.processRes, 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 = args.sa.getNonResourceString(args.processRes); } outInfo.processName = buildProcessName(owner.applicationInfo.packageName, owner.applicationInfo.processName, pname, args.flags, args.sepProcesses, args.outError); } if (args.descriptionRes != 0) { outInfo.descriptionRes = args.sa.getResourceId(args.descriptionRes, 0); } outInfo.enabled = args.sa.getBoolean(args.enabledRes, true); } public Component(Component clone) { owner = clone.owner; intents = clone.intents; className = clone.className; componentName = clone.componentName; componentShortName = clone.componentShortName; } @UnsupportedAppUsage public ComponentName getComponentName() { if (componentName != null) { return componentName; } if (className != null) { componentName = new ComponentName(owner.applicationInfo.packageName, className); } return componentName; } protected Component(Parcel in) { className = in.readString(); metaData = in.readBundle(); intents = createIntentsList(in); owner = null; } protected void writeToParcel(Parcel dest, int flags) { dest.writeString(className); dest.writeBundle(metaData); writeIntentsList(intents, dest, flags); } /** *

* Implementation note: The serialized form for the intent list also contains the name * of the concrete class that's stored in the list, and assumes that every element of the * list is of the same type. This is very similar to the original parcelable mechanism. * We cannot use that directly because IntentInfo extends IntentFilter, which is parcelable * and is public API. It also declares Parcelable related methods as final which means * we can't extend them. The approach of using composition instead of inheritance leads to * a large set of cascading changes in the PackageManagerService, which seem undesirable. * *

* WARNING: The list of objects returned by this function might need to be fixed up * to make sure their owner fields are consistent. See {@code fixupOwner}. */ private static void writeIntentsList(ArrayList list, Parcel out, int flags) { if (list == null) { out.writeInt(-1); return; } final int N = list.size(); out.writeInt(N); // Don't bother writing the component name if the list is empty. if (N > 0) { IntentInfo info = list.get(0); out.writeString(info.getClass().getName()); for (int i = 0; i < N;i++) { list.get(i).writeIntentInfoToParcel(out, flags); } } } private static ArrayList createIntentsList(Parcel in) { int N = in.readInt(); if (N == -1) { return null; } if (N == 0) { return new ArrayList<>(0); } String componentName = in.readString(); final ArrayList intentsList; try { final Class cls = (Class) Class.forName(componentName); final Constructor cons = cls.getConstructor(Parcel.class); intentsList = new ArrayList<>(N); for (int i = 0; i < N; ++i) { intentsList.add(cons.newInstance(in)); } } catch (ReflectiveOperationException ree) { throw new AssertionError("Unable to construct intent list for: " + componentName); } return intentsList; } public void appendComponentShortName(StringBuilder sb) { ComponentName.appendShortString(sb, owner.applicationInfo.packageName, className); } public void printComponentShortName(PrintWriter pw) { ComponentName.printShortString(pw, owner.applicationInfo.packageName, className); } public void setPackageName(String packageName) { componentName = null; componentShortName = null; } } public final static class Permission extends Component implements Parcelable { @UnsupportedAppUsage public final PermissionInfo info; @UnsupportedAppUsage public boolean tree; @UnsupportedAppUsage public PermissionGroup group; /** * @hide */ public Permission(Package owner, @Nullable String backgroundPermission) { super(owner); info = new PermissionInfo(backgroundPermission); } @UnsupportedAppUsage public Permission(Package _owner, PermissionInfo _info) { super(_owner); info = _info; } public void setPackageName(String packageName) { super.setPackageName(packageName); info.packageName = packageName; } public String toString() { return "Permission{" + Integer.toHexString(System.identityHashCode(this)) + " " + info.name + "}"; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(info, flags); dest.writeInt(tree ? 1 : 0); dest.writeParcelable(group, flags); } /** @hide */ public boolean isAppOp() { return info.isAppOp(); } private Permission(Parcel in) { super(in); final ClassLoader boot = Object.class.getClassLoader(); info = in.readParcelable(boot); if (info.group != null) { info.group = info.group.intern(); } tree = (in.readInt() == 1); group = in.readParcelable(boot); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Permission createFromParcel(Parcel in) { return new Permission(in); } public Permission[] newArray(int size) { return new Permission[size]; } }; } public final static class PermissionGroup extends Component implements Parcelable { @UnsupportedAppUsage public final PermissionGroupInfo info; public PermissionGroup(Package owner, @StringRes int requestDetailResourceId, @StringRes int backgroundRequestResourceId, @StringRes int backgroundRequestDetailResourceId) { super(owner); info = new PermissionGroupInfo(requestDetailResourceId, backgroundRequestResourceId, backgroundRequestDetailResourceId); } public PermissionGroup(Package _owner, PermissionGroupInfo _info) { super(_owner); info = _info; } public void setPackageName(String packageName) { super.setPackageName(packageName); info.packageName = packageName; } public String toString() { return "PermissionGroup{" + Integer.toHexString(System.identityHashCode(this)) + " " + info.name + "}"; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(info, flags); } private PermissionGroup(Parcel in) { super(in); info = in.readParcelable(Object.class.getClassLoader()); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public PermissionGroup createFromParcel(Parcel in) { return new PermissionGroup(in); } public PermissionGroup[] newArray(int size) { return new PermissionGroup[size]; } }; } private static boolean copyNeeded(int flags, Package p, PackageUserState state, Bundle metaData, int userId) { if (userId != UserHandle.USER_SYSTEM) { // We always need to copy for other users, since we need // to fix up the uid. return true; } if (state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { boolean enabled = state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; if (p.applicationInfo.enabled != enabled) { return true; } } boolean suspended = (p.applicationInfo.flags & FLAG_SUSPENDED) != 0; if (state.suspended != suspended) { return true; } if (!state.installed || state.hidden) { return true; } if (state.stopped) { return true; } if (state.instantApp != p.applicationInfo.isInstantApp()) { return true; } if ((flags & PackageManager.GET_META_DATA) != 0 && (metaData != null || p.mAppMetaData != null)) { return true; } if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0 && p.usesLibraryFiles != null) { return true; } if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0 && p.usesLibraryInfos != null) { return true; } if (p.staticSharedLibName != null) { return true; } return false; } @UnsupportedAppUsage public static ApplicationInfo generateApplicationInfo(Package p, int flags, PackageUserState state) { return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId()); } private static void updateApplicationInfo(ApplicationInfo ai, int flags, PackageUserState state) { // CompatibilityMode is global state. if (!sCompatibilityModeEnabled) { ai.disableCompatibilityMode(); } if (state.installed) { ai.flags |= ApplicationInfo.FLAG_INSTALLED; } else { ai.flags &= ~ApplicationInfo.FLAG_INSTALLED; } if (state.suspended) { ai.flags |= ApplicationInfo.FLAG_SUSPENDED; } else { ai.flags &= ~ApplicationInfo.FLAG_SUSPENDED; } if (state.instantApp) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT; } else { ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_INSTANT; } if (state.virtualPreload) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD; } else { ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD; } if (state.hidden) { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN; } else { ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HIDDEN; } if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { ai.enabled = true; } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { ai.enabled = (flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0; } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { ai.enabled = false; } ai.enabledSetting = state.enabled; if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) { ai.category = state.categoryHint; } if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); ai.resourceDirs = state.overlayPaths; ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes; } @UnsupportedAppUsage public static ApplicationInfo generateApplicationInfo(Package p, int flags, PackageUserState state, int userId) { if (p == null) return null; if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) { return null; } if (!copyNeeded(flags, p, state, null, userId) && ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) == 0 || state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) { // In this case it is safe to directly modify the internal ApplicationInfo state: // - CompatibilityMode is global state, so will be the same for every call. // - We only come in to here if the app should reported as installed; this is the // default state, and we will do a copy otherwise. // - The enable state will always be reported the same for the application across // calls; the only exception is for the UNTIL_USED mode, and in that case we will // be doing a copy. updateApplicationInfo(p.applicationInfo, flags, state); return p.applicationInfo; } // Make shallow copy so we can store the metadata/libraries safely ApplicationInfo ai = new ApplicationInfo(p.applicationInfo); ai.initForUser(userId); if ((flags & PackageManager.GET_META_DATA) != 0) { ai.metaData = p.mAppMetaData; } if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) { ai.sharedLibraryFiles = p.usesLibraryFiles; ai.sharedLibraryInfos = p.usesLibraryInfos; } if (state.stopped) { ai.flags |= ApplicationInfo.FLAG_STOPPED; } else { ai.flags &= ~ApplicationInfo.FLAG_STOPPED; } updateApplicationInfo(ai, flags, state); return ai; } public static ApplicationInfo generateApplicationInfo(ApplicationInfo ai, int flags, PackageUserState state, int userId) { if (ai == null) return null; if (!checkUseInstalledOrHidden(flags, state, ai)) { return null; } // This is only used to return the ResolverActivity; we will just always // make a copy. ai = new ApplicationInfo(ai); ai.initForUser(userId); if (state.stopped) { ai.flags |= ApplicationInfo.FLAG_STOPPED; } else { ai.flags &= ~ApplicationInfo.FLAG_STOPPED; } updateApplicationInfo(ai, flags, state); return ai; } @UnsupportedAppUsage public static final PermissionInfo generatePermissionInfo( Permission p, int flags) { if (p == null) return null; if ((flags&PackageManager.GET_META_DATA) == 0) { return p.info; } PermissionInfo pi = new PermissionInfo(p.info); pi.metaData = p.metaData; return pi; } @UnsupportedAppUsage public static final PermissionGroupInfo generatePermissionGroupInfo( PermissionGroup pg, int flags) { if (pg == null) return null; if ((flags&PackageManager.GET_META_DATA) == 0) { return pg.info; } PermissionGroupInfo pgi = new PermissionGroupInfo(pg.info); pgi.metaData = pg.metaData; return pgi; } public final static class Activity extends Component implements Parcelable { @UnsupportedAppUsage public final ActivityInfo info; private boolean mHasMaxAspectRatio; private boolean mHasMinAspectRatio; private boolean hasMaxAspectRatio() { return mHasMaxAspectRatio; } private boolean hasMinAspectRatio() { return mHasMinAspectRatio; } // To construct custom activity which does not exist in manifest Activity(final Package owner, final String className, final ActivityInfo info) { super(owner, new ArrayList<>(0), className); this.info = info; this.info.applicationInfo = owner.applicationInfo; } public Activity(final ParseComponentArgs args, final ActivityInfo _info) { super(args, _info); info = _info; info.applicationInfo = args.owner.applicationInfo; } public void setPackageName(String packageName) { super.setPackageName(packageName); info.packageName = packageName; } private void setMaxAspectRatio(float maxAspectRatio) { if (info.resizeMode == RESIZE_MODE_RESIZEABLE || info.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) { // Resizeable activities can be put in any aspect ratio. return; } if (maxAspectRatio < 1.0f && maxAspectRatio != 0) { // Ignore any value lesser than 1.0. return; } info.maxAspectRatio = maxAspectRatio; mHasMaxAspectRatio = true; } private void setMinAspectRatio(float minAspectRatio) { if (info.resizeMode == RESIZE_MODE_RESIZEABLE || info.resizeMode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) { // Resizeable activities can be put in any aspect ratio. return; } if (minAspectRatio < 1.0f && minAspectRatio != 0) { // Ignore any value lesser than 1.0. return; } info.minAspectRatio = minAspectRatio; mHasMinAspectRatio = true; } public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Activity{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); appendComponentShortName(sb); sb.append('}'); return sb.toString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); dest.writeBoolean(mHasMaxAspectRatio); dest.writeBoolean(mHasMinAspectRatio); } private Activity(Parcel in) { super(in); info = in.readParcelable(Object.class.getClassLoader()); mHasMaxAspectRatio = in.readBoolean(); mHasMinAspectRatio = in.readBoolean(); for (ActivityIntentInfo aii : intents) { aii.activity = this; order = Math.max(aii.getOrder(), order); } if (info.permission != null) { info.permission = info.permission.intern(); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Activity createFromParcel(Parcel in) { return new Activity(in); } public Activity[] newArray(int size) { return new Activity[size]; } }; } @UnsupportedAppUsage public static final ActivityInfo generateActivityInfo(Activity a, int flags, PackageUserState state, int userId) { if (a == null) return null; if (!checkUseInstalledOrHidden(flags, state, a.owner.applicationInfo)) { return null; } if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) { updateApplicationInfo(a.info.applicationInfo, flags, state); return a.info; } // Make shallow copies so we can store the metadata safely ActivityInfo ai = new ActivityInfo(a.info); ai.metaData = a.metaData; ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId); return ai; } public static final ActivityInfo generateActivityInfo(ActivityInfo ai, int flags, PackageUserState state, int userId) { if (ai == null) return null; if (!checkUseInstalledOrHidden(flags, state, ai.applicationInfo)) { return null; } // This is only used to return the ResolverActivity; we will just always // make a copy. ai = new ActivityInfo(ai); ai.applicationInfo = generateApplicationInfo(ai.applicationInfo, flags, state, userId); return ai; } public final static class Service extends Component implements Parcelable { @UnsupportedAppUsage public final ServiceInfo info; public Service(final ParseComponentArgs args, final ServiceInfo _info) { super(args, _info); info = _info; info.applicationInfo = args.owner.applicationInfo; } public void setPackageName(String packageName) { super.setPackageName(packageName); info.packageName = packageName; } public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Service{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); appendComponentShortName(sb); sb.append('}'); return sb.toString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); } private Service(Parcel in) { super(in); info = in.readParcelable(Object.class.getClassLoader()); for (ServiceIntentInfo aii : intents) { aii.service = this; order = Math.max(aii.getOrder(), order); } if (info.permission != null) { info.permission = info.permission.intern(); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Service createFromParcel(Parcel in) { return new Service(in); } public Service[] newArray(int size) { return new Service[size]; } }; } @UnsupportedAppUsage public static final ServiceInfo generateServiceInfo(Service s, int flags, PackageUserState state, int userId) { if (s == null) return null; if (!checkUseInstalledOrHidden(flags, state, s.owner.applicationInfo)) { return null; } if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) { updateApplicationInfo(s.info.applicationInfo, flags, state); return s.info; } // Make shallow copies so we can store the metadata safely ServiceInfo si = new ServiceInfo(s.info); si.metaData = s.metaData; si.applicationInfo = generateApplicationInfo(s.owner, flags, state, userId); return si; } public final static class Provider extends Component implements Parcelable { @UnsupportedAppUsage public final ProviderInfo info; @UnsupportedAppUsage public boolean syncable; public Provider(final ParseComponentArgs args, final ProviderInfo _info) { super(args, _info); info = _info; info.applicationInfo = args.owner.applicationInfo; syncable = false; } @UnsupportedAppUsage public Provider(Provider existingProvider) { super(existingProvider); this.info = existingProvider.info; this.syncable = existingProvider.syncable; } public void setPackageName(String packageName) { super.setPackageName(packageName); info.packageName = packageName; } public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Provider{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); appendComponentShortName(sb); sb.append('}'); return sb.toString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); dest.writeInt((syncable) ? 1 : 0); } private Provider(Parcel in) { super(in); info = in.readParcelable(Object.class.getClassLoader()); syncable = (in.readInt() == 1); for (ProviderIntentInfo aii : intents) { aii.provider = this; } if (info.readPermission != null) { info.readPermission = info.readPermission.intern(); } if (info.writePermission != null) { info.writePermission = info.writePermission.intern(); } if (info.authority != null) { info.authority = info.authority.intern(); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Provider createFromParcel(Parcel in) { return new Provider(in); } public Provider[] newArray(int size) { return new Provider[size]; } }; } @UnsupportedAppUsage public static final ProviderInfo generateProviderInfo(Provider p, int flags, PackageUserState state, int userId) { if (p == null) return null; if (!checkUseInstalledOrHidden(flags, state, p.owner.applicationInfo)) { return null; } if (!copyNeeded(flags, p.owner, state, p.metaData, userId) && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0 || p.info.uriPermissionPatterns == null)) { updateApplicationInfo(p.info.applicationInfo, flags, state); return p.info; } // Make shallow copies so we can store the metadata safely ProviderInfo pi = new ProviderInfo(p.info); pi.metaData = p.metaData; if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) { pi.uriPermissionPatterns = null; } pi.applicationInfo = generateApplicationInfo(p.owner, flags, state, userId); return pi; } public final static class Instrumentation extends Component implements Parcelable { @UnsupportedAppUsage public final InstrumentationInfo info; public Instrumentation(final ParsePackageItemArgs args, final InstrumentationInfo _info) { super(args, _info); info = _info; } public void setPackageName(String packageName) { super.setPackageName(packageName); info.packageName = packageName; } public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Instrumentation{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); appendComponentShortName(sb); sb.append('}'); return sb.toString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeParcelable(info, flags); } private Instrumentation(Parcel in) { super(in); info = in.readParcelable(Object.class.getClassLoader()); if (info.targetPackage != null) { info.targetPackage = info.targetPackage.intern(); } if (info.targetProcesses != null) { info.targetProcesses = info.targetProcesses.intern(); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Instrumentation createFromParcel(Parcel in) { return new Instrumentation(in); } public Instrumentation[] newArray(int size) { return new Instrumentation[size]; } }; } @UnsupportedAppUsage public static final InstrumentationInfo generateInstrumentationInfo( Instrumentation i, int flags) { if (i == null) return null; if ((flags&PackageManager.GET_META_DATA) == 0) { return i.info; } InstrumentationInfo ii = new InstrumentationInfo(i.info); ii.metaData = i.metaData; return ii; } public static abstract class IntentInfo extends IntentFilter { @UnsupportedAppUsage public boolean hasDefault; @UnsupportedAppUsage public int labelRes; @UnsupportedAppUsage public CharSequence nonLocalizedLabel; @UnsupportedAppUsage public int icon; @UnsupportedAppUsage public int logo; @UnsupportedAppUsage public int banner; public int preferred; @UnsupportedAppUsage protected IntentInfo() { } protected IntentInfo(Parcel dest) { super(dest); hasDefault = (dest.readInt() == 1); labelRes = dest.readInt(); nonLocalizedLabel = dest.readCharSequence(); icon = dest.readInt(); logo = dest.readInt(); banner = dest.readInt(); preferred = dest.readInt(); } public void writeIntentInfoToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(hasDefault ? 1 : 0); dest.writeInt(labelRes); dest.writeCharSequence(nonLocalizedLabel); dest.writeInt(icon); dest.writeInt(logo); dest.writeInt(banner); dest.writeInt(preferred); } } public final static class ActivityIntentInfo extends IntentInfo { @UnsupportedAppUsage public Activity activity; public ActivityIntentInfo(Activity _activity) { activity = _activity; } public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("ActivityIntentInfo{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); activity.appendComponentShortName(sb); sb.append('}'); return sb.toString(); } public ActivityIntentInfo(Parcel in) { super(in); } } public final static class ServiceIntentInfo extends IntentInfo { @UnsupportedAppUsage public Service service; public ServiceIntentInfo(Service _service) { service = _service; } public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("ServiceIntentInfo{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); service.appendComponentShortName(sb); sb.append('}'); return sb.toString(); } public ServiceIntentInfo(Parcel in) { super(in); } } public static final class ProviderIntentInfo extends IntentInfo { @UnsupportedAppUsage public Provider provider; public ProviderIntentInfo(Provider provider) { this.provider = provider; } public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("ProviderIntentInfo{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); provider.appendComponentShortName(sb); sb.append('}'); return sb.toString(); } public ProviderIntentInfo(Parcel in) { super(in); } } /** * @hide */ @UnsupportedAppUsage public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) { sCompatibilityModeEnabled = compatibilityModeEnabled; } /** * @hide */ public static void readConfigUseRoundIcon(Resources r) { if (r != null) { sUseRoundIcon = r.getBoolean(com.android.internal.R.bool.config_useRoundIcon); return; } ApplicationInfo androidAppInfo; try { androidAppInfo = ActivityThread.getPackageManager().getApplicationInfo( "android", 0 /* flags */, UserHandle.myUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Resources systemResources = Resources.getSystem(); // Create in-flight as this overlayable resource is only used when config changes Resources overlayableRes = ResourcesManager.getInstance().getResources(null, null, null, androidAppInfo.resourceDirs, androidAppInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, systemResources.getCompatibilityInfo(), systemResources.getClassLoader()); sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon); } public static class PackageParserException extends Exception { public final int error; public PackageParserException(int error, String detailMessage) { super(detailMessage); this.error = error; } public PackageParserException(int error, String detailMessage, Throwable throwable) { super(detailMessage, throwable); this.error = error; } } // TODO(b/129261524): Clean up API /** * PackageInfo parser specifically for apex files. * NOTE: It will collect certificates * * @param apexInfo * @return PackageInfo * @throws PackageParserException */ public static PackageInfo generatePackageInfoFromApex(ApexInfo apexInfo, int flags) throws PackageParserException { PackageParser pp = new PackageParser(); File apexFile = new File(apexInfo.modulePath); final Package p = pp.parsePackage(apexFile, flags, false); PackageUserState state = new PackageUserState(); PackageInfo pi = generatePackageInfo(p, EmptyArray.INT, flags, 0, 0, Collections.emptySet(), state); pi.applicationInfo.sourceDir = apexFile.getPath(); pi.applicationInfo.publicSourceDir = apexFile.getPath(); if (apexInfo.isFactory) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; } if (apexInfo.isActive) { pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; } else { pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } pi.isApex = true; // Collect certificates if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) { collectCertificates(p, apexFile, false); // Keep legacy mechanism for handling signatures. While this is deprecated, it's // still part of the public API and needs to be maintained if (p.mSigningDetails.hasPastSigningCertificates()) { // Package has included signing certificate rotation information. Return // the oldest cert so that programmatic checks keep working even if unaware // of key rotation. pi.signatures = new Signature[1]; pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0]; } else if (p.mSigningDetails.hasSignatures()) { // otherwise keep old behavior int numberOfSigs = p.mSigningDetails.signatures.length; pi.signatures = new Signature[numberOfSigs]; System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs); } if (p.mSigningDetails != SigningDetails.UNKNOWN) { // only return a valid SigningInfo if there is signing information to report pi.signingInfo = new SigningInfo(p.mSigningDetails); } else { pi.signingInfo = null; } } return pi; } }