blob: 3a74ab51e9c7b4d229f6a647c29c1ccd495718ef [file] [log] [blame]
Calin Juravleb8976d82016-12-16 16:22:00 +00001/*
2 * Copyright (C) 2016 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
17package com.android.server.pm.dex;
18
Victor Hsieh785d6182018-04-19 14:26:28 -070019import android.content.ContentResolver;
20import android.content.Context;
Calin Juravlec22c30e2017-01-16 19:18:48 -080021import android.content.pm.ApplicationInfo;
22import android.content.pm.IPackageManager;
Calin Juravleb8976d82016-12-16 16:22:00 +000023import android.content.pm.PackageInfo;
Victor Hsieh785d6182018-04-19 14:26:28 -070024import android.content.pm.PackageParser;
25import android.database.ContentObserver;
26import android.os.Build;
Calin Juravleadbadd52017-03-28 18:19:15 -070027import android.os.FileUtils;
Calin Juravlec22c30e2017-01-16 19:18:48 -080028import android.os.RemoteException;
Calin Juravle1aa5f882017-01-25 01:05:50 -080029import android.os.storage.StorageManager;
Victor Hsieh785d6182018-04-19 14:26:28 -070030import android.os.SystemProperties;
Calin Juravle99dd37b2017-02-22 19:05:06 -080031import android.os.UserHandle;
Victor Hsieh785d6182018-04-19 14:26:28 -070032import android.provider.Settings.Global;
Alan Stokes6dba50d2018-10-30 15:05:36 +000033import android.util.Log;
Calin Juravleb8976d82016-12-16 16:22:00 +000034import android.util.Slog;
Victor Hsieh785d6182018-04-19 14:26:28 -070035import android.util.jar.StrictJarFile;
Calin Juravleb8976d82016-12-16 16:22:00 +000036
Calin Juravle1aa5f882017-01-25 01:05:50 -080037import com.android.internal.annotations.GuardedBy;
Alan Stokesb6c3a602018-11-02 12:10:42 +000038import com.android.internal.annotations.VisibleForTesting;
Victor Hsieh785d6182018-04-19 14:26:28 -070039import com.android.internal.util.ArrayUtils;
Calin Juravle1aa5f882017-01-25 01:05:50 -080040import com.android.server.pm.Installer;
41import com.android.server.pm.Installer.InstallerException;
Calin Juravlec22c30e2017-01-16 19:18:48 -080042import com.android.server.pm.PackageDexOptimizer;
Calin Juravle3d2af7f2017-04-19 19:56:21 -070043import com.android.server.pm.PackageManagerService;
Calin Juravleb8976d82016-12-16 16:22:00 +000044import com.android.server.pm.PackageManagerServiceUtils;
45
46import java.io.File;
47import java.io.IOException;
Victor Hsieh785d6182018-04-19 14:26:28 -070048import java.util.Arrays;
Calin Juravle52a452c2017-08-04 01:42:17 -070049import java.util.Collection;
50import java.util.Collections;
Victor Hsieh785d6182018-04-19 14:26:28 -070051import java.util.Iterator;
Calin Juravleb8976d82016-12-16 16:22:00 +000052import java.util.List;
53import java.util.HashMap;
54import java.util.HashSet;
55import java.util.Map;
56import java.util.Set;
Victor Hsieh785d6182018-04-19 14:26:28 -070057import java.util.zip.ZipEntry;
Calin Juravleb8976d82016-12-16 16:22:00 +000058
Calin Juravle3d2af7f2017-04-19 19:56:21 -070059import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
Calin Juravlec22c30e2017-01-16 19:18:48 -080060import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
61import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
62
Calin Juravleb8976d82016-12-16 16:22:00 +000063/**
64 * This class keeps track of how dex files are used.
65 * Every time it gets a notification about a dex file being loaded it tracks
66 * its owning package and records it in PackageDexUsage (package-dex-usage.list).
67 *
68 * TODO(calin): Extract related dexopt functionality from PackageManagerService
69 * into this class.
70 */
71public class DexManager {
72 private static final String TAG = "DexManager";
73
Victor Hsieh785d6182018-04-19 14:26:28 -070074 private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
75 private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST =
76 "pm.dexopt.priv-apps-oob-list";
77
Alan Stokes6dba50d2018-10-30 15:05:36 +000078 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Calin Juravleb8976d82016-12-16 16:22:00 +000079
Victor Hsieh785d6182018-04-19 14:26:28 -070080 private final Context mContext;
81
Calin Juravleb8976d82016-12-16 16:22:00 +000082 // Maps package name to code locations.
83 // It caches the code locations for the installed packages. This allows for
84 // faster lookups (no locks) when finding what package owns the dex file.
Calin Juravle1fade2f2017-05-02 18:50:56 -070085 @GuardedBy("mPackageCodeLocationsCache")
Calin Juravleb8976d82016-12-16 16:22:00 +000086 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;
87
88 // PackageDexUsage handles the actual I/O operations. It is responsible to
89 // encode and save the dex usage data.
90 private final PackageDexUsage mPackageDexUsage;
91
Calin Juravlec22c30e2017-01-16 19:18:48 -080092 private final IPackageManager mPackageManager;
93 private final PackageDexOptimizer mPackageDexOptimizer;
Calin Juravle1aa5f882017-01-25 01:05:50 -080094 private final Object mInstallLock;
95 @GuardedBy("mInstallLock")
96 private final Installer mInstaller;
Alan Stokesa0023602017-10-16 12:31:44 +010097 private final Listener mListener;
Calin Juravlec22c30e2017-01-16 19:18:48 -080098
Calin Juravleb8976d82016-12-16 16:22:00 +000099 // Possible outcomes of a dex search.
100 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
101 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk
102 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk
103 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex
104
Calin Juravle3b74c412017-08-03 19:48:37 -0700105 /**
106 * We do not record packages that have no secondary dex files or that are not used by other
107 * apps. This is an optimization to reduce the amount of data that needs to be written to
108 * disk (apps will not usually be shared so this trims quite a bit the number we record).
109 *
110 * To make this behaviour transparent to the callers which need use information on packages,
111 * DexManager will return this DEFAULT instance from
112 * {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and
113 * is marked as not being used by other apps. This reflects the intended behaviour when we don't
114 * find the package in the underlying data file.
115 */
116 private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
117
Alan Stokesa0023602017-10-16 12:31:44 +0100118 public interface Listener {
119 /**
120 * Invoked just before the secondary dex file {@code dexPath} for the specified application
121 * is reconciled.
122 */
123 void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
124 String dexPath, int storageFlags);
125 }
126
Victor Hsieh785d6182018-04-19 14:26:28 -0700127 public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
Alan Stokesa0023602017-10-16 12:31:44 +0100128 Installer installer, Object installLock, Listener listener) {
Victor Hsieh785d6182018-04-19 14:26:28 -0700129 mContext = context;
Calin Juravleb8976d82016-12-16 16:22:00 +0000130 mPackageCodeLocationsCache = new HashMap<>();
131 mPackageDexUsage = new PackageDexUsage();
Calin Juravlec22c30e2017-01-16 19:18:48 -0800132 mPackageManager = pms;
133 mPackageDexOptimizer = pdo;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800134 mInstaller = installer;
135 mInstallLock = installLock;
Alan Stokesa0023602017-10-16 12:31:44 +0100136 mListener = listener;
Calin Juravleb8976d82016-12-16 16:22:00 +0000137 }
138
Victor Hsieh785d6182018-04-19 14:26:28 -0700139 public void systemReady() {
140 registerSettingObserver();
141 }
142
Calin Juravleb8976d82016-12-16 16:22:00 +0000143 /**
144 * Notify about dex files loads.
145 * Note that this method is invoked when apps load dex files and it should
146 * return as fast as possible.
147 *
Calin Juravleadbadd52017-03-28 18:19:15 -0700148 * @param loadingAppInfo the package performing the load
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700149 * @param classLoadersNames the names of the class loaders present in the loading chain. The
150 * list encodes the class loader chain in the natural order. The first class loader has
151 * the second one as its parent and so on. The dex files present in the class path of the
152 * first class loader will be recorded in the usage file.
153 * @param classPaths the class paths corresponding to the class loaders names from
154 * {@param classLoadersNames}. The the first element corresponds to the first class loader
155 * and so on. A classpath is represented as a list of dex files separated by
Alan Stokesb6c3a602018-11-02 12:10:42 +0000156 * {@code File.pathSeparator}, or null if the class loader's classpath is not known.
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700157 * The dex files found in the first class path will be recorded in the usage file.
Calin Juravleb8976d82016-12-16 16:22:00 +0000158 * @param loaderIsa the ISA of the app loading the dex files
159 * @param loaderUserId the user id which runs the code loading the dex files
160 */
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700161 public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames,
162 List<String> classPaths, String loaderIsa, int loaderUserId) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000163 try {
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700164 notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
165 loaderUserId);
Calin Juravleb8976d82016-12-16 16:22:00 +0000166 } catch (Exception e) {
167 Slog.w(TAG, "Exception while notifying dex load for package " +
168 loadingAppInfo.packageName, e);
169 }
170 }
171
Alan Stokesb6c3a602018-11-02 12:10:42 +0000172 @VisibleForTesting
173 /*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700174 List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
175 int loaderUserId) {
176 if (classLoaderNames.size() != classPaths.size()) {
177 Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
178 return;
179 }
180 if (classLoaderNames.isEmpty()) {
181 Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
182 return;
183 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000184 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700185 Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
Calin Juravleb8976d82016-12-16 16:22:00 +0000186 loaderIsa + "?");
187 return;
188 }
189
Alan Stokesb6c3a602018-11-02 12:10:42 +0000190 // The first classpath should never be null because the first classloader
191 // should always be an instance of BaseDexClassLoader.
192 String firstClassPath = classPaths.get(0);
193 if (firstClassPath == null) {
194 return;
195 }
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700196 // The classpath is represented as a list of dex files separated by File.pathSeparator.
Alan Stokesb6c3a602018-11-02 12:10:42 +0000197 String[] dexPathsToRegister = firstClassPath.split(File.pathSeparator);
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700198
199 // Encode the class loader contexts for the dexPathsToRegister.
200 String[] classLoaderContexts = DexoptUtils.processContextForDexLoad(
201 classLoaderNames, classPaths);
202
Alan Stokes6dba50d2018-10-30 15:05:36 +0000203 // A null classLoaderContexts means that there are unsupported class loaders in the
204 // chain.
205 if (classLoaderContexts == null) {
206 if (DEBUG) {
207 Slog.i(TAG, loadingAppInfo.packageName +
208 " uses unsupported class loader in " + classLoaderNames);
209 }
210 return;
211 }
212
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700213 int dexPathIndex = 0;
214 for (String dexPath : dexPathsToRegister) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000215 // Find the owning package name.
216 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
217
218 if (DEBUG) {
219 Slog.i(TAG, loadingAppInfo.packageName
220 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
221 }
222
223 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
224 // TODO(calin): extend isUsedByOtherApps check to detect the cases where
225 // different apps share the same runtime. In that case we should not mark the dex
226 // file as isUsedByOtherApps. Currently this is a safe approximation.
227 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals(
228 searchResult.mOwningPackageName);
229 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
230 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;
231
232 if (primaryOrSplit && !isUsedByOtherApps) {
233 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
234 // do not record it. This case does not bring any new usable information
235 // and can be safely skipped.
236 continue;
237 }
238
239 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
Alan Stokes6dba50d2018-10-30 15:05:36 +0000240 // or UsedByOtherApps), record will return true and we trigger an async write
Calin Juravleb8976d82016-12-16 16:22:00 +0000241 // to disk to make sure we don't loose the data in case of a reboot.
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700242
Alan Stokes6dba50d2018-10-30 15:05:36 +0000243 String classLoaderContext = classLoaderContexts[dexPathIndex];
Calin Juravleb8976d82016-12-16 16:22:00 +0000244 if (mPackageDexUsage.record(searchResult.mOwningPackageName,
Calin Juravle535a4752017-02-03 16:55:49 -0800245 dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700246 loadingAppInfo.packageName, classLoaderContext)) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000247 mPackageDexUsage.maybeWriteAsync();
248 }
249 } else {
Calin Juravleb8976d82016-12-16 16:22:00 +0000250 // If we can't find the owner of the dex we simply do not track it. The impact is
251 // that the dex file will not be considered for offline optimizations.
Calin Juravleb8976d82016-12-16 16:22:00 +0000252 if (DEBUG) {
253 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
254 }
255 }
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700256 dexPathIndex++;
Calin Juravleb8976d82016-12-16 16:22:00 +0000257 }
258 }
259
260 /**
261 * Read the dex usage from disk and populate the code cache locations.
262 * @param existingPackages a map containing information about what packages
263 * are available to what users. Only packages in this list will be
264 * recognized during notifyDexLoad().
265 */
266 public void load(Map<Integer, List<PackageInfo>> existingPackages) {
267 try {
268 loadInternal(existingPackages);
269 } catch (Exception e) {
270 mPackageDexUsage.clear();
271 Slog.w(TAG, "Exception while loading package dex usage. " +
272 "Starting with a fresh state.", e);
273 }
274 }
275
Calin Juravle99dd37b2017-02-22 19:05:06 -0800276 /**
277 * Notifies that a new package was installed for {@code userId}.
278 * {@code userId} must not be {@code UserHandle.USER_ALL}.
279 *
280 * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}.
281 */
282 public void notifyPackageInstalled(PackageInfo pi, int userId) {
283 if (userId == UserHandle.USER_ALL) {
284 throw new IllegalArgumentException(
285 "notifyPackageInstalled called with USER_ALL");
286 }
Calin Juravleadbadd52017-03-28 18:19:15 -0700287 cachePackageInfo(pi, userId);
Calin Juravle0d4b8f82017-01-23 23:34:25 -0800288 }
289
Calin Juravle99dd37b2017-02-22 19:05:06 -0800290 /**
291 * Notifies that package {@code packageName} was updated.
292 * This will clear the UsedByOtherApps mark if it exists.
293 */
294 public void notifyPackageUpdated(String packageName, String baseCodePath,
295 String[] splitCodePaths) {
296 cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1);
297 // In case there was an update, write the package use info to disk async.
298 // Note that we do the writing here and not in PackageDexUsage in order to be
299 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
Calin Juravle1fade2f2017-05-02 18:50:56 -0700300 // multiple updates in PackageDexUsage before writing it).
Calin Juravle99dd37b2017-02-22 19:05:06 -0800301 if (mPackageDexUsage.clearUsedByOtherApps(packageName)) {
302 mPackageDexUsage.maybeWriteAsync();
303 }
304 }
305
306 /**
307 * Notifies that the user {@code userId} data for package {@code packageName}
308 * was destroyed. This will remove all usage info associated with the package
309 * for the given user.
310 * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case
311 * all usage information for the package will be removed.
312 */
313 public void notifyPackageDataDestroyed(String packageName, int userId) {
314 boolean updated = userId == UserHandle.USER_ALL
315 ? mPackageDexUsage.removePackage(packageName)
316 : mPackageDexUsage.removeUserPackage(packageName, userId);
317 // In case there was an update, write the package use info to disk async.
318 // Note that we do the writing here and not in PackageDexUsage in order to be
319 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
Calin Juravle1fade2f2017-05-02 18:50:56 -0700320 // multiple updates in PackageDexUsage before writing it).
Calin Juravle99dd37b2017-02-22 19:05:06 -0800321 if (updated) {
322 mPackageDexUsage.maybeWriteAsync();
323 }
324 }
325
Calin Juravleadbadd52017-03-28 18:19:15 -0700326 /**
327 * Caches the code location from the given package info.
328 */
329 private void cachePackageInfo(PackageInfo pi, int userId) {
330 ApplicationInfo ai = pi.applicationInfo;
331 String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir,
332 ai.credentialProtectedDataDir};
333 cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs,
334 dataDirs, userId);
335 }
336
337 private void cachePackageCodeLocation(String packageName, String baseCodePath,
338 String[] splitCodePaths, String[] dataDirs, int userId) {
Calin Juravle1fade2f2017-05-02 18:50:56 -0700339 synchronized (mPackageCodeLocationsCache) {
340 PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName,
341 new PackageCodeLocations(packageName, baseCodePath, splitCodePaths));
342 // TODO(calin): We are forced to extend the scope of this synchronization because
343 // the values of the cache (PackageCodeLocations) are updated in place.
344 // Make PackageCodeLocations immutable to simplify the synchronization reasoning.
345 pcl.updateCodeLocation(baseCodePath, splitCodePaths);
346 if (dataDirs != null) {
347 for (String dataDir : dataDirs) {
348 // The set of data dirs includes deviceProtectedDataDir and
349 // credentialProtectedDataDir which might be null for shared
350 // libraries. Currently we don't track these but be lenient
351 // and check in case we ever decide to store their usage data.
352 if (dataDir != null) {
353 pcl.mergeAppDataDirs(dataDir, userId);
354 }
Calin Juravleadbadd52017-03-28 18:19:15 -0700355 }
356 }
Calin Juravle0d4b8f82017-01-23 23:34:25 -0800357 }
358 }
359
Calin Juravleb8976d82016-12-16 16:22:00 +0000360 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
361 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
Calin Juravle52a452c2017-08-04 01:42:17 -0700362 Map<String, Set<String>> packageToCodePaths = new HashMap<>();
363
Calin Juravleb8976d82016-12-16 16:22:00 +0000364 // Cache the code locations for the installed packages. This allows for
365 // faster lookups (no locks) when finding what package owns the dex file.
366 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
367 List<PackageInfo> packageInfoList = entry.getValue();
368 int userId = entry.getKey();
369 for (PackageInfo pi : packageInfoList) {
370 // Cache the code locations.
Calin Juravleadbadd52017-03-28 18:19:15 -0700371 cachePackageInfo(pi, userId);
Calin Juravle0d4b8f82017-01-23 23:34:25 -0800372
Calin Juravle52a452c2017-08-04 01:42:17 -0700373 // Cache two maps:
374 // - from package name to the set of user ids who installed the package.
375 // - from package name to the set of code paths.
Calin Juravleb8976d82016-12-16 16:22:00 +0000376 // We will use it to sync the data and remove obsolete entries from
377 // mPackageDexUsage.
378 Set<Integer> users = putIfAbsent(
379 packageToUsersMap, pi.packageName, new HashSet<>());
380 users.add(userId);
Calin Juravle52a452c2017-08-04 01:42:17 -0700381
382 Set<String> codePaths = putIfAbsent(
383 packageToCodePaths, pi.packageName, new HashSet<>());
384 codePaths.add(pi.applicationInfo.sourceDir);
385 if (pi.applicationInfo.splitSourceDirs != null) {
386 Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs);
387 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000388 }
389 }
390
391 mPackageDexUsage.read();
Calin Juravle52a452c2017-08-04 01:42:17 -0700392 mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths);
Calin Juravleb8976d82016-12-16 16:22:00 +0000393 }
394
395 /**
396 * Get the package dex usage for the given package name.
Calin Juravle3b74c412017-08-03 19:48:37 -0700397 * If there is no usage info the method will return a default {@code PackageUseInfo} with
398 * no data about secondary dex files and marked as not being used by other apps.
399 *
400 * Note that no use info means the package was not used or it was used but not by other apps.
401 * Also, note that right now we might prune packages which are not used by other apps.
402 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
403 * to access the package use.
Calin Juravleb8976d82016-12-16 16:22:00 +0000404 */
Calin Juravle3b74c412017-08-03 19:48:37 -0700405 public PackageUseInfo getPackageUseInfoOrDefault(String packageName) {
406 PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName);
407 return useInfo == null ? DEFAULT_USE_INFO : useInfo;
408 }
409
410 /**
411 * Return whether or not the manager has usage information on the give package.
412 *
413 * Note that no use info means the package was not used or it was used but not by other apps.
414 * Also, note that right now we might prune packages which are not used by other apps.
415 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
416 * to access the package use.
417 */
418 /*package*/ boolean hasInfoOnPackage(String packageName) {
419 return mPackageDexUsage.getPackageUseInfo(packageName) != null;
Calin Juravleb8976d82016-12-16 16:22:00 +0000420 }
421
422 /**
Calin Juravle1d0e83d2017-07-17 15:12:01 -0700423 * Perform dexopt on with the given {@code options} on the secondary dex files.
Calin Juravlec22c30e2017-01-16 19:18:48 -0800424 * @return true if all secondary dex files were processed successfully (compiled or skipped
425 * because they don't need to be compiled)..
426 */
Calin Juravle1d0e83d2017-07-17 15:12:01 -0700427 public boolean dexoptSecondaryDex(DexoptOptions options) {
Calin Juravlec22c30e2017-01-16 19:18:48 -0800428 // Select the dex optimizer based on the force parameter.
429 // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
430 // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
431 // passing the force flag through the multitude of layers.
432 // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
433 // allocate an object here.
Calin Juravle1d0e83d2017-07-17 15:12:01 -0700434 PackageDexOptimizer pdo = options.isForce()
Calin Juravlec22c30e2017-01-16 19:18:48 -0800435 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
436 : mPackageDexOptimizer;
Calin Juravle1d0e83d2017-07-17 15:12:01 -0700437 String packageName = options.getPackageName();
Calin Juravle3b74c412017-08-03 19:48:37 -0700438 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
Alan Stokesa0023602017-10-16 12:31:44 +0100439 if (useInfo.getDexUseInfoMap().isEmpty()) {
Calin Juravlec22c30e2017-01-16 19:18:48 -0800440 if (DEBUG) {
441 Slog.d(TAG, "No secondary dex use for package:" + packageName);
442 }
443 // Nothing to compile, return true.
444 return true;
445 }
446 boolean success = true;
447 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
448 String dexPath = entry.getKey();
449 DexUseInfo dexUseInfo = entry.getValue();
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700450
451 PackageInfo pkg;
Calin Juravlec22c30e2017-01-16 19:18:48 -0800452 try {
453 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
454 dexUseInfo.getOwnerUserId());
455 } catch (RemoteException e) {
456 throw new AssertionError(e);
457 }
458 // It may be that the package gets uninstalled while we try to compile its
459 // secondary dex files. If that's the case, just ignore.
460 // Note that we don't break the entire loop because the package might still be
461 // installed for other users.
462 if (pkg == null) {
463 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
464 + " for user " + dexUseInfo.getOwnerUserId());
Calin Juravle1aa5f882017-01-25 01:05:50 -0800465 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
Calin Juravlec22c30e2017-01-16 19:18:48 -0800466 continue;
467 }
Calin Juravleadbadd52017-03-28 18:19:15 -0700468
Calin Juravlec22c30e2017-01-16 19:18:48 -0800469 int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700470 dexUseInfo, options);
Calin Juravlec22c30e2017-01-16 19:18:48 -0800471 success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
472 }
473 return success;
474 }
475
476 /**
Calin Juravle1aa5f882017-01-25 01:05:50 -0800477 * Reconcile the information we have about the secondary dex files belonging to
478 * {@code packagName} and the actual dex files. For all dex files that were
479 * deleted, update the internal records and delete any generated oat files.
480 */
481 public void reconcileSecondaryDexFiles(String packageName) {
Calin Juravle3b74c412017-08-03 19:48:37 -0700482 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
Alan Stokesa0023602017-10-16 12:31:44 +0100483 if (useInfo.getDexUseInfoMap().isEmpty()) {
Calin Juravle1aa5f882017-01-25 01:05:50 -0800484 if (DEBUG) {
485 Slog.d(TAG, "No secondary dex use for package:" + packageName);
486 }
487 // Nothing to reconcile.
488 return;
489 }
Calin Juravleadbadd52017-03-28 18:19:15 -0700490
Calin Juravleb1097412017-01-26 18:53:23 -0800491 boolean updated = false;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800492 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
493 String dexPath = entry.getKey();
494 DexUseInfo dexUseInfo = entry.getValue();
495 PackageInfo pkg = null;
496 try {
497 // Note that we look for the package in the PackageManager just to be able
498 // to get back the real app uid and its storage kind. These are only used
499 // to perform extra validation in installd.
500 // TODO(calin): maybe a bit overkill.
501 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
502 dexUseInfo.getOwnerUserId());
503 } catch (RemoteException ignore) {
504 // Can't happen, DexManager is local.
505 }
506 if (pkg == null) {
507 // It may be that the package was uninstalled while we process the secondary
508 // dex files.
509 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
510 + " for user " + dexUseInfo.getOwnerUserId());
511 // Update the usage and continue, another user might still have the package.
Calin Juravleb1097412017-01-26 18:53:23 -0800512 updated = mPackageDexUsage.removeUserPackage(
513 packageName, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800514 continue;
515 }
516 ApplicationInfo info = pkg.applicationInfo;
517 int flags = 0;
Calin Juravleadbadd52017-03-28 18:19:15 -0700518 if (info.deviceProtectedDataDir != null &&
519 FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
Calin Juravle1aa5f882017-01-25 01:05:50 -0800520 flags |= StorageManager.FLAG_STORAGE_DE;
Calin Juravleadbadd52017-03-28 18:19:15 -0700521 } else if (info.credentialProtectedDataDir!= null &&
522 FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
Calin Juravle1aa5f882017-01-25 01:05:50 -0800523 flags |= StorageManager.FLAG_STORAGE_CE;
524 } else {
Calin Juravleadbadd52017-03-28 18:19:15 -0700525 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
526 updated = mPackageDexUsage.removeDexFile(
527 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800528 continue;
529 }
530
Alan Stokesa0023602017-10-16 12:31:44 +0100531 if (mListener != null) {
532 mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
533 }
534
Calin Juravle1aa5f882017-01-25 01:05:50 -0800535 boolean dexStillExists = true;
536 synchronized(mInstallLock) {
537 try {
538 String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
539 dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
Alan Stokesa0023602017-10-16 12:31:44 +0100540 info.uid, isas, info.volumeUuid, flags);
Calin Juravle1aa5f882017-01-25 01:05:50 -0800541 } catch (InstallerException e) {
542 Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
543 " : " + e.getMessage());
544 }
545 }
546 if (!dexStillExists) {
Calin Juravleb1097412017-01-26 18:53:23 -0800547 updated = mPackageDexUsage.removeDexFile(
548 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800549 }
Calin Juravleb1097412017-01-26 18:53:23 -0800550
Calin Juravle1aa5f882017-01-25 01:05:50 -0800551 }
Calin Juravleb1097412017-01-26 18:53:23 -0800552 if (updated) {
553 mPackageDexUsage.maybeWriteAsync();
Calin Juravle1aa5f882017-01-25 01:05:50 -0800554 }
555 }
556
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700557 // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
558 // compilation happening here will use a pessimistic context.
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700559 public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
560 boolean isUsedByOtherApps, int userId) {
561 // Find the owning package record.
562 DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
563
564 if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) {
565 return new RegisterDexModuleResult(false, "Package not found");
566 }
567 if (!info.packageName.equals(searchResult.mOwningPackageName)) {
568 return new RegisterDexModuleResult(false, "Dex path does not belong to package");
569 }
570 if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
571 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) {
572 return new RegisterDexModuleResult(false, "Main apks cannot be registered");
573 }
574
575 // We found the package. Now record the usage for all declared ISAs.
576 boolean update = false;
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700577 for (String isa : getAppDexInstructionSets(info)) {
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700578 boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
Calin Juravle535a4752017-02-03 16:55:49 -0800579 dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false,
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700580 searchResult.mOwningPackageName,
581 PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT);
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700582 update |= newUpdate;
583 }
584 if (update) {
585 mPackageDexUsage.maybeWriteAsync();
586 }
587
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700588 DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName)
589 .getDexUseInfoMap().get(dexPath);
590
Calin Juravle4bc8f4d2018-02-12 12:00:44 -0800591 // Try to optimize the package according to the install reason.
592 DexoptOptions options = new DexoptOptions(info.packageName,
593 PackageManagerService.REASON_INSTALL, /*flags*/0);
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700594
595 int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo,
596 options);
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700597
598 // If we fail to optimize the package log an error but don't propagate the error
599 // back to the app. The app cannot do much about it and the background job
600 // will rety again when it executes.
601 // TODO(calin): there might be some value to return the error here but it may
602 // cause red herrings since that doesn't mean the app cannot use the module.
603 if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
604 Slog.e(TAG, "Failed to optimize dex module " + dexPath);
605 }
606 return new RegisterDexModuleResult(true, "Dex module registered successfully");
607 }
608
Calin Juravle1aa5f882017-01-25 01:05:50 -0800609 /**
Calin Juravle51f521c2017-01-25 18:00:05 -0800610 * Return all packages that contain records of secondary dex files.
611 */
612 public Set<String> getAllPackagesWithSecondaryDexFiles() {
613 return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
Calin Juravleb8976d82016-12-16 16:22:00 +0000614 }
615
616 /**
Calin Juravleb8976d82016-12-16 16:22:00 +0000617 * Retrieves the package which owns the given dexPath.
618 */
619 private DexSearchResult getDexPackage(
620 ApplicationInfo loadingAppInfo, String dexPath, int userId) {
621 // Ignore framework code.
622 // TODO(calin): is there a better way to detect it?
623 if (dexPath.startsWith("/system/framework/")) {
Calin Juravle2dfc1b32017-03-10 18:24:33 -0800624 return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
Calin Juravleb8976d82016-12-16 16:22:00 +0000625 }
626
627 // First, check if the package which loads the dex file actually owns it.
628 // Most of the time this will be true and we can return early.
629 PackageCodeLocations loadingPackageCodeLocations =
630 new PackageCodeLocations(loadingAppInfo, userId);
631 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
632 if (outcome != DEX_SEARCH_NOT_FOUND) {
633 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
634 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
635 }
636
637 // The loadingPackage does not own the dex file.
638 // Perform a reverse look-up in the cache to detect if any package has ownership.
639 // Note that we can have false negatives if the cache falls out of date.
Calin Juravle1fade2f2017-05-02 18:50:56 -0700640 synchronized (mPackageCodeLocationsCache) {
641 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
642 outcome = pcl.searchDex(dexPath, userId);
643 if (outcome != DEX_SEARCH_NOT_FOUND) {
644 return new DexSearchResult(pcl.mPackageName, outcome);
645 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000646 }
647 }
648
Calin Juravle6304ec32017-03-20 23:46:44 -0700649 if (DEBUG) {
650 // TODO(calin): Consider checking for /data/data symlink.
651 // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
652 // to load dex files through it.
653 try {
654 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
655 if (dexPathReal != dexPath) {
656 Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
657 dexPath + " dexPathReal=" + dexPathReal);
658 }
659 } catch (IOException e) {
660 // Ignore
661 }
662 }
663 // Cache miss. The cache is updated during installs and uninstalls,
664 // so if we get here we're pretty sure the dex path does not exist.
Calin Juravleb8976d82016-12-16 16:22:00 +0000665 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
666 }
667
668 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
669 V existingValue = map.putIfAbsent(key, newValue);
670 return existingValue == null ? newValue : existingValue;
671 }
672
Calin Juravle14876bd2017-07-28 16:13:35 -0700673 /**
Calin Juravle52a452c2017-08-04 01:42:17 -0700674 * Writes the in-memory package dex usage to disk right away.
Calin Juravle14876bd2017-07-28 16:13:35 -0700675 */
Calin Juravle52a452c2017-08-04 01:42:17 -0700676 public void writePackageDexUsageNow() {
Calin Juravle14876bd2017-07-28 16:13:35 -0700677 mPackageDexUsage.writeNow();
678 }
679
Victor Hsieh785d6182018-04-19 14:26:28 -0700680 private void registerSettingObserver() {
681 final ContentResolver resolver = mContext.getContentResolver();
682
683 // This observer provides a one directional mapping from Global.PRIV_APP_OOB_ENABLED to
684 // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
685 // it is done.
686 ContentObserver privAppOobObserver = new ContentObserver(null) {
687 @Override
688 public void onChange(boolean selfChange) {
689 int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
690 SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
691 oobEnabled == 1 ? "true" : "false");
692 }
693 };
694 resolver.registerContentObserver(
695 Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
696 UserHandle.USER_SYSTEM);
697 // At boot, restore the value from the setting, which persists across reboot.
698 privAppOobObserver.onChange(true);
699
700 ContentObserver privAppOobListObserver = new ContentObserver(null) {
701 @Override
702 public void onChange(boolean selfChange) {
703 String oobList = Global.getString(resolver, Global.PRIV_APP_OOB_LIST);
704 if (oobList == null) {
705 oobList = "ALL";
706 }
707 SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, oobList);
708 }
709 };
710 resolver.registerContentObserver(
711 Global.getUriFor(Global.PRIV_APP_OOB_LIST), false, privAppOobListObserver,
712 UserHandle.USER_SYSTEM);
713 // At boot, restore the value from the setting, which persists across reboot.
714 privAppOobListObserver.onChange(true);
715 }
716
717 /**
718 * Returns whether the given package is in the list of privilaged apps that should run out of
719 * box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that when
720 * the the OOB list is empty, all priv apps will run in OOB mode.
721 */
722 public static boolean isPackageSelectedToRunOob(String packageName) {
723 return isPackageSelectedToRunOob(Arrays.asList(packageName));
724 }
725
726 /**
727 * Returns whether any of the given packages are in the list of privilaged apps that should run
728 * out of box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that
729 * when the the OOB list is empty, all priv apps will run in OOB mode.
730 */
731 public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
732 if (!SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
733 return false;
734 }
735 String oobListProperty = SystemProperties.get(
736 PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL");
737 if ("ALL".equals(oobListProperty)) {
738 return true;
739 }
740 for (String oobPkgName : oobListProperty.split(",")) {
741 if (packageNamesInSameProcess.contains(oobPkgName)) {
742 return true;
743 }
744 }
745 return false;
746 }
747
748 /**
749 * Generates package related log if the package has code stored in unexpected way.
750 */
751 public static void maybeLogUnexpectedPackageDetails(PackageParser.Package pkg) {
752 if (!Build.IS_DEBUGGABLE) {
753 return;
754 }
755
756 if (pkg.isPrivileged() && isPackageSelectedToRunOob(pkg.packageName)) {
757 logIfPackageHasUncompressedCode(pkg);
758 }
759 }
760
761 /**
762 * Generates log if the APKs in the given package have uncompressed dex file and so
763 * files that can be direclty mapped.
764 */
765 private static void logIfPackageHasUncompressedCode(PackageParser.Package pkg) {
766 logIfApkHasUncompressedCode(pkg.baseCodePath);
767 if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
768 for (int i = 0; i < pkg.splitCodePaths.length; i++) {
769 logIfApkHasUncompressedCode(pkg.splitCodePaths[i]);
770 }
771 }
772 }
773
774 /**
775 * Generates log if the archive located at {@code fileName} has uncompressed dex file and so
776 * files that can be direclty mapped.
777 */
778 private static void logIfApkHasUncompressedCode(String fileName) {
779 StrictJarFile jarFile = null;
780 try {
781 jarFile = new StrictJarFile(fileName,
782 false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
783 Iterator<ZipEntry> it = jarFile.iterator();
784 while (it.hasNext()) {
785 ZipEntry entry = it.next();
786 if (entry.getName().endsWith(".dex")) {
787 if (entry.getMethod() != ZipEntry.STORED) {
788 Slog.w(TAG, "APK " + fileName + " has compressed dex code " +
789 entry.getName());
790 } else if ((entry.getDataOffset() & 0x3) != 0) {
791 Slog.w(TAG, "APK " + fileName + " has unaligned dex code " +
792 entry.getName());
793 }
794 } else if (entry.getName().endsWith(".so")) {
795 if (entry.getMethod() != ZipEntry.STORED) {
796 Slog.w(TAG, "APK " + fileName + " has compressed native code " +
797 entry.getName());
798 } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
799 Slog.w(TAG, "APK " + fileName + " has unaligned native code " +
800 entry.getName());
801 }
802 }
803 }
804 } catch (IOException ignore) {
805 Slog.wtf(TAG, "Error when parsing APK " + fileName);
806 } finally {
807 try {
808 if (jarFile != null) {
809 jarFile.close();
810 }
811 } catch (IOException ignore) {}
812 }
813 }
814
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700815 public static class RegisterDexModuleResult {
816 public RegisterDexModuleResult() {
817 this(false, null);
818 }
819
820 public RegisterDexModuleResult(boolean success, String message) {
821 this.success = success;
822 this.message = message;
823 }
824
825 public final boolean success;
826 public final String message;
827 }
828
Calin Juravleb8976d82016-12-16 16:22:00 +0000829 /**
830 * Convenience class to store the different locations where a package might
831 * own code.
832 */
833 private static class PackageCodeLocations {
834 private final String mPackageName;
Calin Juravle99dd37b2017-02-22 19:05:06 -0800835 private String mBaseCodePath;
Calin Juravleb8976d82016-12-16 16:22:00 +0000836 private final Set<String> mSplitCodePaths;
837 // Maps user id to the application private directory.
838 private final Map<Integer, Set<String>> mAppDataDirs;
839
840 public PackageCodeLocations(ApplicationInfo ai, int userId) {
Calin Juravle99dd37b2017-02-22 19:05:06 -0800841 this(ai.packageName, ai.sourceDir, ai.splitSourceDirs);
842 mergeAppDataDirs(ai.dataDir, userId);
843 }
844 public PackageCodeLocations(String packageName, String baseCodePath,
845 String[] splitCodePaths) {
846 mPackageName = packageName;
Calin Juravleb8976d82016-12-16 16:22:00 +0000847 mSplitCodePaths = new HashSet<>();
Calin Juravle99dd37b2017-02-22 19:05:06 -0800848 mAppDataDirs = new HashMap<>();
849 updateCodeLocation(baseCodePath, splitCodePaths);
850 }
851
852 public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) {
853 mBaseCodePath = baseCodePath;
854 mSplitCodePaths.clear();
855 if (splitCodePaths != null) {
856 for (String split : splitCodePaths) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000857 mSplitCodePaths.add(split);
858 }
859 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000860 }
861
Calin Juravle99dd37b2017-02-22 19:05:06 -0800862 public void mergeAppDataDirs(String dataDir, int userId) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000863 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
Calin Juravle99dd37b2017-02-22 19:05:06 -0800864 dataDirs.add(dataDir);
Calin Juravleb8976d82016-12-16 16:22:00 +0000865 }
866
867 public int searchDex(String dexPath, int userId) {
868 // First check that this package is installed or active for the given user.
Calin Juravle6304ec32017-03-20 23:46:44 -0700869 // A missing data dir means the package is not installed.
Calin Juravleb8976d82016-12-16 16:22:00 +0000870 Set<String> userDataDirs = mAppDataDirs.get(userId);
871 if (userDataDirs == null) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000872 return DEX_SEARCH_NOT_FOUND;
873 }
874
875 if (mBaseCodePath.equals(dexPath)) {
876 return DEX_SEARCH_FOUND_PRIMARY;
877 }
878 if (mSplitCodePaths.contains(dexPath)) {
879 return DEX_SEARCH_FOUND_SPLIT;
880 }
881 for (String dataDir : userDataDirs) {
882 if (dexPath.startsWith(dataDir)) {
883 return DEX_SEARCH_FOUND_SECONDARY;
884 }
885 }
Calin Juravlec0662052016-12-22 18:47:05 +0200886
Calin Juravleb8976d82016-12-16 16:22:00 +0000887 return DEX_SEARCH_NOT_FOUND;
888 }
889 }
890
891 /**
892 * Convenience class to store ownership search results.
893 */
894 private class DexSearchResult {
895 private String mOwningPackageName;
896 private int mOutcome;
897
898 public DexSearchResult(String owningPackageName, int outcome) {
899 this.mOwningPackageName = owningPackageName;
900 this.mOutcome = outcome;
901 }
902
903 @Override
904 public String toString() {
905 return mOwningPackageName + "-" + mOutcome;
906 }
907 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000908}