summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt6
-rw-r--r--core/java/android/content/pm/parsing/ParsingPackageUtils.java123
-rw-r--r--core/java/android/content/pm/parsing/component/ParsedActivityUtils.java9
-rw-r--r--core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java15
-rw-r--r--core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java15
-rw-r--r--core/java/android/content/pm/parsing/result/ParseInput.java65
-rw-r--r--core/java/android/content/pm/parsing/result/ParseResult.java15
-rw-r--r--core/java/android/content/pm/parsing/result/ParseTypeImpl.java144
-rw-r--r--core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt281
-rw-r--r--services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java35
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageParser2.java108
-rw-r--r--services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkQ.apkbin0 -> 534055 bytes
-rw-r--r--services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkR.apkbin0 -> 534055 bytes
-rw-r--r--services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkQ.apkbin0 -> 534055 bytes
-rw-r--r--services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkR.apkbin0 -> 534055 bytes
-rw-r--r--services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkQ.apkbin0 -> 12638 bytes
-rw-r--r--services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkR.apkbin0 -> 12630 bytes
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt10
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt149
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt34
-rw-r--r--services/tests/servicestests/test-apps/PackageParsingTestManifests/Android.bp70
-rw-r--r--services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestEmptyAction.xml31
-rw-r--r--services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestEmptyCategory.xml29
-rw-r--r--services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestMissingApp.xml24
-rw-r--r--services/tests/servicestests/test-apps/PackageParsingTestManifests/src/com/android/servicestests/pm/parsing/test/TestActivity.kt19
32 files changed, 1106 insertions, 193 deletions
diff --git a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
index 9e463652d0b6..29721c593646 100644
--- a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
@@ -22,6 +22,7 @@ import android.content.pm.PackageParserCacheHelper.WriteHelper
import android.content.pm.parsing.ParsingPackageImpl
import android.content.pm.parsing.ParsingPackageRead
import android.content.pm.parsing.ParsingPackageUtils
+import android.content.pm.parsing.result.ParseInput
import android.content.pm.parsing.result.ParseTypeImpl
import android.content.res.TypedArray
import android.perftests.utils.BenchmarkState
@@ -173,7 +174,10 @@ class PackageParsingPerfTest {
class ParallelParser2(cacher: PackageCacher2? = null)
: ParallelParser<ParsingPackageRead>(cacher) {
- val input = ThreadLocal.withInitial { ParseTypeImpl() }
+ val input = ThreadLocal.withInitial {
+ // For testing, just disable enforcement to avoid hooking up to compat framework
+ ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
+ }
val parser = ParsingPackageUtils(false, null, null,
object : ParsingPackageUtils.Callback {
override fun hasFeature(feature: String) = true
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index e90ccdffa8a8..12328cf32fb3 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -65,7 +65,9 @@ import android.content.pm.parsing.component.ParsedProviderUtils;
import android.content.pm.parsing.component.ParsedService;
import android.content.pm.parsing.component.ParsedServiceUtils;
import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseInput.DeferredError;
import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
@@ -126,6 +128,51 @@ public class ParsingPackageUtils {
public static final String TAG = ParsingUtils.TAG;
+ /**
+ * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off
+ * request, without caching the input object and without querying the internal system state
+ * for feature support.
+ */
+ @NonNull
+ public static ParseResult<ParsingPackage> parseDefaultOneTime(File file, int flags,
+ @NonNull ParseInput.Callback inputCallback, @NonNull Callback callback) {
+ if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
+ // Caller expressed no opinion about what encryption
+ // aware/unaware components they want to see, so match both
+ flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ }
+
+ ParseInput input = new ParseTypeImpl(inputCallback).reset();
+ ParseResult<ParsingPackage> result;
+
+
+ ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, callback);
+ try {
+ result = parser.parsePackage(input, file, flags);
+ if (result.isError()) {
+ return result;
+ }
+ } catch (PackageParser.PackageParserException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Error parsing package", e);
+ }
+
+ try {
+ ParsingPackage pkg = result.getResult();
+ if ((flags & PackageManager.GET_SIGNATURES) != 0
+ || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+ ParsingPackageUtils.collectCertificates(pkg, false /* skipVerify */);
+ }
+
+ return input.success(pkg);
+ } catch (PackageParser.PackageParserException e) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "Error collecting package certificates", e);
+ }
+ }
+
private boolean mOnlyCoreApps;
private String[] mSeparateProcesses;
private DisplayMetrics mDisplayMetrics;
@@ -456,10 +503,11 @@ public class ParsingPackageUtils {
}
if (!foundApp) {
- return input.error(
- PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY,
- "<manifest> does not contain an <application>"
- );
+ ParseResult<?> deferResult = input.deferError(
+ "<manifest> does not contain an <application>", DeferredError.MISSING_APP_TAG);
+ if (deferResult.isError()) {
+ return input.error(deferResult);
+ }
}
return input.success(pkg);
@@ -663,10 +711,12 @@ public class ParsingPackageUtils {
}
if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
- return input.error(
- PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY,
- "<manifest> does not contain an <application> or <instrumentation>"
- );
+ ParseResult<?> deferResult = input.deferError(
+ "<manifest> does not contain an <application> or <instrumentation>",
+ DeferredError.MISSING_APP_TAG);
+ if (deferResult.isError()) {
+ return input.error(deferResult);
+ }
}
if (!ParsedAttribution.isCombinationValid(pkg.getAttributions())) {
@@ -758,11 +808,9 @@ public class ParsingPackageUtils {
return input.success(pkg);
}
- ParseResult nameResult = validateName(input, str, true, true);
- if (nameResult.isError()) {
- if ("android".equals(pkg.getPackageName())) {
- nameResult.ignoreError();
- } else {
+ if (!"android".equals(pkg.getPackageName())) {
+ ParseResult<?> nameResult = validateName(input, str, true, true);
+ if (nameResult.isError()) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID,
"<manifest> specifies bad sharedUserId name \"" + str + "\": "
+ nameResult.getErrorMessage());
@@ -1166,6 +1214,20 @@ public class ParsingPackageUtils {
targetCode = minCode;
}
+ ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion(
+ targetVers, targetCode, PackageParser.SDK_CODENAMES, input);
+ if (targetSdkVersionResult.isError()) {
+ return input.error(targetSdkVersionResult);
+ }
+
+ int targetSdkVersion = targetSdkVersionResult.getResult();
+
+ ParseResult<?> deferResult =
+ input.enableDeferredError(pkg.getPackageName(), targetSdkVersion);
+ if (deferResult.isError()) {
+ return input.error(deferResult);
+ }
+
ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode,
PackageParser.SDK_VERSION, PackageParser.SDK_CODENAMES, input);
if (minSdkVersionResult.isError()) {
@@ -1174,14 +1236,6 @@ public class ParsingPackageUtils {
int minSdkVersion = minSdkVersionResult.getResult();
- ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion(
- targetVers, targetCode, PackageParser.SDK_CODENAMES, input);
- if (targetSdkVersionResult.isError()) {
- return input.error(targetSdkVersionResult);
- }
-
- int targetSdkVersion = minSdkVersionResult.getResult();
-
pkg.setMinSdkVersion(minSdkVersion)
.setTargetSdkVersion(targetSdkVersion);
@@ -1763,9 +1817,15 @@ public class ParsingPackageUtils {
// Add a hidden app detail activity to normal apps which forwards user to App Details
// page.
ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
- // Backwards-compat, assume success
+ if (a.isError()) {
+ // Error should be impossible here, as the only failure case as of SDK R is a
+ // string validation error on a constant ":app_details" string passed in by the
+ // parsing code itself. For this reason, this is just a hard failure instead of
+ // deferred.
+ return input.error(a);
+ }
+
pkg.addActivity(a.getResult());
- a.ignoreError();
}
if (hasActivityOrder) {
@@ -2122,18 +2182,14 @@ public class ParsingPackageUtils {
private static ParseResult<ParsedActivity> generateAppDetailsHiddenActivity(ParseInput input,
ParsingPackage pkg) {
String packageName = pkg.getPackageName();
- ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
+ ParseResult<String> result = ComponentParseUtils.buildTaskAffinityName(
packageName, packageName, ":app_details", input);
-
- String taskAffinity;
- if (taskAffinityResult.isSuccess()) {
- taskAffinity = taskAffinityResult.getResult();
- } else {
- // Backwards-compat, do not fail
- taskAffinity = null;
- taskAffinityResult.ignoreError();
+ if (result.isError()) {
+ return input.error(result);
}
+ String taskAffinity = result.getResult();
+
// Build custom App Details activity info instead of parsing it from xml
return input.success(ParsedActivity.makeAppDetailsActivity(packageName,
pkg.getProcessName(), pkg.getUiOptions(), taskAffinity,
@@ -2688,7 +2744,8 @@ public class ParsingPackageUtils {
public interface Callback {
boolean hasFeature(String feature);
- ParsingPackage startParsingPackage(String packageName, String baseCodePath, String codePath,
+ ParsingPackage startParsingPackage(@NonNull String packageName,
+ @NonNull String baseCodePath, @NonNull String codePath,
@NonNull TypedArray manifestArray, boolean isCoreApp);
}
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
index 6e5c51a22025..f64560a14832 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -179,13 +179,12 @@ public class ParsedActivityUtils {
ParseResult<String> affinityNameResult = ComponentParseUtils.buildTaskAffinityName(
packageName, pkg.getTaskAffinity(), taskAffinity, input);
- if (affinityNameResult.isSuccess()) {
- activity.taskAffinity = affinityNameResult.getResult();
- } else {
- // Backwards-compat, ignore error
- affinityNameResult.ignoreError();
+ if (affinityNameResult.isError()) {
+ return input.error(affinityNameResult);
}
+ activity.taskAffinity = affinityNameResult.getResult();
+
boolean visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
if (visibleToEphemeral) {
activity.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
index a7b950b194d2..390f76968e7c 100644
--- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
@@ -29,7 +29,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.PatternMatcher;
-import android.text.TextUtils;
import android.util.Slog;
import android.util.TypedValue;
@@ -97,8 +96,13 @@ public class ParsedIntentInfoUtils {
case "action": {
String value = parser.getAttributeValue(PackageParser.ANDROID_RESOURCES,
"name");
- if (TextUtils.isEmpty(value)) {
+ if (value == null) {
result = input.error("No value supplied for <android:name>");
+ } else if (value.isEmpty()) {
+ intentInfo.addAction(value);
+ // Prior to R, this was not a failure
+ result = input.deferError("No value supplied for <android:name>",
+ ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
} else {
intentInfo.addAction(value);
result = input.success(null);
@@ -108,8 +112,13 @@ public class ParsedIntentInfoUtils {
case "category": {
String value = parser.getAttributeValue(PackageParser.ANDROID_RESOURCES,
"name");
- if (TextUtils.isEmpty(value)) {
+ if (value == null) {
result = input.error("No value supplied for <android:name>");
+ } else if (value.isEmpty()) {
+ intentInfo.addCategory(value);
+ // Prior to R, this was not a failure
+ result = input.deferError("No value supplied for <android:name>",
+ ParseInput.DeferredError.EMPTY_INTENT_ACTION_CATEGORY);
} else {
intentInfo.addCategory(value);
result = input.success(null);
diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java
index 6188f8933ab2..f4c9914cb69f 100644
--- a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java
@@ -20,6 +20,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.IntentFilter;
import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -28,9 +31,6 @@ import android.os.Build;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.result.ParseInput;
-import android.content.pm.parsing.result.ParseResult;
import org.xmlpull.v1.XmlPullParserException;
@@ -83,12 +83,11 @@ class ParsedMainComponentUtils {
ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
pkg.getPackageName(), pkg.getProcessName(), processName, flags,
separateProcesses, input);
- if (processNameResult.isSuccess()) {
- component.setProcessName(processNameResult.getResult());
- } else {
- // Backwards-compat, ignore error
- processNameResult.ignoreError();
+ if (processNameResult.isError()) {
+ return input.error(processNameResult);
}
+
+ component.setProcessName(processNameResult.getResult());
}
if (splitNameAttr != null) {
diff --git a/core/java/android/content/pm/parsing/result/ParseInput.java b/core/java/android/content/pm/parsing/result/ParseInput.java
index c46850609d9e..538510049e04 100644
--- a/core/java/android/content/pm/parsing/result/ParseInput.java
+++ b/core/java/android/content/pm/parsing/result/ParseInput.java
@@ -16,10 +16,12 @@
package android.content.pm.parsing.result;
-import android.annotation.Hide;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.pm.PackageManager;
+import android.os.Build;
/**
* Used as a method parameter which is then transformed into a {@link ParseResult}. This is
@@ -30,8 +32,52 @@ import android.content.pm.PackageManager;
*/
public interface ParseInput {
+ /**
+ * Errors encountered during parsing may rely on the targetSDK version of the application to
+ * determine whether or not to fail. These are passed into {@link #deferError(String, long)}
+ * when encountered, and the implementation will handle how to defer the errors until the
+ * targetSdkVersion is known and sent to {@link #enableDeferredError(String, int)}.
+ *
+ * All of these must be marked {@link ChangeId}, as that is the mechanism used to check if the
+ * error must be propagated. This framework also allows developers to pre-disable specific
+ * checks if they wish to target a newer SDK version in a development environment without
+ * having to migrate their entire app to validate on a newer platform.
+ */
+ final class DeferredError {
+ /**
+ * Missing an "application" or "instrumentation" tag.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ public static final long MISSING_APP_TAG = 150776642;
+
+ /**
+ * An intent filter's actor or category is an empty string. A bug in the platform before R
+ * allowed this to pass through without an error. This does not include cases when the
+ * attribute is null/missing, as that has always been a failure.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ public static final long EMPTY_INTENT_ACTION_CATEGORY = 151163173;
+ }
+
<ResultType> ParseResult<ResultType> success(ResultType result);
+ /**
+ * Used for errors gated by {@link DeferredError}. Will return an error result if the
+ * targetSdkVersion is already known and this must be returned as a real error. The result
+ * contains null and should not be unwrapped.
+ *
+ * @see #error(String)
+ */
+ ParseResult<?> deferError(@NonNull String parseError, long deferredError);
+
+ /**
+ * Called after targetSdkVersion is known. Returns an error result if a previously deferred
+ * error was registered. The result contains null and should not be unwrapped.
+ */
+ ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion);
+
/** @see #error(int, String, Exception) */
<ResultType> ParseResult<ResultType> error(int parseError);
@@ -52,9 +98,6 @@ public interface ParseInput {
* The calling site of that method is then expected to check the result for error, and
* continue to bubble up if it is an error.
*
- * Or, if the code explicitly handles an error,
- * {@link ParseResult#ignoreError()} should be called.
- *
* If the result {@link ParseResult#isSuccess()}, then it can be used as-is, as
* overlapping/consecutive successes are allowed.
*/
@@ -66,5 +109,17 @@ public interface ParseInput {
* but cast the type of the {@link ParseResult} for type safety, since the parameter
* and the receiver should be the same object.
*/
- <ResultType> ParseResult<ResultType> error(ParseResult result);
+ <ResultType> ParseResult<ResultType> error(ParseResult<?> result);
+
+ /**
+ * Implemented instead of a direct reference to
+ * {@link com.android.internal.compat.IPlatformCompat}, allowing caching and testing logic to
+ * be separated out.
+ */
+ interface Callback {
+ /**
+ * @return true if the changeId should be enabled
+ */
+ boolean isChangeEnabled(long changeId, @NonNull String packageName, int targetSdkVersion);
+ }
}
diff --git a/core/java/android/content/pm/parsing/result/ParseResult.java b/core/java/android/content/pm/parsing/result/ParseResult.java
index 338048c3c1ec..518395d7d392 100644
--- a/core/java/android/content/pm/parsing/result/ParseResult.java
+++ b/core/java/android/content/pm/parsing/result/ParseResult.java
@@ -17,32 +17,19 @@
package android.content.pm.parsing.result;
import android.annotation.Nullable;
-import android.content.pm.PackageParser;
/**
* The output side of {@link ParseInput}, which must result from a method call on
* {@link ParseInput}.
*
* When using this class, keep in mind that all {@link ParseInput}s and {@link ParseResult}s
- * are the exact same object, scoped to a per {@link PackageParser} instance, per thread basis,
- * thrown around and casted everywhere for type safety.
+ * are the exact same object, scoped per thread, thrown around and casted for type safety.
*
* @hide
*/
public interface ParseResult<ResultType> {
/**
- * Un-marks this result as an error, also allowing it to be re-used as {@link ParseInput}.
- *
- * This should only be used in cases where it's absolutely certain that error handling is
- * irrelevant. Such as for backwards compatibility where it previously didn't fail and that
- * behavior has to be maintained.
- *
- * Mostly an alias for readability.
- */
- void ignoreError();
-
- /**
* Returns true if the result is not an error and thus contains a valid object.
*
* For backwards-compat reasons, it's possible to have a successful result with a null
diff --git a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
index 9b22f09b2978..b26bf71a61c5 100644
--- a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
+++ b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java
@@ -20,54 +20,133 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.parsing.ParsingUtils;
+import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
-import java.util.Arrays;
+import com.android.internal.util.CollectionUtils;
/** @hide */
public class ParseTypeImpl implements ParseInput, ParseResult<Object> {
private static final String TAG = ParsingUtils.TAG;
- private static final boolean DEBUG_FILL_STACK_TRACE = false;
+ public static final boolean DEBUG_FILL_STACK_TRACE = false;
- private static final boolean DEBUG_LOG_ON_ERROR = false;
+ public static final boolean DEBUG_LOG_ON_ERROR = false;
- private Object result;
+ public static final boolean DEBUG_THROW_ALL_ERRORS = false;
- private int errorCode = PackageManager.INSTALL_SUCCEEDED;
+ @NonNull
+ private Callback mCallback;
+
+ private Object mResult;
+
+ private int mErrorCode = PackageManager.INSTALL_SUCCEEDED;
@Nullable
- private String errorMessage;
+ private String mErrorMessage;
@Nullable
- private Exception exception;
+ private Exception mException;
- public ParseInput reset() {
- this.result = null;
- this.errorCode = PackageManager.INSTALL_SUCCEEDED;
- this.errorMessage = null;
- this.exception = null;
- return this;
+ /**
+ * Errors encountered before targetSdkVersion is known.
+ * The size upper bound is the number of longs in {@link DeferredError}
+ */
+ @Nullable
+ private ArrayMap<Long, String> mDeferredErrors = null;
+
+ private String mPackageName;
+ private Integer mTargetSdkVersion;
+
+ /**
+ * @param callback if nullable, fallback to manual targetSdk > Q check
+ */
+ public ParseTypeImpl(@NonNull Callback callback) {
+ mCallback = callback;
}
- @Override
- public void ignoreError() {
- reset();
+ public ParseInput reset() {
+ mResult = null;
+ mErrorCode = PackageManager.INSTALL_SUCCEEDED;
+ mErrorMessage = null;
+ mException = null;
+ if (mDeferredErrors != null) {
+ // If the memory was already allocated, don't bother freeing and re-allocating,
+ // as this could occur hundreds of times depending on what the caller is doing and
+ // how many APKs they're going through.
+ mDeferredErrors.erase();
+ }
+ return this;
}
@Override
public <ResultType> ParseResult<ResultType> success(ResultType result) {
- if (errorCode != PackageManager.INSTALL_SUCCEEDED || errorMessage != null) {
- throw new IllegalStateException("Cannot set to success after set to error, was "
- + errorMessage, exception);
+ if (mErrorCode != PackageManager.INSTALL_SUCCEEDED) {
+ Slog.wtf(ParsingUtils.TAG, "Cannot set to success after set to error, was "
+ + mErrorMessage, mException);
}
- this.result = result;
+ mResult = result;
//noinspection unchecked
return (ParseResult<ResultType>) this;
}
@Override
+ public ParseResult<?> deferError(@NonNull String parseError, long deferredError) {
+ if (DEBUG_THROW_ALL_ERRORS) {
+ return error(parseError);
+ }
+ if (mTargetSdkVersion != null) {
+ if (mDeferredErrors != null && mDeferredErrors.containsKey(deferredError)) {
+ // If the map already contains the key, that means it's already been checked and
+ // found to be disabled. Otherwise it would've failed when mTargetSdkVersion was
+ // set to non-null.
+ return success(null);
+ }
+
+ if (mCallback.isChangeEnabled(deferredError, mPackageName, mTargetSdkVersion)) {
+ return error(parseError);
+ } else {
+ if (mDeferredErrors == null) {
+ mDeferredErrors = new ArrayMap<>();
+ }
+ mDeferredErrors.put(deferredError, null);
+ return success(null);
+ }
+ }
+
+ if (mDeferredErrors == null) {
+ mDeferredErrors = new ArrayMap<>();
+ }
+
+ // Only save the first occurrence of any particular error
+ mDeferredErrors.putIfAbsent(deferredError, parseError);
+ return success(null);
+ }
+
+ @Override
+ public ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion) {
+ mPackageName = packageName;
+ mTargetSdkVersion = targetSdkVersion;
+
+ int size = CollectionUtils.size(mDeferredErrors);
+ for (int index = size - 1; index >= 0; index--) {
+ long changeId = mDeferredErrors.keyAt(index);
+ String errorMessage = mDeferredErrors.valueAt(index);
+ if (mCallback.isChangeEnabled(changeId, mPackageName, mTargetSdkVersion)) {
+ return error(errorMessage);
+ } else {
+ // No point holding onto the string, but need to maintain the key to signal
+ // that the error was checked with isChangeEnabled and found to be disabled.
+ mDeferredErrors.setValueAt(index, null);
+ }
+ }
+
+ return success(null);
+ }
+
+ @Override
public <ResultType> ParseResult<ResultType> error(int parseError) {
return error(parseError, null);
}
@@ -84,25 +163,26 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> {
}
@Override
- public <ResultType> ParseResult<ResultType> error(ParseResult intentResult) {
- return error(intentResult.getErrorCode(), intentResult.getErrorMessage());
+ public <ResultType> ParseResult<ResultType> error(ParseResult<?> intentResult) {
+ return error(intentResult.getErrorCode(), intentResult.getErrorMessage(),
+ intentResult.getException());
}
@Override
public <ResultType> ParseResult<ResultType> error(int errorCode, @Nullable String errorMessage,
Exception exception) {
- this.errorCode = errorCode;
- this.errorMessage = errorMessage;
- this.exception = exception;
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ mException = exception;
if (DEBUG_FILL_STACK_TRACE) {
if (exception == null) {
- this.exception = new Exception();
+ mException = new Exception();
}
}
if (DEBUG_LOG_ON_ERROR) {
- Exception exceptionToLog = this.exception != null ? this.exception : new Exception();
+ Exception exceptionToLog = mException != null ? mException : new Exception();
Log.w(TAG, "ParseInput set to error " + errorCode + ", " + errorMessage,
exceptionToLog);
}
@@ -113,12 +193,12 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> {
@Override
public Object getResult() {
- return this.result;
+ return mResult;
}
@Override
public boolean isSuccess() {
- return errorCode == PackageManager.INSTALL_SUCCEEDED;
+ return mErrorCode == PackageManager.INSTALL_SUCCEEDED;
}
@Override
@@ -128,18 +208,18 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> {
@Override
public int getErrorCode() {
- return errorCode;
+ return mErrorCode;
}
@Nullable
@Override
public String getErrorMessage() {
- return errorMessage;
+ return mErrorMessage;
}
@Nullable
@Override
public Exception getException() {
- return exception;
+ return mException;
}
}
diff --git a/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt b/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt
new file mode 100644
index 000000000000..d45fee97950f
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.result
+
+import android.content.pm.PackageManager
+import android.os.Build
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import java.io.IOException
+
+class ParseInputAndResultTest {
+
+ companion object {
+
+ private const val TEST_PACKAGE = "com.android.test"
+
+ private const val ENABLED_ERROR = 11L
+ private const val DISABLED_ERROR = 22L
+
+ @JvmStatic
+ @BeforeClass
+ fun assumeNotDebug() {
+ // None of these tests consider cases where debugging logic is enabled
+ assumeFalse(ParseTypeImpl.DEBUG_FILL_STACK_TRACE)
+ assumeFalse(ParseTypeImpl.DEBUG_LOG_ON_ERROR)
+ assumeFalse(ParseTypeImpl.DEBUG_THROW_ALL_ERRORS)
+ }
+ }
+
+ private lateinit var mockCallback: ParseInput.Callback
+ private lateinit var input: ParseInput
+
+ @Before
+ fun createInput() {
+ // Use an open class instead off a lambda so it can be spied
+ open class TestCallback : ParseInput.Callback {
+ override fun isChangeEnabled(changeId: Long, pkgName: String, targetSdk: Int): Boolean {
+ return when (changeId) {
+ ENABLED_ERROR -> targetSdk > Build.VERSION_CODES.Q
+ DISABLED_ERROR -> false
+ else -> throw IllegalStateException("changeId $changeId is not mocked for test")
+ }
+ }
+ }
+
+ mockCallback = spy(TestCallback())
+ input = ParseTypeImpl(mockCallback)
+ }
+
+ @Test
+ fun errorCode() {
+ val errorCode = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE
+ val result = input.error<Any?>(errorCode)
+ assertError(result)
+ assertThat(result.errorCode).isEqualTo(errorCode)
+ assertThat(result.errorMessage).isNull()
+ assertThat(result.exception).isNull()
+ }
+
+ @Test
+ fun errorMessage() {
+ val errorMessage = "Test error"
+ val result = input.error<Any?>(errorMessage)
+ assertError(result)
+ assertThat(result.errorCode).isNotEqualTo(PackageManager.INSTALL_SUCCEEDED)
+ assertThat(result.errorMessage).isEqualTo(errorMessage)
+ assertThat(result.exception).isNull()
+ }
+
+ @Test
+ fun errorCodeAndMessage() {
+ val errorCode = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE
+ val errorMessage = "Test error"
+ val result = input.error<Any?>(errorCode, errorMessage)
+ assertError(result)
+ assertThat(result.errorCode).isEqualTo(errorCode)
+ assertThat(result.errorMessage).isEqualTo(errorMessage)
+ assertThat(result.exception).isNull()
+ }
+
+ @Test
+ fun errorCodeAndMessageAndException() {
+ val errorCode = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE
+ val errorMessage = "Test error"
+ val exception = IOException()
+ val result = input.error<Any?>(errorCode, errorMessage, exception)
+ assertError(result)
+ assertThat(result.errorCode).isEqualTo(errorCode)
+ assertThat(result.errorMessage).isEqualTo(errorMessage)
+ assertThat(result.exception).isSameAs(exception)
+ }
+
+ @Test
+ fun errorCarryResult() {
+ val errorCode = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE
+ val errorMessage = "Test error"
+ val exception = IOException()
+ val result = input.error<Any?>(errorCode, errorMessage, exception)
+ assertError(result)
+ assertThat(result.errorCode).isEqualTo(errorCode)
+ assertThat(result.errorMessage).isEqualTo(errorMessage)
+ assertThat(result.exception).isSameAs(exception)
+
+ val carriedResult = input.error<Int>(result)
+ assertError(carriedResult)
+ assertThat(carriedResult.errorCode).isEqualTo(errorCode)
+ assertThat(carriedResult.errorMessage).isEqualTo(errorMessage)
+ assertThat(carriedResult.exception).isSameAs(exception)
+ }
+
+ @Test
+ fun success() {
+ val value = "Test success"
+ assertSuccess(value, input.success(value))
+ }
+
+ @Test
+ fun deferErrorEnableFirstSdkQ() {
+ assertSuccess(input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.Q))
+
+ assertSuccess(input.deferError("Test error", ENABLED_ERROR))
+ }
+
+ @Test
+ fun deferErrorEnableLastSdkQ() {
+ assertSuccess(input.deferError("Test error", ENABLED_ERROR))
+
+ assertSuccess(input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.Q))
+ }
+
+ @Test
+ fun deferErrorEnableFirstSdkR() {
+ val error = "Test error"
+ assertSuccess(input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.R))
+
+ val deferResult = input.deferError(error, ENABLED_ERROR)
+ assertError(deferResult)
+ assertThat(deferResult.errorCode).isNotEqualTo(PackageManager.INSTALL_SUCCEEDED)
+ assertThat(deferResult.errorMessage).isEqualTo(error)
+ assertThat(deferResult.exception).isNull()
+ }
+
+ @Test
+ fun deferErrorEnableLastSdkR() {
+ val error = "Test error"
+ assertSuccess(input.deferError(error, ENABLED_ERROR))
+
+ val result = input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.R)
+ assertError(result)
+ assertThat(result.errorCode).isNotEqualTo(PackageManager.INSTALL_SUCCEEDED)
+ assertThat(result.errorMessage).isEqualTo(error)
+ assertThat(result.exception).isNull()
+ }
+
+ @Test
+ fun enableDeferredErrorAndSuccessSdkQ() {
+ val value = "Test success"
+ assertSuccess(input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.Q))
+
+ assertSuccess(value, input.success(value))
+ }
+
+ @Test
+ fun enableDeferredErrorAndSuccessSdkR() {
+ val value = "Test success"
+ assertSuccess(input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.R))
+
+ assertSuccess(value, input.success(value))
+ }
+
+ @Test
+ fun multipleDeferErrorKeepsFirst() {
+ val errorOne = "Test error one"
+ val errorTwo = "Test error two"
+
+ assertSuccess(input.deferError(errorOne, ENABLED_ERROR))
+ assertSuccess(input.deferError(errorTwo, ENABLED_ERROR))
+
+ val result = input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.R)
+ assertError(result)
+ assertThat(result.errorCode).isNotEqualTo(PackageManager.INSTALL_SUCCEEDED)
+ assertThat(result.errorMessage).isEqualTo(errorOne)
+ assertThat(result.exception).isNull()
+ }
+
+ @Test
+ fun multipleDisabledErrorsQueriesOnceEnableFirst() {
+ val errorOne = "Test error one"
+ val errorTwo = "Test error two"
+
+ assertSuccess(input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.R))
+
+ assertSuccess(input.deferError(errorOne, DISABLED_ERROR))
+
+ verify(mockCallback, times(1)).isChangeEnabled(anyLong(), anyString(), anyInt())
+
+ assertSuccess(input.deferError(errorTwo, DISABLED_ERROR))
+
+ verifyNoMoreInteractions(mockCallback)
+ }
+
+ @Test
+ fun multipleDisabledErrorsQueriesOnceEnableSecond() {
+ val errorOne = "Test error one"
+ val errorTwo = "Test error two"
+
+ assertSuccess(input.deferError(errorOne, DISABLED_ERROR))
+
+ verify(mockCallback, never()).isChangeEnabled(anyLong(), anyString(), anyInt())
+
+ assertSuccess(input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.R))
+
+ verify(mockCallback, times(1)).isChangeEnabled(anyLong(), anyString(), anyInt())
+
+ assertSuccess(input.deferError(errorTwo, DISABLED_ERROR))
+
+ verifyNoMoreInteractions(mockCallback)
+ }
+
+ @After
+ fun verifyReset() {
+ var result = (input as ParseTypeImpl).reset() as ParseResult<*>
+ result.assertReset()
+
+ // The deferred error is not directly accessible, so attempt to re-enable the deferred
+ // error and assert it was also reset.
+ result = input.enableDeferredError(TEST_PACKAGE, Build.VERSION_CODES.R)
+ result.assertReset()
+ }
+
+ private fun assertSuccess(result: ParseResult<*>) = assertSuccess(null, result)
+
+ private fun assertSuccess(expected: Any? = null, result: ParseResult<*>) {
+ assertThat(result.isError).isFalse()
+ assertThat(result.isSuccess).isTrue()
+ assertThat(result.result).isSameAs(expected)
+ assertThat(result.errorCode).isEqualTo(PackageManager.INSTALL_SUCCEEDED)
+ assertThat(result.errorMessage).isNull()
+ assertThat(result.exception).isNull()
+ }
+
+ private fun assertError(result: ParseResult<*>) {
+ assertThat(result.isError).isTrue()
+ assertThat(result.isSuccess).isFalse()
+ assertThat(result.result).isNull()
+ }
+
+ private fun ParseResult<*>.assertReset() {
+ assertThat(this.isSuccess).isTrue()
+ assertThat(this.isError).isFalse()
+ assertThat(this.errorCode).isEqualTo(PackageManager.INSTALL_SUCCEEDED)
+ assertThat(this.errorMessage).isNull()
+ assertThat(this.exception).isNull()
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 44ab43828c2d..273fd48b0d96 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -91,6 +91,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import java.util.function.Supplier;
/** Implementation of {@link AppIntegrityManagerService}. */
public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
@@ -125,6 +126,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
private final Context mContext;
private final Handler mHandler;
private final PackageManagerInternal mPackageManagerInternal;
+ private final Supplier<PackageParser2> mParserSupplier;
private final RuleEvaluationEngine mEvaluationEngine;
private final IntegrityFileManager mIntegrityFileManager;
@@ -136,6 +138,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
return new AppIntegrityManagerServiceImpl(
context,
LocalServices.getService(PackageManagerInternal.class),
+ PackageParser2::forParsingFileWithDefaults,
RuleEvaluationEngine.getRuleEvaluationEngine(),
IntegrityFileManager.getInstance(),
handlerThread.getThreadHandler());
@@ -145,11 +148,13 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
AppIntegrityManagerServiceImpl(
Context context,
PackageManagerInternal packageManagerInternal,
+ Supplier<PackageParser2> parserSupplier,
RuleEvaluationEngine evaluationEngine,
IntegrityFileManager integrityFileManager,
Handler handler) {
mContext = context;
mPackageManagerInternal = packageManagerInternal;
+ mParserSupplier = parserSupplier;
mEvaluationEngine = evaluationEngine;
mIntegrityFileManager = integrityFileManager;
mHandler = handler;
@@ -562,8 +567,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
throw new IllegalArgumentException("Installation path is null, package not found");
}
- PackageParser2 parser = new PackageParser2(null, false, null, null, null);
- try {
+ try (PackageParser2 parser = mParserSupplier.get()) {
ParsedPackage pkg = parser.parsePackage(installationPath, 0, false);
int flags = PackageManager.GET_SIGNING_CERTIFICATES | PackageManager.GET_META_DATA;
pkg.setSigningDetails(ParsingPackageUtils.collectCertificates(pkg, false));
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 414449d42d78..8850f29e9bc2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1035,13 +1035,7 @@ public class PackageManagerService extends IPackageManager.Stub
private final AppsFilter mAppsFilter;
- class PackageParserCallback extends PackageParser2.Callback {
- @Override public final boolean hasFeature(String feature) {
- return PackageManagerService.this.hasSystemFeature(feature, 0);
- }
- }
-
- final PackageParser2.Callback mPackageParserCallback = new PackageParserCallback();
+ final PackageParser2.Callback mPackageParserCallback;
// Currently known shared libraries.
final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = new ArrayMap<>();
@@ -2720,6 +2714,18 @@ public class PackageManagerService extends IPackageManager.Stub
mPermissionManagerService = (IPermissionManager) ServiceManager.getService("permissionmgr");
mIncrementalManager =
(IncrementalManager) mContext.getSystemService(Context.INCREMENTAL_SERVICE);
+ PlatformCompat platformCompat = mInjector.getCompatibility();
+ mPackageParserCallback = new PackageParser2.Callback() {
+ @Override
+ public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+ return platformCompat.isChangeEnabled(changeId, appInfo);
+ }
+
+ @Override
+ public boolean hasFeature(String feature) {
+ return PackageManagerService.this.hasSystemFeature(feature, 0);
+ }
+ };
// CHECKSTYLE:ON IndentationCheck
t.traceEnd();
@@ -3075,6 +3081,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
+ packageParser.close();
+
List<Runnable> unfinishedTasks = executorService.shutdownNow();
if (!unfinishedTasks.isEmpty()) {
throw new IllegalStateException("Not all tasks finished before calling close: "
@@ -8901,12 +8909,11 @@ public class PackageManagerService extends IPackageManager.Stub
private AndroidPackage scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
- PackageParser2 pp = new PackageParser2(mSeparateProcesses, mOnlyCore, mMetrics, null,
- mPackageParserCallback);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final ParsedPackage parsedPackage;
- try {
+ try (PackageParser2 pp = new PackageParser2(mSeparateProcesses, mOnlyCore, mMetrics, null,
+ mPackageParserCallback)) {
parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
@@ -16676,12 +16683,10 @@ public class PackageManagerService extends IPackageManager.Stub
| PackageParser.PARSE_ENFORCE_CODE
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
- PackageParser2 pp = new PackageParser2(mSeparateProcesses, false, mMetrics, null,
- mPackageParserCallback);
-
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
ParsedPackage parsedPackage;
- try {
+ try (PackageParser2 pp = new PackageParser2(mSeparateProcesses, false, mMetrics, null,
+ mPackageParserCallback)) {
parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
} catch (PackageParserException e) {
@@ -16746,8 +16751,6 @@ public class PackageManagerService extends IPackageManager.Stub
"Instant app package must be signed with APK Signature Scheme v2 or greater");
}
- // Get rid of all references to package scan path via parser.
- pp = null;
boolean systemApp = false;
boolean replace = false;
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index d561b9c1ffdd..e860c4857bf4 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -17,7 +17,10 @@
package com.android.server.pm.parsing;
import android.annotation.AnyThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.parsing.ParsingPackage;
@@ -27,10 +30,13 @@ import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.TypedArray;
import android.os.Build;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Slog;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.PackageManagerService;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
@@ -39,19 +45,53 @@ import java.io.File;
/**
* The v2 of {@link PackageParser} for use when parsing is initiated in the server and must
* contain state contained by the server.
+ *
+ * The {@link AutoCloseable} helps signal that this class contains resources that must be freed.
+ * Although it is sufficient to release references to an instance of this class and let it get
+ * collected automatically.
*/
-public class PackageParser2 {
+public class PackageParser2 implements AutoCloseable {
+
+ /**
+ * For parsing inside the system server but outside of {@link PackageManagerService}.
+ * Generally used for parsing information in an APK that hasn't been installed yet.
+ *
+ * This must be called inside the system process as it relies on {@link ServiceManager}.
+ */
+ public static PackageParser2 forParsingFileWithDefaults() {
+ PlatformCompat platformCompat =
+ (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+ return new PackageParser2(null /* separateProcesses */, false /* onlyCoreApps */,
+ null /* displayMetrics */, null /* cacheDir */, new Callback() {
+ @Override
+ public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+ return platformCompat.isChangeEnabled(changeId, appInfo);
+ }
+
+ @Override
+ public boolean hasFeature(String feature) {
+ // Assume the device doesn't support anything. This will affect permission parsing
+ // and will force <uses-permission/> declarations to include all requiredNotFeature
+ // permissions and exclude all requiredFeature permissions. This mirrors the old
+ // behavior.
+ return false;
+ }
+ });
+ }
static final String TAG = "PackageParser2";
private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100;
- private ThreadLocal<ParseTypeImpl> mSharedResult = ThreadLocal.withInitial(ParseTypeImpl::new);
+ private ThreadLocal<ApplicationInfo> mSharedAppInfo =
+ ThreadLocal.withInitial(() -> {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.uid = -1; // Not a valid UID since the app will not be installed yet
+ return appInfo;
+ });
- private final String[] mSeparateProcesses;
- private final boolean mOnlyCoreApps;
- private final DisplayMetrics mDisplayMetrics;
+ private ThreadLocal<ParseTypeImpl> mSharedResult;
@Nullable
protected PackageCacher mCacher;
@@ -64,27 +104,26 @@ public class PackageParser2 {
* creating a minimalist boot environment.
*/
public PackageParser2(String[] separateProcesses, boolean onlyCoreApps,
- DisplayMetrics displayMetrics, @Nullable File cacheDir, Callback callback) {
- mSeparateProcesses = separateProcesses;
- mOnlyCoreApps = onlyCoreApps;
-
+ DisplayMetrics displayMetrics, @Nullable File cacheDir, @NonNull Callback callback) {
if (displayMetrics == null) {
- mDisplayMetrics = new DisplayMetrics();
- mDisplayMetrics.setToDefaults();
- } else {
- mDisplayMetrics = displayMetrics;
+ displayMetrics = new DisplayMetrics();
+ displayMetrics.setToDefaults();
}
mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
- // TODO(b/135203078): Remove nullability of callback
- callback = callback != null ? callback : new Callback() {
- @Override
- public boolean hasFeature(String feature) {
- return false;
- }
+
+ parsingUtils = new ParsingPackageUtils(onlyCoreApps, separateProcesses, displayMetrics,
+ callback);
+
+ ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
+ ApplicationInfo appInfo = mSharedAppInfo.get();
+ //noinspection ConstantConditions
+ appInfo.packageName = packageName;
+ appInfo.targetSdkVersion = targetSdkVersion;
+ return callback.isChangeEnabled(changeId, appInfo);
};
- parsingUtils = new ParsingPackageUtils(onlyCoreApps, separateProcesses, displayMetrics, callback);
+ mSharedResult = ThreadLocal.withInitial(() -> new ParseTypeImpl(enforcementCallback));
}
/**
@@ -126,13 +165,38 @@ public class PackageParser2 {
return parsed;
}
+ /**
+ * Removes the cached value for the thread the parser was created on. It is assumed that
+ * any threads created for parallel parsing will be created and released, so they don't
+ * need an explicit close call.
+ *
+ * Realistically an instance should never be retained, so when the enclosing class is released,
+ * the values will also be released, making this method unnecessary.
+ */
+ @Override
+ public void close() {
+ mSharedResult.remove();
+ mSharedAppInfo.remove();
+ }
+
public static abstract class Callback implements ParsingPackageUtils.Callback {
@Override
- public final ParsingPackage startParsingPackage(String packageName, String baseCodePath,
- String codePath, TypedArray manifestArray, boolean isCoreApp) {
+ public final ParsingPackage startParsingPackage(@NonNull String packageName,
+ @NonNull String baseCodePath, @NonNull String codePath,
+ @NonNull TypedArray manifestArray, boolean isCoreApp) {
return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray,
isCoreApp);
}
+
+ /**
+ * An indirection from {@link ParseInput.Callback#isChangeEnabled(long, String, int)},
+ * allowing the {@link ApplicationInfo} objects to be cached in {@link #mSharedAppInfo}
+ * and cleaned up with the parser instance, not the callback instance.
+ *
+ * @param appInfo will only have 3 fields filled in, {@link ApplicationInfo#packageName},
+ * {@link ApplicationInfo#targetSdkVersion}, and {@link ApplicationInfo#uid}
+ */
+ public abstract boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo);
}
}
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkQ.apk b/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkQ.apk
new file mode 100644
index 000000000000..841fcebd6c1f
--- /dev/null
+++ b/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkQ.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkR.apk b/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkR.apk
new file mode 100644
index 000000000000..3520650215ef
--- /dev/null
+++ b/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyActionSdkR.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkQ.apk b/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkQ.apk
new file mode 100644
index 000000000000..6774d5f0a452
--- /dev/null
+++ b/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkQ.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkR.apk b/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkR.apk
new file mode 100644
index 000000000000..23efb1977bbc
--- /dev/null
+++ b/services/tests/servicestests/res/raw/PackageParsingTestAppEmptyCategorySdkR.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkQ.apk b/services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkQ.apk
new file mode 100644
index 000000000000..f684f8691151
--- /dev/null
+++ b/services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkQ.apk
Binary files differ
diff --git a/services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkR.apk b/services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkR.apk
new file mode 100644
index 000000000000..491c56cef86b
--- /dev/null
+++ b/services/tests/servicestests/res/raw/PackageParsingTestAppMissingAppSdkR.apk
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index d3a3e7e32ece..e5741ee1a384 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -67,8 +67,11 @@ import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import com.android.server.compat.PlatformCompat;
import com.android.server.integrity.engine.RuleEvaluationEngine;
import com.android.server.integrity.model.IntegrityCheckResult;
+import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.testutils.TestUtils;
import org.junit.After;
@@ -88,6 +91,7 @@ import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
/** Unit test for {@link com.android.server.integrity.AppIntegrityManagerServiceImpl} */
@RunWith(JUnit4.class)
@@ -131,12 +135,15 @@ public class AppIntegrityManagerServiceImplTest {
@org.junit.Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock PackageManagerInternal mPackageManagerInternal;
+ @Mock PlatformCompat mPlatformCompat;
@Mock Context mMockContext;
@Mock Resources mMockResources;
@Mock RuleEvaluationEngine mRuleEvaluationEngine;
@Mock IntegrityFileManager mIntegrityFileManager;
@Mock Handler mHandler;
+ private Supplier<PackageParser2> mParserSupplier = TestPackageParser2::new;
+
private final Context mRealContext = InstrumentationRegistry.getTargetContext();
private PackageManager mSpyPackageManager;
@@ -168,6 +175,7 @@ public class AppIntegrityManagerServiceImplTest {
new AppIntegrityManagerServiceImpl(
mMockContext,
mPackageManagerInternal,
+ mParserSupplier,
mRuleEvaluationEngine,
mIntegrityFileManager,
mHandler);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 73e5b6ddfb1d..3718c5c5a5e1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -46,6 +46,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.TestPackageParser2;
import org.junit.Before;
import org.junit.Test;
@@ -77,7 +78,7 @@ public class ApexManagerTest {
ApexManager.ApexManagerImpl managerImpl = spy(new ApexManager.ApexManagerImpl());
doReturn(mApexService).when(managerImpl).waitForApexService();
mApexManager = managerImpl;
- mPackageParser2 = new PackageParser2(null, false, null, null, null);
+ mPackageParser2 = new TestPackageParser2();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index d760629552b8..daaf870fa695 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -49,7 +49,6 @@ import android.os.Bundle;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
@@ -61,6 +60,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
@@ -108,7 +108,7 @@ public class PackageParserTest {
@Test
public void testParse_noCache() throws Exception {
- CachePackageNameParser pp = new CachePackageNameParser(null, false, null, null, null);
+ CachePackageNameParser pp = new CachePackageNameParser(null);
ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
false /* useCaches */);
assertNotNull(pkg);
@@ -125,7 +125,7 @@ public class PackageParserTest {
@Test
public void testParse_withCache() throws Exception {
- CachePackageNameParser pp = new CachePackageNameParser(null, false, null, null, null);
+ CachePackageNameParser pp = new CachePackageNameParser(null);
pp.setCacheDir(mTmpDir);
// The first parse will write this package to the cache.
@@ -144,7 +144,7 @@ public class PackageParserTest {
// We haven't set a cache directory here : the parse should still succeed,
// just not using the cached results.
- pp = new CachePackageNameParser(null, false, null, null, null);
+ pp = new CachePackageNameParser(null);
pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, true /* useCaches */);
assertEquals("android", pkg.getPackageName());
@@ -154,17 +154,29 @@ public class PackageParserTest {
@Test
public void test_serializePackage() throws Exception {
- PackageParser2 pp = new PackageParser2(null, false, null, mTmpDir, null);
- ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
- true /* useCaches */);
-
- Parcel p = Parcel.obtain();
- pkg.writeToParcel(p, 0 /* flags */);
-
- p.setDataPosition(0);
- ParsedPackage deserialized = new PackageImpl(p);
-
- assertPackagesEqual(pkg, deserialized);
+ try (PackageParser2 pp = new PackageParser2(null, false, null, mTmpDir,
+ new PackageParser2.Callback() {
+ @Override
+ public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+ return true;
+ }
+
+ @Override
+ public boolean hasFeature(String feature) {
+ return false;
+ }
+ })) {
+ ParsedPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
+ true /* useCaches */);
+
+ Parcel p = Parcel.obtain();
+ pkg.writeToParcel(p, 0 /* flags */);
+
+ p.setDataPosition(0);
+ ParsedPackage deserialized = new PackageImpl(p);
+
+ assertPackagesEqual(pkg, deserialized);
+ }
}
@Test
@@ -218,10 +230,6 @@ public class PackageParserTest {
assertSame(deserialized.getSharedUserId(), deserialized2.getSharedUserId());
}
- private static PackageParser2 makeParser() {
- return new PackageParser2(null, false, null, null, null);
- }
-
private File extractFile(String filename) throws Exception {
final Context context = InstrumentationRegistry.getTargetContext();
final File tmpFile = File.createTempFile(filename, ".apk");
@@ -238,7 +246,7 @@ public class PackageParserTest {
public void testParseIsolatedSplitsDefault() throws Exception {
final File testFile = extractFile(TEST_APP1_APK);
try {
- final ParsedPackage pkg = makeParser().parsePackage(testFile, 0, false);
+ final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
assertFalse("isolatedSplits", pkg.isIsolatedSplitLoading());
} finally {
testFile.delete();
@@ -252,7 +260,7 @@ public class PackageParserTest {
public void testParseIsolatedSplitsConstant() throws Exception {
final File testFile = extractFile(TEST_APP2_APK);
try {
- final ParsedPackage pkg = makeParser().parsePackage(testFile, 0, false);
+ final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
assertTrue("isolatedSplits", pkg.isIsolatedSplitLoading());
} finally {
testFile.delete();
@@ -266,7 +274,7 @@ public class PackageParserTest {
public void testParseIsolatedSplitsResource() throws Exception {
final File testFile = extractFile(TEST_APP3_APK);
try {
- final ParsedPackage pkg = makeParser().parsePackage(testFile, 0, false);
+ final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
assertTrue("isolatedSplits", pkg.isIsolatedSplitLoading());
} finally {
testFile.delete();
@@ -279,10 +287,18 @@ public class PackageParserTest {
*/
public static class CachePackageNameParser extends PackageParser2 {
- CachePackageNameParser(String[] separateProcesses, boolean onlyCoreApps,
- DisplayMetrics displayMetrics, @Nullable File cacheDir,
- PackageParser2.Callback callback) {
- super(separateProcesses, onlyCoreApps, displayMetrics, cacheDir, callback);
+ CachePackageNameParser(@Nullable File cacheDir) {
+ super(null, false, null, null, new Callback() {
+ @Override
+ public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+ return true;
+ }
+
+ @Override
+ public boolean hasFeature(String feature) {
+ return false;
+ }
+ });
if (cacheDir != null) {
setCacheDir(cacheDir);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
index 9d3ac1720930..38d01d0c4c18 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
@@ -16,13 +16,13 @@
package com.android.server.pm;
-import android.content.pm.PackageParser;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import junit.framework.Assert;
@@ -48,7 +48,7 @@ public class ParallelPackageParserTest {
@Before
public void setUp() {
- mParser = new TestParallelPackageParser(new PackageParser2(null, false, null, null, null),
+ mParser = new TestParallelPackageParser(new TestPackageParser2(),
ParallelPackageParser.makeExecutorService());
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index f87f68d4942f..3888ff3e278a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -35,7 +35,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
@@ -93,15 +93,11 @@ public class DexMetadataHelperTest {
return outFile;
}
- private PackageParser2 makeParser() {
- return new PackageParser2(null, false, null, null, null);
- }
-
@Test
public void testParsePackageWithDmFileValid() throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk");
- ParsedPackage pkg = makeParser().parsePackage(mTmpDir, 0 /* flags */, false);
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
assertEquals(1, packageDexMetadata.size());
@@ -117,7 +113,7 @@ public class DexMetadataHelperTest {
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
createDexMetadataFile("install_split_feature_a.apk");
- ParsedPackage pkg = makeParser().parsePackage(mTmpDir, 0 /* flags */, false);
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
assertEquals(2, packageDexMetadata.size());
@@ -136,7 +132,7 @@ public class DexMetadataHelperTest {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_feature_a.apk");
- ParsedPackage pkg = makeParser().parsePackage(mTmpDir, 0 /* flags */, false);
+ ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
assertEquals(1, packageDexMetadata.size());
@@ -152,7 +148,8 @@ public class DexMetadataHelperTest {
File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
Files.createFile(invalidDmFile.toPath());
try {
- ParsedPackage pkg = makeParser().parsePackage(mTmpDir, 0 /* flags */, false);
+ ParsedPackage pkg = new TestPackageParser2()
+ .parsePackage(mTmpDir, 0 /* flags */, false);
AndroidPackageUtils.validatePackageDexMetadata(pkg);
} catch (PackageParserException e) {
assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
@@ -169,7 +166,8 @@ public class DexMetadataHelperTest {
Files.createFile(invalidDmFile.toPath());
try {
- ParsedPackage pkg = makeParser().parsePackage(mTmpDir, 0 /* flags */, false);
+ ParsedPackage pkg = new TestPackageParser2()
+ .parsePackage(mTmpDir, 0 /* flags */, false);
AndroidPackageUtils.validatePackageDexMetadata(pkg);
} catch (PackageParserException e) {
assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index ca6b296f155c..f532dd87909e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -47,8 +47,7 @@ open class AndroidPackageParsingTestBase {
companion object {
- // TODO(chiuwinson): Enable in separate change to fail all presubmit builds and fix errors
- private const val VERIFY_ALL_APKS = false
+ private const val VERIFY_ALL_APKS = true
/** For auditing memory usage differences */
private const val DUMP_HPROF_TO_EXTERNAL = false
@@ -57,13 +56,10 @@ open class AndroidPackageParsingTestBase {
protected val packageParser = PackageParser().apply {
setOnlyCoreApps(false)
setDisplayMetrics(context.resources.displayMetrics)
- setCallback { true }
+ setCallback { false /* hasFeature */ }
}
- protected val packageParser2 = PackageParser2(null, false, context.resources.displayMetrics,
- null, object : PackageParser2.Callback() {
- override fun hasFeature(feature: String?) = true
- })
+ protected val packageParser2 = TestPackageParser2()
/**
* It would be difficult to mock all possibilities, so just use the APKs on device.
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 66cd46699334..af89761acf9d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -102,10 +102,6 @@ public class PackageParserLegacyCoreTest {
}
}
- private PackageParser2 makeParser() {
- return new PackageParser2(null, false, null, null, null);
- }
-
@Test
public void testComputeMinSdkVersion_preReleasePlatform() {
// Do allow older release minSdkVersion on pre-release platform.
@@ -357,8 +353,8 @@ public class PackageParserLegacyCoreTest {
File outFile = null;
try {
outFile = copyRawResourceToFile(apkFileName, apkResourceId);
- return converter.apply(
- makeParser().parsePackage(outFile, 0 /* flags */, false));
+ return converter.apply(new TestPackageParser2()
+ .parsePackage(outFile, 0 /* flags */, false));
} finally {
if (outFile != null) {
outFile.delete();
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
new file mode 100644
index 000000000000..22487071cd71
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.parsing
+
+import android.annotation.RawRes
+import android.content.Context
+import android.content.pm.parsing.ParsingPackage
+import android.content.pm.parsing.ParsingPackageImpl
+import android.content.pm.parsing.ParsingPackageUtils
+import android.content.pm.parsing.result.ParseInput
+import android.content.pm.parsing.result.ParseInput.DeferredError
+import android.content.pm.parsing.result.ParseResult
+import android.content.res.TypedArray
+import android.os.Build
+import androidx.test.InstrumentationRegistry
+import com.android.frameworks.servicestests.R
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+/**
+ * There are 2 known errors when parsing a manifest that were promoted to true failures in R:
+ * 1. Missing an <application> or <instrumentation> tag
+ * 2. An empty string action/category in an intent-filter
+ *
+ * This verifies these failures when the APK targets R.
+ */
+class PackageParsingDeferErrorTest {
+
+ companion object {
+ private const val TEST_ACTIVITY =
+ "com.android.servicestests.pm.parsing.test.TestActivity"
+ private const val TEST_ACTION =
+ "com.android.servicestests.pm.parsing.test.TEST_ACTION"
+ private const val TEST_CATEGORY =
+ "com.android.servicestests.pm.parsing.test.TEST_CATEGORY"
+ private const val TEST_PERMISSION =
+ "com.android.servicestests.pm.parsing.missingapp.TEST_PERMISSION"
+ }
+
+ private val context: Context = InstrumentationRegistry.getContext()
+
+ private val inputCallback = ParseInput.Callback { changeId, _, targetSdk ->
+ when (changeId) {
+ DeferredError.MISSING_APP_TAG -> targetSdk > Build.VERSION_CODES.Q
+ DeferredError.EMPTY_INTENT_ACTION_CATEGORY -> targetSdk > Build.VERSION_CODES.Q
+ else -> throw IllegalStateException("changeId $changeId is not mocked for test")
+ }
+ }
+
+ private val parsingCallback = object : ParsingPackageUtils.Callback {
+ override fun hasFeature(feature: String?) = true
+
+ override fun startParsingPackage(
+ packageName: String,
+ baseCodePath: String,
+ codePath: String,
+ manifestArray: TypedArray,
+ isCoreApp: Boolean
+ ): ParsingPackage {
+ return ParsingPackageImpl(packageName, baseCodePath, codePath, manifestArray)
+ }
+ }
+
+ @get:Rule
+ val tempFolder = TemporaryFolder(context.filesDir)
+
+ @Test
+ fun emptyIntentFilterActionSdkQ() {
+ val result = parseFile(R.raw.PackageParsingTestAppEmptyActionSdkQ)
+ assertWithMessage(result.errorMessage).that(result.isError).isFalse()
+ val activities = result.result.activities
+ // 2 because of AppDetailsActivity
+ assertThat(activities).hasSize(2)
+ val first = activities.first()
+ assertThat(first.name).isEqualTo(TEST_ACTIVITY)
+ val intents = first.intents
+ assertThat(intents).hasSize(1)
+ assertThat(intents.first().hasCategory(TEST_CATEGORY)).isTrue()
+ assertThat(intents.first().hasAction(TEST_ACTION)).isTrue()
+ }
+
+ @Test
+ fun emptyIntentFilterActionSdkR() {
+ val result = parseFile(R.raw.PackageParsingTestAppEmptyActionSdkR)
+ assertThat(result.isError).isTrue()
+ }
+
+ @Test
+ fun emptyIntentFilterCategorySdkQ() {
+ val result = parseFile(R.raw.PackageParsingTestAppEmptyCategorySdkQ)
+ assertWithMessage(result.errorMessage).that(result.isError).isFalse()
+ val activities = result.result.activities
+ // 2 because of AppDetailsActivity
+ assertThat(activities).hasSize(2)
+ val first = activities.first()
+ assertThat(first.name).isEqualTo(TEST_ACTIVITY)
+ val intents = first.intents
+ assertThat(intents).hasSize(1)
+ assertThat(intents.first().hasAction(TEST_ACTION)).isTrue()
+ }
+
+ @Test
+ fun emptyIntentFilterCategorySdkR() {
+ val result = parseFile(R.raw.PackageParsingTestAppEmptyCategorySdkR)
+ assertThat(result.isError).isTrue()
+ }
+
+ @Test
+ fun missingAppTagSdkQ() {
+ val result = parseFile(R.raw.PackageParsingTestAppMissingAppSdkQ)
+ assertWithMessage(result.errorMessage).that(result.isError).isFalse()
+ val permissions = result.result.permissions
+ assertThat(permissions).hasSize(1)
+ assertThat(permissions.first().name).isEqualTo(TEST_PERMISSION)
+ }
+
+ @Test
+ fun missingAppTagSdkR() {
+ val result = parseFile(R.raw.PackageParsingTestAppMissingAppSdkR)
+ assertThat(result.isError).isTrue()
+ }
+
+ private fun parseFile(@RawRes id: Int): ParseResult<ParsingPackage> {
+ val file = tempFolder.newFile()
+ context.resources.openRawResource(id).use { input ->
+ file.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ return ParsingPackageUtils.parseDefaultOneTime(file, 0, inputCallback, parsingCallback)
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
new file mode 100644
index 000000000000..7ca7682f1c82
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.parsing
+
+import android.content.pm.ApplicationInfo
+
+class TestPackageParser2 : PackageParser2(null /* separateProcesses */, false /* onlyCoreApps */,
+ null /* displayMetrics */, null /* cacheDir */, object : PackageParser2.Callback() {
+ override fun isChangeEnabled(changeId: Long, appInfo: ApplicationInfo): Boolean {
+ return true
+ }
+
+ override fun hasFeature(feature: String): Boolean {
+ // Assume the device doesn't support anything. This will affect permission parsing
+ // and will force <uses-permission/> declarations to include all requiredNotFeature
+ // permissions and exclude all requiredFeature permissions. This mirrors the old
+ // behavior.
+ return false
+ }
+})
diff --git a/services/tests/servicestests/test-apps/PackageParsingTestManifests/Android.bp b/services/tests/servicestests/test-apps/PackageParsingTestManifests/Android.bp
new file mode 100644
index 000000000000..8eb56f676f91
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParsingTestManifests/Android.bp
@@ -0,0 +1,70 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// NOTE: NONE_OF_THESE_TARGETS_ACTUALLY_WORK. AAPT2 doesn't seem to be able to skip
+// errors within tag attributes. This is here as an reference of how to build the test apps, but
+// they will have to built manually and checked into the tree as prebuilts. A modified version of
+// AAPT2 is necessary to build the broken APKs.
+
+// android_test_helper_app {
+// name: "PackageParsingTestAppEmptyActionSdkQ",
+// manifest: "AndroidManifestEmptyAction.xml",
+// srcs: ["**/*.kt"],
+// min_sdk_version: "28",
+// target_sdk_version: "29",
+// aaptflags: ["--warn-manifest-validation"],
+// }
+
+// android_test_helper_app {
+// name: "PackageParsingTestAppEmptyActionSdkR",
+// manifest: "AndroidManifestEmptyAction.xml",
+// srcs: ["**/*.kt"],
+// min_sdk_version: "28",
+// target_sdk_version: "30",
+// aaptflags: ["--warn-manifest-validation"],
+// }
+
+// android_test_helper_app {
+// name: "PackageParsingTestAppEmptyCategorySdkQ",
+// manifest: "AndroidManifestEmptyCategory.xml",
+// srcs: ["**/*.kt"],
+// min_sdk_version: "28",
+// target_sdk_version: "29",
+// aaptflags: ["--warn-manifest-validation"],
+// }
+
+// android_test_helper_app {
+// name: "PackageParsingTestAppEmptyCategorySdkR",
+// manifest: "AndroidManifestEmptyCategory.xml",
+// srcs: ["**/*.kt"],
+// min_sdk_version: "28",
+// target_sdk_version: "30",
+// aaptflags: ["--warn-manifest-validation"],
+// }
+
+// android_test_helper_app {
+// name: "PackageParsingTestAppMissingAppSdkQ",
+// manifest: "AndroidManifestMissingApp.xml",
+// min_sdk_version: "28",
+// target_sdk_version: "29",
+// aaptflags: ["--warn-manifest-validation"],
+// }
+
+// android_test_helper_app {
+// name: "PackageParsingTestAppMissingAppSdkR",
+// manifest: "AndroidManifestMissingApp.xml",
+// min_sdk_version: "28",
+// target_sdk_version: "30",
+// aaptflags: ["--warn-manifest-validation"],
+// }
diff --git a/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestEmptyAction.xml b/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestEmptyAction.xml
new file mode 100644
index 000000000000..cb294f6e87e1
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestEmptyAction.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.pm.parsing.emptyaction">
+
+ <application>
+ <activity android:name="com.android.servicestests.pm.parsing.test.TestActivity">
+ <intent-filter>
+ <action android:name="" />
+ <!-- Non-empty action use to verify filter, since 0 action filters are stripped -->
+ <action android:name="com.android.servicestests.pm.parsing.test.TEST_ACTION" />
+ <category android:name="com.android.servicestests.pm.parsing.test.TEST_CATEGORY"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestEmptyCategory.xml b/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestEmptyCategory.xml
new file mode 100644
index 000000000000..5b0f80a1754c
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestEmptyCategory.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.pm.parsing.emptycategory">
+
+ <application>
+ <activity android:name="com.android.servicestests.pm.parsing.test.TestActivity">
+ <intent-filter>
+ <action android:name="com.android.servicestests.pm.parsing.test.TEST_ACTION"/>
+ <category android:name="" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestMissingApp.xml b/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestMissingApp.xml
new file mode 100644
index 000000000000..38249478551b
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParsingTestManifests/AndroidManifestMissingApp.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.pm.parsing.missingapp">
+
+ <!-- Only to assert that the manifest parsed correctly -->
+ <permission android:name="com.android.servicestests.pm.parsing.missingapp.TEST_PERMISSION"
+ android:protectionLevel="normal" />
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/PackageParsingTestManifests/src/com/android/servicestests/pm/parsing/test/TestActivity.kt b/services/tests/servicestests/test-apps/PackageParsingTestManifests/src/com/android/servicestests/pm/parsing/test/TestActivity.kt
new file mode 100644
index 000000000000..3b8b29d19a41
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParsingTestManifests/src/com/android/servicestests/pm/parsing/test/TestActivity.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.servicestests.pm.parsing.test
+
+class TestActivity