1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.content.pm.parsing.result; 18 19 import android.annotation.IntRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.compat.annotation.ChangeId; 23 import android.compat.annotation.EnabledAfter; 24 import android.content.pm.PackageManager; 25 import android.os.Build; 26 27 /** 28 * Used as a method parameter which is then transformed into a {@link ParseResult}. This is 29 * generalized as it doesn't matter what type this input is for. It's simply to hide the 30 * methods of {@link ParseResult}. 31 * 32 * @hide 33 */ 34 public interface ParseInput { 35 36 /** 37 * Errors encountered during parsing may rely on the targetSDK version of the application to 38 * determine whether or not to fail. These are passed into {@link #deferError(String, long)} 39 * when encountered, and the implementation will handle how to defer the errors until the 40 * targetSdkVersion is known and sent to {@link #enableDeferredError(String, int)}. 41 * 42 * All of these must be marked {@link ChangeId}, as that is the mechanism used to check if the 43 * error must be propagated. This framework also allows developers to pre-disable specific 44 * checks if they wish to target a newer SDK version in a development environment without 45 * having to migrate their entire app to validate on a newer platform. 46 */ 47 final class DeferredError { 48 /** 49 * Missing an "application" or "instrumentation" tag. 50 */ 51 @ChangeId 52 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) 53 public static final long MISSING_APP_TAG = 150776642; 54 55 /** 56 * An intent filter's actor or category is an empty string. A bug in the platform before R 57 * allowed this to pass through without an error. This does not include cases when the 58 * attribute is null/missing, as that has always been a failure. 59 */ 60 @ChangeId 61 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) 62 public static final long EMPTY_INTENT_ACTION_CATEGORY = 151163173; 63 64 /** 65 * The {@code resources.arsc} of one of the APKs being installed is compressed or not 66 * aligned on a 4-byte boundary. Resource tables that cannot be memory mapped exert excess 67 * memory pressure on the system and drastically slow down construction of 68 * {@link android.content.res.Resources} objects. 69 */ 70 @ChangeId 71 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) 72 public static final long RESOURCES_ARSC_COMPRESSED = 132742131; 73 74 /** 75 * Missing `android:exported` flag. When an intent filter is defined, an explicit value 76 * for the android:exported flag is required. 77 */ 78 @ChangeId 79 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) 80 public static final long MISSING_EXPORTED_FLAG = 150232615; 81 82 /** 83 * TODO(chiuwinson): This is required because PackageManager#getPackageArchiveInfo 84 * cannot read the targetSdk info from the changeId because it requires the 85 * READ_COMPAT_CHANGE_CONFIG which cannot be obtained automatically without entering the 86 * server process. This should be removed once an alternative is found, or if the API 87 * is removed. 88 * @return the targetSdk that this change is gated on (> check), or -1 if disabled 89 */ 90 @IntRange(from = -1, to = Integer.MAX_VALUE) getTargetSdkForChange(long changeId)91 public static int getTargetSdkForChange(long changeId) { 92 if (changeId == MISSING_APP_TAG 93 || changeId == EMPTY_INTENT_ACTION_CATEGORY 94 || changeId == RESOURCES_ARSC_COMPRESSED) { 95 return Build.VERSION_CODES.Q; 96 } 97 98 if (changeId == MISSING_EXPORTED_FLAG) { 99 return Build.VERSION_CODES.R; 100 } 101 102 return -1; 103 } 104 } 105 success(ResultType result)106 <ResultType> ParseResult<ResultType> success(ResultType result); 107 108 /** 109 * Used for errors gated by {@link DeferredError}. Will return an error result if the 110 * targetSdkVersion is already known and this must be returned as a real error. The result 111 * contains null and should not be unwrapped. 112 * 113 * @see #error(String) 114 */ deferError(@onNull String parseError, long deferredError)115 ParseResult<?> deferError(@NonNull String parseError, long deferredError); 116 117 /** 118 * Called after targetSdkVersion is known. Returns an error result if a previously deferred 119 * error was registered. The result contains null and should not be unwrapped. 120 */ enableDeferredError(String packageName, int targetSdkVersion)121 ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion); 122 123 /** 124 * This will assign errorCode to {@link PackageManager#INSTALL_PARSE_FAILED_SKIPPED, used for 125 * packages which should be ignored by the caller. 126 * 127 * @see #error(int, String, Exception) 128 */ skip(@onNull String parseError)129 <ResultType> ParseResult<ResultType> skip(@NonNull String parseError); 130 131 /** @see #error(int, String, Exception) */ error(int parseError)132 <ResultType> ParseResult<ResultType> error(int parseError); 133 134 /** 135 * This will assign errorCode to {@link PackageManager#INSTALL_PARSE_FAILED_MANIFEST_MALFORMED}. 136 * @see #error(int, String, Exception) 137 */ error(@onNull String parseError)138 <ResultType> ParseResult<ResultType> error(@NonNull String parseError); 139 140 /** @see #error(int, String, Exception) */ error(int parseError, @Nullable String errorMessage)141 <ResultType> ParseResult<ResultType> error(int parseError, @Nullable String errorMessage); 142 143 /** 144 * Marks this as an error result. When this method is called, the return value <b>must</b> 145 * be returned to the exit of the parent method that took in this {@link ParseInput} as a 146 * parameter. 147 * 148 * The calling site of that method is then expected to check the result for error, and 149 * continue to bubble up if it is an error. 150 * 151 * If the result {@link ParseResult#isSuccess()}, then it can be used as-is, as 152 * overlapping/consecutive successes are allowed. 153 */ error(int parseError, @Nullable String errorMessage, @Nullable Exception exception)154 <ResultType> ParseResult<ResultType> error(int parseError, @Nullable String errorMessage, 155 @Nullable Exception exception); 156 157 /** 158 * Moves the error in {@param result} to this input's type. In practice this does nothing 159 * but cast the type of the {@link ParseResult} for type safety, since the parameter 160 * and the receiver should be the same object. 161 */ error(ParseResult<?> result)162 <ResultType> ParseResult<ResultType> error(ParseResult<?> result); 163 164 /** 165 * Implemented instead of a direct reference to 166 * {@link com.android.internal.compat.IPlatformCompat}, allowing caching and testing logic to 167 * be separated out. 168 */ 169 interface Callback { 170 /** 171 * @return true if the changeId should be enabled 172 */ isChangeEnabled(long changeId, @NonNull String packageName, int targetSdkVersion)173 boolean isChangeEnabled(long changeId, @NonNull String packageName, int targetSdkVersion); 174 } 175 } 176