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          * TODO(chiuwinson): This is required because PackageManager#getPackageArchiveInfo
76          *   cannot read the targetSdk info from the changeId because it requires the
77          *   READ_COMPAT_CHANGE_CONFIG which cannot be obtained automatically without entering the
78          *   server process. This should be removed once an alternative is found, or if the API
79          *   is removed.
80          * @return the targetSdk that this change is gated on (> check), or -1 if disabled
81          */
82         @IntRange(from = -1, to = Integer.MAX_VALUE)
getTargetSdkForChange(long changeId)83         public static int getTargetSdkForChange(long changeId) {
84             if (changeId == MISSING_APP_TAG
85                     || changeId == EMPTY_INTENT_ACTION_CATEGORY
86                     || changeId == RESOURCES_ARSC_COMPRESSED) {
87                 return Build.VERSION_CODES.Q;
88             }
89 
90             return -1;
91         }
92     }
93 
success(ResultType result)94     <ResultType> ParseResult<ResultType> success(ResultType result);
95 
96     /**
97      * Used for errors gated by {@link DeferredError}. Will return an error result if the
98      * targetSdkVersion is already known and this must be returned as a real error. The result
99      * contains null and should not be unwrapped.
100      *
101      * @see #error(String)
102      */
deferError(@onNull String parseError, long deferredError)103     ParseResult<?> deferError(@NonNull String parseError, long deferredError);
104 
105     /**
106      * Called after targetSdkVersion is known. Returns an error result if a previously deferred
107      * error was registered. The result contains null and should not be unwrapped.
108      */
enableDeferredError(String packageName, int targetSdkVersion)109     ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion);
110 
111     /**
112      * This will assign errorCode to {@link PackageManager#INSTALL_PARSE_FAILED_SKIPPED, used for
113      * packages which should be ignored by the caller.
114      *
115      * @see #error(int, String, Exception)
116      */
skip(@onNull String parseError)117     <ResultType> ParseResult<ResultType> skip(@NonNull String parseError);
118 
119     /** @see #error(int, String, Exception) */
error(int parseError)120     <ResultType> ParseResult<ResultType> error(int parseError);
121 
122     /**
123      * This will assign errorCode to {@link PackageManager#INSTALL_PARSE_FAILED_MANIFEST_MALFORMED}.
124      * @see #error(int, String, Exception)
125      */
error(@onNull String parseError)126     <ResultType> ParseResult<ResultType> error(@NonNull String parseError);
127 
128     /** @see #error(int, String, Exception) */
error(int parseError, @Nullable String errorMessage)129     <ResultType> ParseResult<ResultType> error(int parseError, @Nullable String errorMessage);
130 
131     /**
132      * Marks this as an error result. When this method is called, the return value <b>must</b>
133      * be returned to the exit of the parent method that took in this {@link ParseInput} as a
134      * parameter.
135      *
136      * The calling site of that method is then expected to check the result for error, and
137      * continue to bubble up if it is an error.
138      *
139      * If the result {@link ParseResult#isSuccess()}, then it can be used as-is, as
140      * overlapping/consecutive successes are allowed.
141      */
error(int parseError, @Nullable String errorMessage, @Nullable Exception exception)142     <ResultType> ParseResult<ResultType> error(int parseError, @Nullable String errorMessage,
143             @Nullable Exception exception);
144 
145     /**
146      * Moves the error in {@param result} to this input's type. In practice this does nothing
147      * but cast the type of the {@link ParseResult} for type safety, since the parameter
148      * and the receiver should be the same object.
149      */
error(ParseResult<?> result)150     <ResultType> ParseResult<ResultType> error(ParseResult<?> result);
151 
152     /**
153      * Implemented instead of a direct reference to
154      * {@link com.android.internal.compat.IPlatformCompat}, allowing caching and testing logic to
155      * be separated out.
156      */
157     interface Callback {
158         /**
159          * @return true if the changeId should be enabled
160          */
isChangeEnabled(long changeId, @NonNull String packageName, int targetSdkVersion)161         boolean isChangeEnabled(long changeId, @NonNull String packageName, int targetSdkVersion);
162     }
163 }
164