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 com.android.server.pm.parsing;
18 
19 import android.annotation.AnyThread;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.ActivityThread;
23 import android.content.Context;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.parsing.result.ParseInput;
26 import android.content.pm.parsing.result.ParseResult;
27 import android.content.pm.parsing.result.ParseTypeImpl;
28 import android.content.res.TypedArray;
29 import android.os.Build;
30 import android.os.ServiceManager;
31 import android.os.SystemClock;
32 import android.permission.PermissionManager;
33 import android.util.DisplayMetrics;
34 import android.util.Slog;
35 
36 import com.android.internal.compat.IPlatformCompat;
37 import com.android.internal.util.ArrayUtils;
38 import com.android.server.pm.PackageManagerException;
39 import com.android.server.pm.PackageManagerService;
40 import com.android.server.pm.parsing.pkg.PackageImpl;
41 import com.android.server.pm.parsing.pkg.ParsedPackage;
42 import com.android.server.pm.pkg.parsing.ParsingPackage;
43 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
44 import com.android.server.pm.pkg.parsing.ParsingUtils;
45 
46 import java.io.File;
47 import java.util.List;
48 
49 /**
50  * The v2 of package parsing for use when parsing is initiated in the server and must
51  * contain state contained by the server.
52  *
53  * The {@link AutoCloseable} helps signal that this class contains resources that must be freed.
54  * Although it is sufficient to release references to an instance of this class and let it get
55  * collected automatically.
56  */
57 public class PackageParser2 implements AutoCloseable {
58 
59     /**
60      * For parsing inside the system server but outside of {@link PackageManagerService}.
61      * Generally used for parsing information in an APK that hasn't been installed yet.
62      *
63      * This must be called inside the system process as it relies on {@link ServiceManager}.
64      */
65     @NonNull
forParsingFileWithDefaults()66     public static PackageParser2 forParsingFileWithDefaults() {
67         IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
68                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
69         return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
70                 null /* cacheDir */, new Callback() {
71             @Override
72             public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
73                 try {
74                     return platformCompat.isChangeEnabled(changeId, appInfo);
75                 } catch (Exception e) {
76                     // This shouldn't happen, but assume enforcement if it does
77                     Slog.wtf(TAG, "IPlatformCompat query failed", e);
78                     return true;
79                 }
80             }
81 
82             @Override
83             public boolean hasFeature(String feature) {
84                 // Assume the device doesn't support anything. This will affect permission parsing
85                 // and will force <uses-permission/> declarations to include all requiredNotFeature
86                 // permissions and exclude all requiredFeature permissions. This mirrors the old
87                 // behavior.
88                 return false;
89             }
90         });
91     }
92 
93     private static final String TAG = ParsingUtils.TAG;
94 
95     private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
96     private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100;
97 
98     private ThreadLocal<ApplicationInfo> mSharedAppInfo =
99             ThreadLocal.withInitial(() -> {
100                 ApplicationInfo appInfo = new ApplicationInfo();
101                 appInfo.uid = -1; // Not a valid UID since the app will not be installed yet
102                 return appInfo;
103             });
104 
105     private ThreadLocal<ParseTypeImpl> mSharedResult;
106 
107     @Nullable
108     protected PackageCacher mCacher;
109 
110     private ParsingPackageUtils parsingUtils;
111 
112     public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics,
113             @Nullable File cacheDir, @NonNull Callback callback) {
114         if (displayMetrics == null) {
115             displayMetrics = new DisplayMetrics();
116             displayMetrics.setToDefaults();
117         }
118 
119         PermissionManager permissionManager = ActivityThread.currentApplication()
120                 .getSystemService(PermissionManager.class);
121         List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
122                 .getSplitPermissions();
123 
124         mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
125 
126         parsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
127                 callback);
128 
129         ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
130             ApplicationInfo appInfo = mSharedAppInfo.get();
131             //noinspection ConstantConditions
132             appInfo.packageName = packageName;
133             appInfo.targetSdkVersion = targetSdkVersion;
134             return callback.isChangeEnabled(changeId, appInfo);
135         };
136 
137         mSharedResult = ThreadLocal.withInitial(() -> new ParseTypeImpl(enforcementCallback));
138     }
139 
140     /**
141      * TODO(b/135203078): Document new package parsing
142      */
143     @AnyThread
144     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
145             throws PackageManagerException {
146         var files = packageFile.listFiles();
147         // Apk directory is directly nested under the current directory
148         if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
149             packageFile = files[0];
150         }
151 
152         if (useCaches && mCacher != null) {
153             ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
154             if (parsed != null) {
155                 return parsed;
156             }
157         }
158 
159         long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
160         ParseInput input = mSharedResult.get().reset();
161         ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
162         if (result.isError()) {
163             throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
164                     result.getException());
165         }
166 
167         ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();
168 
169         long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
170         if (mCacher != null) {
171             mCacher.cacheResult(packageFile, flags, parsed);
172         }
173         if (LOG_PARSE_TIMINGS) {
174             parseTime = cacheTime - parseTime;
175             cacheTime = SystemClock.uptimeMillis() - cacheTime;
176             if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
177                 Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
178                         + "ms, update_cache=" + cacheTime + " ms");
179             }
180         }
181 
182         return parsed;
183     }
184 
185     /**
186      * Removes the cached value for the thread the parser was created on. It is assumed that
187      * any threads created for parallel parsing will be created and released, so they don't
188      * need an explicit close call.
189      *
190      * Realistically an instance should never be retained, so when the enclosing class is released,
191      * the values will also be released, making this method unnecessary.
192      */
193     @Override
194     public void close() {
195         mSharedResult.remove();
196         mSharedAppInfo.remove();
197     }
198 
199     public static abstract class Callback implements ParsingPackageUtils.Callback {
200 
201         @Override
202         public final ParsingPackage startParsingPackage(@NonNull String packageName,
203                 @NonNull String baseCodePath, @NonNull String codePath,
204                 @NonNull TypedArray manifestArray, boolean isCoreApp) {
205             return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray,
206                     isCoreApp);
207         }
208 
209         /**
210          * An indirection from {@link ParseInput.Callback#isChangeEnabled(long, String, int)},
211          * allowing the {@link ApplicationInfo} objects to be cached in {@link #mSharedAppInfo}
212          * and cleaned up with the parser instance, not the callback instance.
213          *
214          * @param appInfo will only have 3 fields filled in, {@link ApplicationInfo#packageName},
215          * {@link ApplicationInfo#targetSdkVersion}, and {@link ApplicationInfo#uid}
216          */
217         public abstract boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo);
218     }
219 }
220