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
This commit is contained in:
Winson Chiu
2020-04-01 05:44:35 +00:00
committed by Android (Google) Code Review
35 changed files with 1247 additions and 192 deletions

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -17,31 +17,18 @@
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.
*

View File

@@ -20,51 +20,130 @@ 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;
/**
* 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;
}
public ParseInput reset() {
this.result = null;
this.errorCode = PackageManager.INSTALL_SUCCEEDED;
this.errorMessage = null;
this.exception = null;
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 void ignoreError() {
reset();
public <ResultType> ParseResult<ResultType> success(ResultType result) {
if (mErrorCode != PackageManager.INSTALL_SUCCEEDED) {
Slog.wtf(ParsingUtils.TAG, "Cannot set to success after set to error, was "
+ mErrorMessage, mException);
}
mResult = result;
//noinspection unchecked
return (ParseResult<ResultType>) 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);
public ParseResult<?> deferError(@NonNull String parseError, long deferredError) {
if (DEBUG_THROW_ALL_ERRORS) {
return error(parseError);
}
this.result = result;
//noinspection unchecked
return (ParseResult<ResultType>) this;
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
@@ -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;
}
}