• 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.internal.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.app.Application;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.parsing.PackageLite;
26 import android.content.pm.parsing.result.ParseInput;
27 import android.content.pm.parsing.result.ParseResult;
28 import android.content.pm.parsing.result.ParseTypeImpl;
29 import android.content.res.TypedArray;
30 import android.os.Build;
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.pm.parsing.pkg.PackageImpl;
37 import com.android.internal.pm.parsing.pkg.ParsedPackage;
38 import com.android.internal.pm.pkg.parsing.ParsingPackage;
39 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
40 import com.android.internal.pm.pkg.parsing.ParsingUtils;
41 import com.android.internal.util.ArrayUtils;
42 
43 import java.io.File;
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * The v2 of package parsing for use when parsing is initiated in the server and must
49  * contain state contained by the server.
50  *
51  * The {@link AutoCloseable} helps signal that this class contains resources that must be freed.
52  * Although it is sufficient to release references to an instance of this class and let it get
53  * collected automatically.
54  */
55 public class PackageParser2 implements AutoCloseable {
56 
57     private static final String TAG = ParsingUtils.TAG;
58 
59     private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
60     private static final int LOG_PARSE_TIMINGS_THRESHOLD_MS = 100;
61 
62     private final ThreadLocal<ApplicationInfo> mSharedAppInfo =
63             ThreadLocal.withInitial(() -> {
64                 ApplicationInfo appInfo = new ApplicationInfo();
65                 appInfo.uid = -1; // Not a valid UID since the app will not be installed yet
66                 return appInfo;
67             });
68 
69     private final ThreadLocal<ParseTypeImpl> mSharedResult;
70 
71     @Nullable
72     protected IPackageCacher mCacher;
73 
74     private final ParsingPackageUtils mParsingUtils;
75 
PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics, @Nullable IPackageCacher cacher, @NonNull Callback callback)76     public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics,
77             @Nullable IPackageCacher cacher, @NonNull Callback callback) {
78         if (displayMetrics == null) {
79             displayMetrics = new DisplayMetrics();
80             displayMetrics.setToDefaults();
81         }
82 
83         List<PermissionManager.SplitPermissionInfo> splitPermissions = null;
84 
85         final Application application = ActivityThread.currentApplication();
86         if (application != null) {
87             final PermissionManager permissionManager =
88                     application.getSystemService(PermissionManager.class);
89             if (permissionManager != null) {
90                 splitPermissions = permissionManager.getSplitPermissions();
91             }
92         }
93         if (splitPermissions == null) {
94             splitPermissions = new ArrayList<>();
95         }
96 
97         mCacher = cacher;
98 
99         mParsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
100                 callback);
101 
102         ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
103             ApplicationInfo appInfo = mSharedAppInfo.get();
104             //noinspection ConstantConditions
105             appInfo.packageName = packageName;
106             appInfo.targetSdkVersion = targetSdkVersion;
107             return callback.isChangeEnabled(changeId, appInfo);
108         };
109 
110         mSharedResult = ThreadLocal.withInitial(() -> new ParseTypeImpl(enforcementCallback));
111     }
112 
113     /**
114      * TODO(b/135203078): Document new package parsing
115      */
116     @AnyThread
parsePackage(File packageFile, int flags, boolean useCaches)117     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
118             throws PackageParserException {
119         var files = packageFile.listFiles();
120         // Apk directory is directly nested under the current directory
121         if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
122             packageFile = files[0];
123         }
124 
125         if (useCaches && mCacher != null) {
126             ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
127             if (parsed != null) {
128                 return parsed;
129             }
130         }
131 
132         long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
133         ParseInput input = mSharedResult.get().reset();
134         ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
135         if (result.isError()) {
136             throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
137                     result.getException());
138         }
139 
140         ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();
141 
142         long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
143         if (mCacher != null) {
144             mCacher.cacheResult(packageFile, flags, parsed);
145         }
146         if (LOG_PARSE_TIMINGS) {
147             parseTime = cacheTime - parseTime;
148             cacheTime = SystemClock.uptimeMillis() - cacheTime;
149             if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
150                 Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
151                         + "ms, update_cache=" + cacheTime + " ms");
152             }
153         }
154 
155         return parsed;
156     }
157 
158     /**
159      * Creates a ParsedPackage from PackageLite without any additional parsing or processing.
160      * Most fields will get reasonable default values, corresponding to "deleted-keep-data".
161      */
162     @AnyThread
parsePackageFromPackageLite(PackageLite packageLite, int flags)163     public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags)
164             throws PackageParserException {
165         ParseInput input = mSharedResult.get().reset();
166         ParseResult<ParsingPackage> result = mParsingUtils.parsePackageFromPackageLite(input,
167                 packageLite, flags);
168         if (result.isError()) {
169             throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
170                     result.getException());
171         }
172         return result.getResult().hideAsParsed();
173     }
174 
175     /**
176      * Removes the cached value for the thread the parser was created on. It is assumed that
177      * any threads created for parallel parsing will be created and released, so they don't
178      * need an explicit close call.
179      *
180      * Realistically an instance should never be retained, so when the enclosing class is released,
181      * the values will also be released, making this method unnecessary.
182      */
183     @Override
close()184     public void close() {
185         mSharedResult.remove();
186         mSharedAppInfo.remove();
187     }
188 
189     public abstract static class Callback implements ParsingPackageUtils.Callback {
190 
191         @Override
startParsingPackage(@onNull String packageName, @NonNull String baseCodePath, @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp)192         public final ParsingPackage startParsingPackage(@NonNull String packageName,
193                 @NonNull String baseCodePath, @NonNull String codePath,
194                 @NonNull TypedArray manifestArray, boolean isCoreApp) {
195             return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray,
196                     isCoreApp, Callback.this);
197         }
198 
199         /**
200          * An indirection from {@link ParseInput.Callback#isChangeEnabled(long, String, int)},
201          * allowing the {@link ApplicationInfo} objects to be cached in {@link #mSharedAppInfo}
202          * and cleaned up with the parser instance, not the callback instance.
203          *
204          * @param appInfo will only have 3 fields filled in, {@link ApplicationInfo#packageName},
205          * {@link ApplicationInfo#targetSdkVersion}, and {@link ApplicationInfo#uid}
206          */
isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo)207         public abstract boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo);
208     }
209 }
210