summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorWinson Chiu <chiuwinson@google.com>2020-04-01 05:44:35 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-04-01 05:44:35 +0000
commitbde104f8be331b97ba8bdb9f0a809245e785ef3f (patch)
treed2d26fa256b278c9875888882babd6bae12ebd4a /core/java/android
parent456365644b157791e41aaa680ffa31634c8c03e1 (diff)
parentdb4355fed9a80951e65a80a3682e206247759b1a (diff)
Merge changes Id7d1ff26,Id53a2e65 into rvc-dev
* changes: Add am compat enable/disable/reset test for not installed app Gate stricter manifest enforcement on targetSdk R
Diffstat (limited to 'core/java/android')
-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
7 files changed, 286 insertions, 100 deletions
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;
}
}