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