blob: 392d4d839c455296a2b9598a30458d8619f1ddbd [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;
Calin Juravleb8976d82016-12-16 16:22:00 +000033import android.util.Slog;
Victor Hsieh785d6182018-04-19 14:26:28 -070034import android.util.jar.StrictJarFile;
Calin Juravleb8976d82016-12-16 16:22:00 +000035
Calin Juravle1aa5f882017-01-25 01:05:50 -080036import com.android.internal.annotations.GuardedBy;
Victor Hsieh785d6182018-04-19 14:26:28 -070037import com.android.internal.util.ArrayUtils;
Calin Juravle1aa5f882017-01-25 01:05:50 -080038import com.android.server.pm.Installer;
39import com.android.server.pm.Installer.InstallerException;
Calin Juravlec22c30e2017-01-16 19:18:48 -080040import com.android.server.pm.PackageDexOptimizer;
Calin Juravle3d2af7f2017-04-19 19:56:21 -070041import com.android.server.pm.PackageManagerService;
Calin Juravleb8976d82016-12-16 16:22:00 +000042import com.android.server.pm.PackageManagerServiceUtils;
Calin Juravlec22c30e2017-01-16 19:18:48 -080043import com.android.server.pm.PackageManagerServiceCompilerMapping;
Calin Juravleb8976d82016-12-16 16:22:00 +000044
45import java.io.File;
46import java.io.IOException;
Victor Hsieh785d6182018-04-19 14:26:28 -070047import java.util.Arrays;
Calin Juravle52a452c2017-08-04 01:42:17 -070048import java.util.Collection;
49import java.util.Collections;
Victor Hsieh785d6182018-04-19 14:26:28 -070050import java.util.Iterator;
Calin Juravleb8976d82016-12-16 16:22:00 +000051import java.util.List;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.Map;
55import java.util.Set;
Victor Hsieh785d6182018-04-19 14:26:28 -070056import java.util.zip.ZipEntry;
Calin Juravleb8976d82016-12-16 16:22:00 +000057
Calin Juravle3d2af7f2017-04-19 19:56:21 -070058import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
Calin Juravlec22c30e2017-01-16 19:18:48 -080059import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
60import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
61
Calin Juravleb8976d82016-12-16 16:22:00 +000062/**
63 * This class keeps track of how dex files are used.
64 * Every time it gets a notification about a dex file being loaded it tracks
65 * its owning package and records it in PackageDexUsage (package-dex-usage.list).
66 *
67 * TODO(calin): Extract related dexopt functionality from PackageManagerService
68 * into this class.
69 */
70public class DexManager {
71 private static final String TAG = "DexManager";
72
Victor Hsieh785d6182018-04-19 14:26:28 -070073 private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
74 private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST =
75 "pm.dexopt.priv-apps-oob-list";
76
Calin Juravleb8976d82016-12-16 16:22:00 +000077 private static final boolean DEBUG = false;
78
Victor Hsieh785d6182018-04-19 14:26:28 -070079 private final Context mContext;
80
Calin Juravleb8976d82016-12-16 16:22:00 +000081 // Maps package name to code locations.
82 // It caches the code locations for the installed packages. This allows for
83 // faster lookups (no locks) when finding what package owns the dex file.
Calin Juravle1fade2f2017-05-02 18:50:56 -070084 @GuardedBy("mPackageCodeLocationsCache")
Calin Juravleb8976d82016-12-16 16:22:00 +000085 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;
86
87 // PackageDexUsage handles the actual I/O operations. It is responsible to
88 // encode and save the dex usage data.
89 private final PackageDexUsage mPackageDexUsage;
90
Calin Juravlec22c30e2017-01-16 19:18:48 -080091 private final IPackageManager mPackageManager;
92 private final PackageDexOptimizer mPackageDexOptimizer;
Calin Juravle1aa5f882017-01-25 01:05:50 -080093 private final Object mInstallLock;
94 @GuardedBy("mInstallLock")
95 private final Installer mInstaller;
Alan Stokesa0023602017-10-16 12:31:44 +010096 private final Listener mListener;
Calin Juravlec22c30e2017-01-16 19:18:48 -080097
Calin Juravleb8976d82016-12-16 16:22:00 +000098 // Possible outcomes of a dex search.
99 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
100 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk
101 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk
102 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex
103
Calin Juravle3b74c412017-08-03 19:48:37 -0700104 /**
105 * We do not record packages that have no secondary dex files or that are not used by other
106 * apps. This is an optimization to reduce the amount of data that needs to be written to
107 * disk (apps will not usually be shared so this trims quite a bit the number we record).
108 *
109 * To make this behaviour transparent to the callers which need use information on packages,
110 * DexManager will return this DEFAULT instance from
111 * {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and
112 * is marked as not being used by other apps. This reflects the intended behaviour when we don't
113 * find the package in the underlying data file.
114 */
115 private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
116
Alan Stokesa0023602017-10-16 12:31:44 +0100117 public interface Listener {
118 /**
119 * Invoked just before the secondary dex file {@code dexPath} for the specified application
120 * is reconciled.
121 */
122 void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
123 String dexPath, int storageFlags);
124 }
125
Victor Hsieh785d6182018-04-19 14:26:28 -0700126 public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
Alan Stokesa0023602017-10-16 12:31:44 +0100127 Installer installer, Object installLock, Listener listener) {
Victor Hsieh785d6182018-04-19 14:26:28 -0700128 mContext = context;
Calin Juravleb8976d82016-12-16 16:22:00 +0000129 mPackageCodeLocationsCache = new HashMap<>();
130 mPackageDexUsage = new PackageDexUsage();
Calin Juravlec22c30e2017-01-16 19:18:48 -0800131 mPackageManager = pms;
132 mPackageDexOptimizer = pdo;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800133 mInstaller = installer;
134 mInstallLock = installLock;
Alan Stokesa0023602017-10-16 12:31:44 +0100135 mListener = listener;
Calin Juravleb8976d82016-12-16 16:22:00 +0000136 }
137
Victor Hsieh785d6182018-04-19 14:26:28 -0700138 public void systemReady() {
139 registerSettingObserver();
140 }
141
Calin Juravleb8976d82016-12-16 16:22:00 +0000142 /**
143 * Notify about dex files loads.
144 * Note that this method is invoked when apps load dex files and it should
145 * return as fast as possible.
146 *
Calin Juravleadbadd52017-03-28 18:19:15 -0700147 * @param loadingAppInfo the package performing the load
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700148 * @param classLoadersNames the names of the class loaders present in the loading chain. The
149 * list encodes the class loader chain in the natural order. The first class loader has
150 * the second one as its parent and so on. The dex files present in the class path of the
151 * first class loader will be recorded in the usage file.
152 * @param classPaths the class paths corresponding to the class loaders names from
153 * {@param classLoadersNames}. The the first element corresponds to the first class loader
154 * and so on. A classpath is represented as a list of dex files separated by
155 * {@code File.pathSeparator}.
156 * The dex files found in the first class path will be recorded in the usage file.
Calin Juravleb8976d82016-12-16 16:22:00 +0000157 * @param loaderIsa the ISA of the app loading the dex files
158 * @param loaderUserId the user id which runs the code loading the dex files
159 */
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700160 public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames,
161 List<String> classPaths, String loaderIsa, int loaderUserId) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000162 try {
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700163 notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
164 loaderUserId);
Calin Juravleb8976d82016-12-16 16:22:00 +0000165 } catch (Exception e) {
166 Slog.w(TAG, "Exception while notifying dex load for package " +
167 loadingAppInfo.packageName, e);
168 }
169 }
170
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700171 private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
172 List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
173 int loaderUserId) {
174 if (classLoaderNames.size() != classPaths.size()) {
175 Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
176 return;
177 }
178 if (classLoaderNames.isEmpty()) {
179 Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
180 return;
181 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000182 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700183 Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
Calin Juravleb8976d82016-12-16 16:22:00 +0000184 loaderIsa + "?");
185 return;
186 }
187
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700188 // The classpath is represented as a list of dex files separated by File.pathSeparator.
189 String[] dexPathsToRegister = classPaths.get(0).split(File.pathSeparator);
190
191 // Encode the class loader contexts for the dexPathsToRegister.
192 String[] classLoaderContexts = DexoptUtils.processContextForDexLoad(
193 classLoaderNames, classPaths);
194
195 int dexPathIndex = 0;
196 for (String dexPath : dexPathsToRegister) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000197 // Find the owning package name.
198 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
199
200 if (DEBUG) {
201 Slog.i(TAG, loadingAppInfo.packageName
202 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
203 }
204
205 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
206 // TODO(calin): extend isUsedByOtherApps check to detect the cases where
207 // different apps share the same runtime. In that case we should not mark the dex
208 // file as isUsedByOtherApps. Currently this is a safe approximation.
209 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals(
210 searchResult.mOwningPackageName);
211 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
212 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;
213
214 if (primaryOrSplit && !isUsedByOtherApps) {
215 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
216 // do not record it. This case does not bring any new usable information
217 // and can be safely skipped.
218 continue;
219 }
220
221 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
222 // or UsedBytOtherApps), record will return true and we trigger an async write
223 // to disk to make sure we don't loose the data in case of a reboot.
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700224
225 // A null classLoaderContexts means that there are unsupported class loaders in the
226 // chain.
227 String classLoaderContext = classLoaderContexts == null
228 ? PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT
229 : classLoaderContexts[dexPathIndex];
Calin Juravleb8976d82016-12-16 16:22:00 +0000230 if (mPackageDexUsage.record(searchResult.mOwningPackageName,
Calin Juravle535a4752017-02-03 16:55:49 -0800231 dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700232 loadingAppInfo.packageName, classLoaderContext)) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000233 mPackageDexUsage.maybeWriteAsync();
234 }
235 } else {
Calin Juravleb8976d82016-12-16 16:22:00 +0000236 // If we can't find the owner of the dex we simply do not track it. The impact is
237 // that the dex file will not be considered for offline optimizations.
Calin Juravleb8976d82016-12-16 16:22:00 +0000238 if (DEBUG) {
239 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
240 }
241 }
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700242 dexPathIndex++;
Calin Juravleb8976d82016-12-16 16:22:00 +0000243 }
244 }
245
246 /**
247 * Read the dex usage from disk and populate the code cache locations.
248 * @param existingPackages a map containing information about what packages
249 * are available to what users. Only packages in this list will be
250 * recognized during notifyDexLoad().
251 */
252 public void load(Map<Integer, List<PackageInfo>> existingPackages) {
253 try {
254 loadInternal(existingPackages);
255 } catch (Exception e) {
256 mPackageDexUsage.clear();
257 Slog.w(TAG, "Exception while loading package dex usage. " +
258 "Starting with a fresh state.", e);
259 }
260 }
261
Calin Juravle99dd37b2017-02-22 19:05:06 -0800262 /**
263 * Notifies that a new package was installed for {@code userId}.
264 * {@code userId} must not be {@code UserHandle.USER_ALL}.
265 *
266 * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}.
267 */
268 public void notifyPackageInstalled(PackageInfo pi, int userId) {
269 if (userId == UserHandle.USER_ALL) {
270 throw new IllegalArgumentException(
271 "notifyPackageInstalled called with USER_ALL");
272 }
Calin Juravleadbadd52017-03-28 18:19:15 -0700273 cachePackageInfo(pi, userId);
Calin Juravle0d4b8f82017-01-23 23:34:25 -0800274 }
275
Calin Juravle99dd37b2017-02-22 19:05:06 -0800276 /**
277 * Notifies that package {@code packageName} was updated.
278 * This will clear the UsedByOtherApps mark if it exists.
279 */
280 public void notifyPackageUpdated(String packageName, String baseCodePath,
281 String[] splitCodePaths) {
282 cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1);
283 // In case there was an update, write the package use info to disk async.
284 // Note that we do the writing here and not in PackageDexUsage in order to be
285 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
Calin Juravle1fade2f2017-05-02 18:50:56 -0700286 // multiple updates in PackageDexUsage before writing it).
Calin Juravle99dd37b2017-02-22 19:05:06 -0800287 if (mPackageDexUsage.clearUsedByOtherApps(packageName)) {
288 mPackageDexUsage.maybeWriteAsync();
289 }
290 }
291
292 /**
293 * Notifies that the user {@code userId} data for package {@code packageName}
294 * was destroyed. This will remove all usage info associated with the package
295 * for the given user.
296 * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case
297 * all usage information for the package will be removed.
298 */
299 public void notifyPackageDataDestroyed(String packageName, int userId) {
300 boolean updated = userId == UserHandle.USER_ALL
301 ? mPackageDexUsage.removePackage(packageName)
302 : mPackageDexUsage.removeUserPackage(packageName, userId);
303 // In case there was an update, write the package use info to disk async.
304 // Note that we do the writing here and not in PackageDexUsage in order to be
305 // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
Calin Juravle1fade2f2017-05-02 18:50:56 -0700306 // multiple updates in PackageDexUsage before writing it).
Calin Juravle99dd37b2017-02-22 19:05:06 -0800307 if (updated) {
308 mPackageDexUsage.maybeWriteAsync();
309 }
310 }
311
Calin Juravleadbadd52017-03-28 18:19:15 -0700312 /**
313 * Caches the code location from the given package info.
314 */
315 private void cachePackageInfo(PackageInfo pi, int userId) {
316 ApplicationInfo ai = pi.applicationInfo;
317 String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir,
318 ai.credentialProtectedDataDir};
319 cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs,
320 dataDirs, userId);
321 }
322
323 private void cachePackageCodeLocation(String packageName, String baseCodePath,
324 String[] splitCodePaths, String[] dataDirs, int userId) {
Calin Juravle1fade2f2017-05-02 18:50:56 -0700325 synchronized (mPackageCodeLocationsCache) {
326 PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName,
327 new PackageCodeLocations(packageName, baseCodePath, splitCodePaths));
328 // TODO(calin): We are forced to extend the scope of this synchronization because
329 // the values of the cache (PackageCodeLocations) are updated in place.
330 // Make PackageCodeLocations immutable to simplify the synchronization reasoning.
331 pcl.updateCodeLocation(baseCodePath, splitCodePaths);
332 if (dataDirs != null) {
333 for (String dataDir : dataDirs) {
334 // The set of data dirs includes deviceProtectedDataDir and
335 // credentialProtectedDataDir which might be null for shared
336 // libraries. Currently we don't track these but be lenient
337 // and check in case we ever decide to store their usage data.
338 if (dataDir != null) {
339 pcl.mergeAppDataDirs(dataDir, userId);
340 }
Calin Juravleadbadd52017-03-28 18:19:15 -0700341 }
342 }
Calin Juravle0d4b8f82017-01-23 23:34:25 -0800343 }
344 }
345
Calin Juravleb8976d82016-12-16 16:22:00 +0000346 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
347 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
Calin Juravle52a452c2017-08-04 01:42:17 -0700348 Map<String, Set<String>> packageToCodePaths = new HashMap<>();
349
Calin Juravleb8976d82016-12-16 16:22:00 +0000350 // Cache the code locations for the installed packages. This allows for
351 // faster lookups (no locks) when finding what package owns the dex file.
352 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
353 List<PackageInfo> packageInfoList = entry.getValue();
354 int userId = entry.getKey();
355 for (PackageInfo pi : packageInfoList) {
356 // Cache the code locations.
Calin Juravleadbadd52017-03-28 18:19:15 -0700357 cachePackageInfo(pi, userId);
Calin Juravle0d4b8f82017-01-23 23:34:25 -0800358
Calin Juravle52a452c2017-08-04 01:42:17 -0700359 // Cache two maps:
360 // - from package name to the set of user ids who installed the package.
361 // - from package name to the set of code paths.
Calin Juravleb8976d82016-12-16 16:22:00 +0000362 // We will use it to sync the data and remove obsolete entries from
363 // mPackageDexUsage.
364 Set<Integer> users = putIfAbsent(
365 packageToUsersMap, pi.packageName, new HashSet<>());
366 users.add(userId);
Calin Juravle52a452c2017-08-04 01:42:17 -0700367
368 Set<String> codePaths = putIfAbsent(
369 packageToCodePaths, pi.packageName, new HashSet<>());
370 codePaths.add(pi.applicationInfo.sourceDir);
371 if (pi.applicationInfo.splitSourceDirs != null) {
372 Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs);
373 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000374 }
375 }
376
377 mPackageDexUsage.read();
Calin Juravle52a452c2017-08-04 01:42:17 -0700378 mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths);
Calin Juravleb8976d82016-12-16 16:22:00 +0000379 }
380
381 /**
382 * Get the package dex usage for the given package name.
Calin Juravle3b74c412017-08-03 19:48:37 -0700383 * If there is no usage info the method will return a default {@code PackageUseInfo} with
384 * no data about secondary dex files and marked as not being used by other apps.
385 *
386 * Note that no use info means the package was not used or it was used but not by other apps.
387 * Also, note that right now we might prune packages which are not used by other apps.
388 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
389 * to access the package use.
Calin Juravleb8976d82016-12-16 16:22:00 +0000390 */
Calin Juravle3b74c412017-08-03 19:48:37 -0700391 public PackageUseInfo getPackageUseInfoOrDefault(String packageName) {
392 PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName);
393 return useInfo == null ? DEFAULT_USE_INFO : useInfo;
394 }
395
396 /**
397 * Return whether or not the manager has usage information on the give package.
398 *
399 * Note that no use info means the package was not used or it was used but not by other apps.
400 * Also, note that right now we might prune packages which are not used by other apps.
401 * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
402 * to access the package use.
403 */
404 /*package*/ boolean hasInfoOnPackage(String packageName) {
405 return mPackageDexUsage.getPackageUseInfo(packageName) != null;
Calin Juravleb8976d82016-12-16 16:22:00 +0000406 }
407
408 /**
Calin Juravle1d0e83d2017-07-17 15:12:01 -0700409 * Perform dexopt on with the given {@code options} on the secondary dex files.
Calin Juravlec22c30e2017-01-16 19:18:48 -0800410 * @return true if all secondary dex files were processed successfully (compiled or skipped
411 * because they don't need to be compiled)..
412 */
Calin Juravle1d0e83d2017-07-17 15:12:01 -0700413 public boolean dexoptSecondaryDex(DexoptOptions options) {
Calin Juravlec22c30e2017-01-16 19:18:48 -0800414 // Select the dex optimizer based on the force parameter.
415 // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
416 // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
417 // passing the force flag through the multitude of layers.
418 // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
419 // allocate an object here.
Calin Juravle1d0e83d2017-07-17 15:12:01 -0700420 PackageDexOptimizer pdo = options.isForce()
Calin Juravlec22c30e2017-01-16 19:18:48 -0800421 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
422 : mPackageDexOptimizer;
Calin Juravle1d0e83d2017-07-17 15:12:01 -0700423 String packageName = options.getPackageName();
Calin Juravle3b74c412017-08-03 19:48:37 -0700424 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
Alan Stokesa0023602017-10-16 12:31:44 +0100425 if (useInfo.getDexUseInfoMap().isEmpty()) {
Calin Juravlec22c30e2017-01-16 19:18:48 -0800426 if (DEBUG) {
427 Slog.d(TAG, "No secondary dex use for package:" + packageName);
428 }
429 // Nothing to compile, return true.
430 return true;
431 }
432 boolean success = true;
433 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
434 String dexPath = entry.getKey();
435 DexUseInfo dexUseInfo = entry.getValue();
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700436
437 PackageInfo pkg;
Calin Juravlec22c30e2017-01-16 19:18:48 -0800438 try {
439 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
440 dexUseInfo.getOwnerUserId());
441 } catch (RemoteException e) {
442 throw new AssertionError(e);
443 }
444 // It may be that the package gets uninstalled while we try to compile its
445 // secondary dex files. If that's the case, just ignore.
446 // Note that we don't break the entire loop because the package might still be
447 // installed for other users.
448 if (pkg == null) {
449 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
450 + " for user " + dexUseInfo.getOwnerUserId());
Calin Juravle1aa5f882017-01-25 01:05:50 -0800451 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
Calin Juravlec22c30e2017-01-16 19:18:48 -0800452 continue;
453 }
Calin Juravleadbadd52017-03-28 18:19:15 -0700454
Calin Juravlec22c30e2017-01-16 19:18:48 -0800455 int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700456 dexUseInfo, options);
Calin Juravlec22c30e2017-01-16 19:18:48 -0800457 success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
458 }
459 return success;
460 }
461
462 /**
Calin Juravle1aa5f882017-01-25 01:05:50 -0800463 * Reconcile the information we have about the secondary dex files belonging to
464 * {@code packagName} and the actual dex files. For all dex files that were
465 * deleted, update the internal records and delete any generated oat files.
466 */
467 public void reconcileSecondaryDexFiles(String packageName) {
Calin Juravle3b74c412017-08-03 19:48:37 -0700468 PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
Alan Stokesa0023602017-10-16 12:31:44 +0100469 if (useInfo.getDexUseInfoMap().isEmpty()) {
Calin Juravle1aa5f882017-01-25 01:05:50 -0800470 if (DEBUG) {
471 Slog.d(TAG, "No secondary dex use for package:" + packageName);
472 }
473 // Nothing to reconcile.
474 return;
475 }
Calin Juravleadbadd52017-03-28 18:19:15 -0700476
Calin Juravleb1097412017-01-26 18:53:23 -0800477 boolean updated = false;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800478 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
479 String dexPath = entry.getKey();
480 DexUseInfo dexUseInfo = entry.getValue();
481 PackageInfo pkg = null;
482 try {
483 // Note that we look for the package in the PackageManager just to be able
484 // to get back the real app uid and its storage kind. These are only used
485 // to perform extra validation in installd.
486 // TODO(calin): maybe a bit overkill.
487 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
488 dexUseInfo.getOwnerUserId());
489 } catch (RemoteException ignore) {
490 // Can't happen, DexManager is local.
491 }
492 if (pkg == null) {
493 // It may be that the package was uninstalled while we process the secondary
494 // dex files.
495 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
496 + " for user " + dexUseInfo.getOwnerUserId());
497 // Update the usage and continue, another user might still have the package.
Calin Juravleb1097412017-01-26 18:53:23 -0800498 updated = mPackageDexUsage.removeUserPackage(
499 packageName, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800500 continue;
501 }
502 ApplicationInfo info = pkg.applicationInfo;
503 int flags = 0;
Calin Juravleadbadd52017-03-28 18:19:15 -0700504 if (info.deviceProtectedDataDir != null &&
505 FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
Calin Juravle1aa5f882017-01-25 01:05:50 -0800506 flags |= StorageManager.FLAG_STORAGE_DE;
Calin Juravleadbadd52017-03-28 18:19:15 -0700507 } else if (info.credentialProtectedDataDir!= null &&
508 FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
Calin Juravle1aa5f882017-01-25 01:05:50 -0800509 flags |= StorageManager.FLAG_STORAGE_CE;
510 } else {
Calin Juravleadbadd52017-03-28 18:19:15 -0700511 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
512 updated = mPackageDexUsage.removeDexFile(
513 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800514 continue;
515 }
516
Alan Stokesa0023602017-10-16 12:31:44 +0100517 if (mListener != null) {
518 mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
519 }
520
Calin Juravle1aa5f882017-01-25 01:05:50 -0800521 boolean dexStillExists = true;
522 synchronized(mInstallLock) {
523 try {
524 String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
525 dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
Alan Stokesa0023602017-10-16 12:31:44 +0100526 info.uid, isas, info.volumeUuid, flags);
Calin Juravle1aa5f882017-01-25 01:05:50 -0800527 } catch (InstallerException e) {
528 Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
529 " : " + e.getMessage());
530 }
531 }
532 if (!dexStillExists) {
Calin Juravleb1097412017-01-26 18:53:23 -0800533 updated = mPackageDexUsage.removeDexFile(
534 packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
Calin Juravle1aa5f882017-01-25 01:05:50 -0800535 }
Calin Juravleb1097412017-01-26 18:53:23 -0800536
Calin Juravle1aa5f882017-01-25 01:05:50 -0800537 }
Calin Juravleb1097412017-01-26 18:53:23 -0800538 if (updated) {
539 mPackageDexUsage.maybeWriteAsync();
Calin Juravle1aa5f882017-01-25 01:05:50 -0800540 }
541 }
542
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700543 // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
544 // compilation happening here will use a pessimistic context.
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700545 public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
546 boolean isUsedByOtherApps, int userId) {
547 // Find the owning package record.
548 DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
549
550 if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) {
551 return new RegisterDexModuleResult(false, "Package not found");
552 }
553 if (!info.packageName.equals(searchResult.mOwningPackageName)) {
554 return new RegisterDexModuleResult(false, "Dex path does not belong to package");
555 }
556 if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
557 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) {
558 return new RegisterDexModuleResult(false, "Main apks cannot be registered");
559 }
560
561 // We found the package. Now record the usage for all declared ISAs.
562 boolean update = false;
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700563 for (String isa : getAppDexInstructionSets(info)) {
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700564 boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
Calin Juravle535a4752017-02-03 16:55:49 -0800565 dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false,
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700566 searchResult.mOwningPackageName,
567 PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT);
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700568 update |= newUpdate;
569 }
570 if (update) {
571 mPackageDexUsage.maybeWriteAsync();
572 }
573
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700574 DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName)
575 .getDexUseInfoMap().get(dexPath);
576
Calin Juravle4bc8f4d2018-02-12 12:00:44 -0800577 // Try to optimize the package according to the install reason.
578 DexoptOptions options = new DexoptOptions(info.packageName,
579 PackageManagerService.REASON_INSTALL, /*flags*/0);
Calin Juravlef1ff36f2017-07-22 12:33:41 -0700580
581 int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo,
582 options);
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700583
584 // If we fail to optimize the package log an error but don't propagate the error
585 // back to the app. The app cannot do much about it and the background job
586 // will rety again when it executes.
587 // TODO(calin): there might be some value to return the error here but it may
588 // cause red herrings since that doesn't mean the app cannot use the module.
589 if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
590 Slog.e(TAG, "Failed to optimize dex module " + dexPath);
591 }
592 return new RegisterDexModuleResult(true, "Dex module registered successfully");
593 }
594
Calin Juravle1aa5f882017-01-25 01:05:50 -0800595 /**
Calin Juravle51f521c2017-01-25 18:00:05 -0800596 * Return all packages that contain records of secondary dex files.
597 */
598 public Set<String> getAllPackagesWithSecondaryDexFiles() {
599 return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
Calin Juravleb8976d82016-12-16 16:22:00 +0000600 }
601
602 /**
Calin Juravleb8976d82016-12-16 16:22:00 +0000603 * Retrieves the package which owns the given dexPath.
604 */
605 private DexSearchResult getDexPackage(
606 ApplicationInfo loadingAppInfo, String dexPath, int userId) {
607 // Ignore framework code.
608 // TODO(calin): is there a better way to detect it?
609 if (dexPath.startsWith("/system/framework/")) {
Calin Juravle2dfc1b32017-03-10 18:24:33 -0800610 return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
Calin Juravleb8976d82016-12-16 16:22:00 +0000611 }
612
613 // First, check if the package which loads the dex file actually owns it.
614 // Most of the time this will be true and we can return early.
615 PackageCodeLocations loadingPackageCodeLocations =
616 new PackageCodeLocations(loadingAppInfo, userId);
617 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
618 if (outcome != DEX_SEARCH_NOT_FOUND) {
619 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
620 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
621 }
622
623 // The loadingPackage does not own the dex file.
624 // Perform a reverse look-up in the cache to detect if any package has ownership.
625 // Note that we can have false negatives if the cache falls out of date.
Calin Juravle1fade2f2017-05-02 18:50:56 -0700626 synchronized (mPackageCodeLocationsCache) {
627 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
628 outcome = pcl.searchDex(dexPath, userId);
629 if (outcome != DEX_SEARCH_NOT_FOUND) {
630 return new DexSearchResult(pcl.mPackageName, outcome);
631 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000632 }
633 }
634
Calin Juravle6304ec32017-03-20 23:46:44 -0700635 if (DEBUG) {
636 // TODO(calin): Consider checking for /data/data symlink.
637 // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
638 // to load dex files through it.
639 try {
640 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
641 if (dexPathReal != dexPath) {
642 Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
643 dexPath + " dexPathReal=" + dexPathReal);
644 }
645 } catch (IOException e) {
646 // Ignore
647 }
648 }
649 // Cache miss. The cache is updated during installs and uninstalls,
650 // so if we get here we're pretty sure the dex path does not exist.
Calin Juravleb8976d82016-12-16 16:22:00 +0000651 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
652 }
653
654 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
655 V existingValue = map.putIfAbsent(key, newValue);
656 return existingValue == null ? newValue : existingValue;
657 }
658
Calin Juravle14876bd2017-07-28 16:13:35 -0700659 /**
Calin Juravle52a452c2017-08-04 01:42:17 -0700660 * Writes the in-memory package dex usage to disk right away.
Calin Juravle14876bd2017-07-28 16:13:35 -0700661 */
Calin Juravle52a452c2017-08-04 01:42:17 -0700662 public void writePackageDexUsageNow() {
Calin Juravle14876bd2017-07-28 16:13:35 -0700663 mPackageDexUsage.writeNow();
664 }
665
Victor Hsieh785d6182018-04-19 14:26:28 -0700666 private void registerSettingObserver() {
667 final ContentResolver resolver = mContext.getContentResolver();
668
669 // This observer provides a one directional mapping from Global.PRIV_APP_OOB_ENABLED to
670 // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
671 // it is done.
672 ContentObserver privAppOobObserver = new ContentObserver(null) {
673 @Override
674 public void onChange(boolean selfChange) {
675 int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
676 SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
677 oobEnabled == 1 ? "true" : "false");
678 }
679 };
680 resolver.registerContentObserver(
681 Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
682 UserHandle.USER_SYSTEM);
683 // At boot, restore the value from the setting, which persists across reboot.
684 privAppOobObserver.onChange(true);
685
686 ContentObserver privAppOobListObserver = new ContentObserver(null) {
687 @Override
688 public void onChange(boolean selfChange) {
689 String oobList = Global.getString(resolver, Global.PRIV_APP_OOB_LIST);
690 if (oobList == null) {
691 oobList = "ALL";
692 }
693 SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, oobList);
694 }
695 };
696 resolver.registerContentObserver(
697 Global.getUriFor(Global.PRIV_APP_OOB_LIST), false, privAppOobListObserver,
698 UserHandle.USER_SYSTEM);
699 // At boot, restore the value from the setting, which persists across reboot.
700 privAppOobListObserver.onChange(true);
701 }
702
703 /**
704 * Returns whether the given package is in the list of privilaged apps that should run out of
705 * box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that when
706 * the the OOB list is empty, all priv apps will run in OOB mode.
707 */
708 public static boolean isPackageSelectedToRunOob(String packageName) {
709 return isPackageSelectedToRunOob(Arrays.asList(packageName));
710 }
711
712 /**
713 * Returns whether any of the given packages are in the list of privilaged apps that should run
714 * out of box. This only makes sense if PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB is true. Note that
715 * when the the OOB list is empty, all priv apps will run in OOB mode.
716 */
717 public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
718 if (!SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
719 return false;
720 }
721 String oobListProperty = SystemProperties.get(
722 PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL");
723 if ("ALL".equals(oobListProperty)) {
724 return true;
725 }
726 for (String oobPkgName : oobListProperty.split(",")) {
727 if (packageNamesInSameProcess.contains(oobPkgName)) {
728 return true;
729 }
730 }
731 return false;
732 }
733
734 /**
735 * Generates package related log if the package has code stored in unexpected way.
736 */
737 public static void maybeLogUnexpectedPackageDetails(PackageParser.Package pkg) {
738 if (!Build.IS_DEBUGGABLE) {
739 return;
740 }
741
742 if (pkg.isPrivileged() && isPackageSelectedToRunOob(pkg.packageName)) {
743 logIfPackageHasUncompressedCode(pkg);
744 }
745 }
746
747 /**
748 * Generates log if the APKs in the given package have uncompressed dex file and so
749 * files that can be direclty mapped.
750 */
751 private static void logIfPackageHasUncompressedCode(PackageParser.Package pkg) {
752 logIfApkHasUncompressedCode(pkg.baseCodePath);
753 if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
754 for (int i = 0; i < pkg.splitCodePaths.length; i++) {
755 logIfApkHasUncompressedCode(pkg.splitCodePaths[i]);
756 }
757 }
758 }
759
760 /**
761 * Generates log if the archive located at {@code fileName} has uncompressed dex file and so
762 * files that can be direclty mapped.
763 */
764 private static void logIfApkHasUncompressedCode(String fileName) {
765 StrictJarFile jarFile = null;
766 try {
767 jarFile = new StrictJarFile(fileName,
768 false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
769 Iterator<ZipEntry> it = jarFile.iterator();
770 while (it.hasNext()) {
771 ZipEntry entry = it.next();
772 if (entry.getName().endsWith(".dex")) {
773 if (entry.getMethod() != ZipEntry.STORED) {
774 Slog.w(TAG, "APK " + fileName + " has compressed dex code " +
775 entry.getName());
776 } else if ((entry.getDataOffset() & 0x3) != 0) {
777 Slog.w(TAG, "APK " + fileName + " has unaligned dex code " +
778 entry.getName());
779 }
780 } else if (entry.getName().endsWith(".so")) {
781 if (entry.getMethod() != ZipEntry.STORED) {
782 Slog.w(TAG, "APK " + fileName + " has compressed native code " +
783 entry.getName());
784 } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
785 Slog.w(TAG, "APK " + fileName + " has unaligned native code " +
786 entry.getName());
787 }
788 }
789 }
790 } catch (IOException ignore) {
791 Slog.wtf(TAG, "Error when parsing APK " + fileName);
792 } finally {
793 try {
794 if (jarFile != null) {
795 jarFile.close();
796 }
797 } catch (IOException ignore) {}
798 }
799 }
800
Calin Juravle3d2af7f2017-04-19 19:56:21 -0700801 public static class RegisterDexModuleResult {
802 public RegisterDexModuleResult() {
803 this(false, null);
804 }
805
806 public RegisterDexModuleResult(boolean success, String message) {
807 this.success = success;
808 this.message = message;
809 }
810
811 public final boolean success;
812 public final String message;
813 }
814
Calin Juravleb8976d82016-12-16 16:22:00 +0000815 /**
816 * Convenience class to store the different locations where a package might
817 * own code.
818 */
819 private static class PackageCodeLocations {
820 private final String mPackageName;
Calin Juravle99dd37b2017-02-22 19:05:06 -0800821 private String mBaseCodePath;
Calin Juravleb8976d82016-12-16 16:22:00 +0000822 private final Set<String> mSplitCodePaths;
823 // Maps user id to the application private directory.
824 private final Map<Integer, Set<String>> mAppDataDirs;
825
826 public PackageCodeLocations(ApplicationInfo ai, int userId) {
Calin Juravle99dd37b2017-02-22 19:05:06 -0800827 this(ai.packageName, ai.sourceDir, ai.splitSourceDirs);
828 mergeAppDataDirs(ai.dataDir, userId);
829 }
830 public PackageCodeLocations(String packageName, String baseCodePath,
831 String[] splitCodePaths) {
832 mPackageName = packageName;
Calin Juravleb8976d82016-12-16 16:22:00 +0000833 mSplitCodePaths = new HashSet<>();
Calin Juravle99dd37b2017-02-22 19:05:06 -0800834 mAppDataDirs = new HashMap<>();
835 updateCodeLocation(baseCodePath, splitCodePaths);
836 }
837
838 public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) {
839 mBaseCodePath = baseCodePath;
840 mSplitCodePaths.clear();
841 if (splitCodePaths != null) {
842 for (String split : splitCodePaths) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000843 mSplitCodePaths.add(split);
844 }
845 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000846 }
847
Calin Juravle99dd37b2017-02-22 19:05:06 -0800848 public void mergeAppDataDirs(String dataDir, int userId) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000849 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
Calin Juravle99dd37b2017-02-22 19:05:06 -0800850 dataDirs.add(dataDir);
Calin Juravleb8976d82016-12-16 16:22:00 +0000851 }
852
853 public int searchDex(String dexPath, int userId) {
854 // First check that this package is installed or active for the given user.
Calin Juravle6304ec32017-03-20 23:46:44 -0700855 // A missing data dir means the package is not installed.
Calin Juravleb8976d82016-12-16 16:22:00 +0000856 Set<String> userDataDirs = mAppDataDirs.get(userId);
857 if (userDataDirs == null) {
Calin Juravleb8976d82016-12-16 16:22:00 +0000858 return DEX_SEARCH_NOT_FOUND;
859 }
860
861 if (mBaseCodePath.equals(dexPath)) {
862 return DEX_SEARCH_FOUND_PRIMARY;
863 }
864 if (mSplitCodePaths.contains(dexPath)) {
865 return DEX_SEARCH_FOUND_SPLIT;
866 }
867 for (String dataDir : userDataDirs) {
868 if (dexPath.startsWith(dataDir)) {
869 return DEX_SEARCH_FOUND_SECONDARY;
870 }
871 }
Calin Juravlec0662052016-12-22 18:47:05 +0200872
Calin Juravleb8976d82016-12-16 16:22:00 +0000873 return DEX_SEARCH_NOT_FOUND;
874 }
875 }
876
877 /**
878 * Convenience class to store ownership search results.
879 */
880 private class DexSearchResult {
881 private String mOwningPackageName;
882 private int mOutcome;
883
884 public DexSearchResult(String owningPackageName, int outcome) {
885 this.mOwningPackageName = owningPackageName;
886 this.mOutcome = outcome;
887 }
888
889 @Override
890 public String toString() {
891 return mOwningPackageName + "-" + mOutcome;
892 }
893 }
Calin Juravleb8976d82016-12-16 16:22:00 +0000894}