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.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.parsing.ParsingUtils;
25 import android.os.ServiceManager;
26 import android.util.ArrayMap;
27 import android.util.Log;
28 import android.util.Slog;
29 
30 import com.android.internal.compat.IPlatformCompat;
31 import com.android.internal.util.CollectionUtils;
32 
33 /** @hide */
34 public class ParseTypeImpl implements ParseInput, ParseResult<Object> {
35 
36     private static final String TAG = ParsingUtils.TAG;
37 
38     public static final boolean DEBUG_FILL_STACK_TRACE = false;
39 
40     public static final boolean DEBUG_LOG_ON_ERROR = false;
41 
42     public static final boolean DEBUG_THROW_ALL_ERRORS = false;
43 
44     @NonNull
45     private Callback mCallback;
46 
47     private Object mResult;
48 
49     private int mErrorCode = PackageManager.INSTALL_SUCCEEDED;
50 
51     @Nullable
52     private String mErrorMessage;
53 
54     @Nullable
55     private Exception mException;
56 
57     /**
58      * Errors encountered before targetSdkVersion is known.
59      * The size upper bound is the number of longs in {@link DeferredError}
60      */
61     @Nullable
62     private ArrayMap<Long, String> mDeferredErrors = null;
63 
64     private String mPackageName;
65     private Integer mTargetSdkVersion;
66 
67     /**
68      * Specifically for {@link PackageManager#getPackageArchiveInfo(String, int)} where
69      * {@link IPlatformCompat} cannot be used because the cross-package READ_COMPAT_CHANGE_CONFIG
70      * permission cannot be obtained.
71      */
forParsingWithoutPlatformCompat()72     public static ParseTypeImpl forParsingWithoutPlatformCompat() {
73         return new ParseTypeImpl((changeId, packageName, targetSdkVersion) -> {
74             int gateSdkVersion = DeferredError.getTargetSdkForChange(changeId);
75             if (gateSdkVersion == -1) {
76                 return false;
77             }
78             return targetSdkVersion > gateSdkVersion;
79         });
80     }
81 
82     /**
83      * Assumes {@link Context#PLATFORM_COMPAT_SERVICE} is available to the caller. For use
84      * with {@link android.content.pm.parsing.ApkLiteParseUtils} or similar where parsing is
85      * done outside of {@link com.android.server.pm.PackageManagerService}.
86      */
forDefaultParsing()87     public static ParseTypeImpl forDefaultParsing() {
88         IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
89                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
90         return new ParseTypeImpl((changeId, packageName, targetSdkVersion) -> {
91             ApplicationInfo appInfo = new ApplicationInfo();
92             appInfo.packageName = packageName;
93             appInfo.targetSdkVersion = targetSdkVersion;
94             try {
95                 return platformCompat.isChangeEnabled(changeId, appInfo);
96             } catch (Exception e) {
97                 // This shouldn't happen, but assume enforcement if it does
98                 Slog.wtf(ParsingUtils.TAG, "IPlatformCompat query failed", e);
99                 return true;
100             }
101         });
102     }
103 
104     /**
105      * @param callback if nullable, fallback to manual targetSdk > Q check
106      */
107     public ParseTypeImpl(@NonNull Callback callback) {
108         mCallback = callback;
109     }
110 
111     public ParseInput reset() {
112         mResult = null;
113         mErrorCode = PackageManager.INSTALL_SUCCEEDED;
114         mErrorMessage = null;
115         mException = null;
116         if (mDeferredErrors != null) {
117             // If the memory was already allocated, don't bother freeing and re-allocating,
118             // as this could occur hundreds of times depending on what the caller is doing and
119             // how many APKs they're going through.
120             mDeferredErrors.erase();
121         }
122         return this;
123     }
124 
125     @Override
126     public <ResultType> ParseResult<ResultType> success(ResultType result) {
127         if (mErrorCode != PackageManager.INSTALL_SUCCEEDED) {
128             Slog.wtf(ParsingUtils.TAG, "Cannot set to success after set to error, was "
129                     + mErrorMessage, mException);
130         }
131         mResult = result;
132         //noinspection unchecked
133         return (ParseResult<ResultType>) this;
134     }
135 
136     @Override
137     public ParseResult<?> deferError(@NonNull String parseError, long deferredError) {
138         if (DEBUG_THROW_ALL_ERRORS) {
139             return error(parseError);
140         }
141         if (mTargetSdkVersion != null) {
142             if (mDeferredErrors != null && mDeferredErrors.containsKey(deferredError)) {
143                 // If the map already contains the key, that means it's already been checked and
144                 // found to be disabled. Otherwise it would've failed when mTargetSdkVersion was
145                 // set to non-null.
146                 return success(null);
147             }
148 
149             if (mCallback.isChangeEnabled(deferredError, mPackageName, mTargetSdkVersion)) {
150                 return error(parseError);
151             } else {
152                 if (mDeferredErrors == null) {
153                     mDeferredErrors = new ArrayMap<>();
154                 }
155                 mDeferredErrors.put(deferredError, null);
156                 return success(null);
157             }
158         }
159 
160         if (mDeferredErrors == null) {
161             mDeferredErrors = new ArrayMap<>();
162         }
163 
164         // Only save the first occurrence of any particular error
165         mDeferredErrors.putIfAbsent(deferredError, parseError);
166         return success(null);
167     }
168 
169     @Override
170     public ParseResult<?> enableDeferredError(String packageName, int targetSdkVersion) {
171         mPackageName = packageName;
172         mTargetSdkVersion = targetSdkVersion;
173 
174         int size = CollectionUtils.size(mDeferredErrors);
175         for (int index = size - 1; index >= 0; index--) {
176             long changeId = mDeferredErrors.keyAt(index);
177             String errorMessage = mDeferredErrors.valueAt(index);
178             if (mCallback.isChangeEnabled(changeId, mPackageName, mTargetSdkVersion)) {
179                 return error(errorMessage);
180             } else {
181                 // No point holding onto the string, but need to maintain the key to signal
182                 // that the error was checked with isChangeEnabled and found to be disabled.
183                 mDeferredErrors.setValueAt(index, null);
184             }
185         }
186 
187         return success(null);
188     }
189 
190     @Override
191     public <ResultType> ParseResult<ResultType> skip(@NonNull String parseError) {
192         return error(PackageManager.INSTALL_PARSE_FAILED_SKIPPED, parseError);
193     }
194 
195     @Override
196     public <ResultType> ParseResult<ResultType> error(int parseError) {
197         return error(parseError, null);
198     }
199 
200     @Override
201     public <ResultType> ParseResult<ResultType> error(@NonNull String parseError) {
202         return error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, parseError);
203     }
204 
205     @Override
206     public <ResultType> ParseResult<ResultType> error(int errorCode,
207             @Nullable String errorMessage) {
208         return error(errorCode, errorMessage, null);
209     }
210 
211     @Override
212     public <ResultType> ParseResult<ResultType> error(ParseResult<?> intentResult) {
213         return error(intentResult.getErrorCode(), intentResult.getErrorMessage(),
214                 intentResult.getException());
215     }
216 
217     @Override
218     public <ResultType> ParseResult<ResultType> error(int errorCode, @Nullable String errorMessage,
219             Exception exception) {
220         mErrorCode = errorCode;
221         mErrorMessage = errorMessage;
222         mException = exception;
223 
224         if (DEBUG_FILL_STACK_TRACE) {
225             if (exception == null) {
226                 mException = new Exception();
227             }
228         }
229 
230         if (DEBUG_LOG_ON_ERROR) {
231             Exception exceptionToLog = mException != null ? mException : new Exception();
232             Log.w(TAG, "ParseInput set to error " + errorCode + ", " + errorMessage,
233                     exceptionToLog);
234         }
235 
236         //noinspection unchecked
237         return (ParseResult<ResultType>) this;
238     }
239 
240     @Override
241     public Object getResult() {
242         return mResult;
243     }
244 
245     @Override
246     public boolean isSuccess() {
247         return mErrorCode == PackageManager.INSTALL_SUCCEEDED;
248     }
249 
250     @Override
251     public boolean isError() {
252         return !isSuccess();
253     }
254 
255     @Override
256     public int getErrorCode() {
257         return mErrorCode;
258     }
259 
260     @Nullable
261     @Override
262     public String getErrorMessage() {
263         return mErrorMessage;
264     }
265 
266     @Nullable
267     @Override
268     public Exception getException() {
269         return mException;
270     }
271 }
272