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