• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server.pm.dex;
18 
19 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
20 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
21 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
22 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
23 
24 import static java.util.function.Function.identity;
25 
26 import android.content.Context;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.IPackageManager;
29 import android.content.pm.PackageInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PackagePartitions;
32 import android.os.BatteryManager;
33 import android.os.FileUtils;
34 import android.os.PowerManager;
35 import android.os.RemoteException;
36 import android.os.SystemProperties;
37 import android.os.UserHandle;
38 import android.os.storage.StorageManager;
39 import android.util.Log;
40 import android.util.Slog;
41 import android.util.jar.StrictJarFile;
42 
43 import com.android.internal.annotations.GuardedBy;
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.server.pm.Installer;
46 import com.android.server.pm.Installer.InstallerException;
47 import com.android.server.pm.PackageDexOptimizer;
48 import com.android.server.pm.PackageManagerService;
49 import com.android.server.pm.PackageManagerServiceUtils;
50 
51 import dalvik.system.VMRuntime;
52 
53 import java.io.File;
54 import java.io.IOException;
55 import java.nio.file.Files;
56 import java.nio.file.Paths;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.HashMap;
62 import java.util.HashSet;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.zip.ZipEntry;
68 
69 /**
70  * This class keeps track of how dex files are used.
71  * Every time it gets a notification about a dex file being loaded it tracks
72  * its owning package and records it in PackageDexUsage (package-dex-usage.list).
73  *
74  * TODO(calin): Extract related dexopt functionality from PackageManagerService
75  * into this class.
76  */
77 public class DexManager {
78     private static final String TAG = "DexManager";
79     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
80 
81     private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
82     private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST =
83             "pm.dexopt.priv-apps-oob-list";
84 
85     // System server cannot load executable code outside system partitions.
86     // However it can load verification data - thus we pick the "verify" compiler filter.
87     private static final String SYSTEM_SERVER_COMPILER_FILTER = "verify";
88 
89     // The suffix we add to the package name when the loading happens in an isolated process.
90     // Note that the double dot creates and "invalid" package name which makes it clear that this
91     // is an artificially constructed name.
92     private static final String ISOLATED_PROCESS_PACKAGE_SUFFIX = "..isolated";
93 
94     private final Context mContext;
95 
96     // Maps package name to code locations.
97     // It caches the code locations for the installed packages. This allows for
98     // faster lookups (no locks) when finding what package owns the dex file.
99     @GuardedBy("mPackageCodeLocationsCache")
100     private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;
101 
102     // PackageDexUsage handles the actual I/O operations. It is responsible to
103     // encode and save the dex usage data.
104     private final PackageDexUsage mPackageDexUsage;
105 
106     // DynamicCodeLogger handles recording of dynamic code loading - which is similar to
107     // PackageDexUsage but records a different aspect of the data.
108     // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't
109     // record class loaders or ISAs.)
110     private final DynamicCodeLogger mDynamicCodeLogger;
111 
112     private final IPackageManager mPackageManager;
113     private final PackageDexOptimizer mPackageDexOptimizer;
114     private final Object mInstallLock;
115     @GuardedBy("mInstallLock")
116     private final Installer mInstaller;
117 
118     private BatteryManager mBatteryManager = null;
119     private PowerManager mPowerManager = null;
120 
121     // An integer percentage value used to determine when the device is considered to be on low
122     // power for compilation purposes.
123     private final int mCriticalBatteryLevel;
124 
125     // Possible outcomes of a dex search.
126     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
127     private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
128     private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
129     private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
130 
DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock)131     public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
132             Installer installer, Object installLock) {
133         mContext = context;
134         mPackageCodeLocationsCache = new HashMap<>();
135         mPackageDexUsage = new PackageDexUsage();
136         mPackageManager = pms;
137         mPackageDexOptimizer = pdo;
138         mInstaller = installer;
139         mInstallLock = installLock;
140         mDynamicCodeLogger = new DynamicCodeLogger(pms, installer);
141 
142         // This is currently checked to handle tests that pass in a null context.
143         // TODO(b/174783329): Modify the tests to pass in a mocked Context, PowerManager,
144         //      and BatteryManager.
145         if (mContext != null) {
146             mPowerManager = mContext.getSystemService(PowerManager.class);
147 
148             if (mPowerManager == null) {
149                 Slog.wtf(TAG, "Power Manager is unavailable at time of Dex Manager start");
150             }
151 
152             mCriticalBatteryLevel = mContext.getResources().getInteger(
153                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
154         } else {
155             // This value will never be used as the Battery Manager null check will fail first.
156             mCriticalBatteryLevel = 0;
157         }
158     }
159 
getDynamicCodeLogger()160     public DynamicCodeLogger getDynamicCodeLogger() {
161         return mDynamicCodeLogger;
162     }
163 
164     /**
165      * Notify about dex files loads.
166      * Note that this method is invoked when apps load dex files and it should
167      * return as fast as possible.
168      *
169      * @param loadingAppInfo the package performing the load
170      * @param classLoaderContextMap a map from file paths to dex files that have been loaded to
171      *     the class loader context that was used to load them.
172      * @param loaderIsa the ISA of the app loading the dex files
173      * @param loaderUserId the user id which runs the code loading the dex files
174      * @param loaderIsIsolatedProcess whether or not the loading process is isolated.
175      */
notifyDexLoad(ApplicationInfo loadingAppInfo, Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId, boolean loaderIsIsolatedProcess)176     public void notifyDexLoad(ApplicationInfo loadingAppInfo,
177             Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId,
178             boolean loaderIsIsolatedProcess) {
179         try {
180             notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa,
181                     loaderUserId, loaderIsIsolatedProcess);
182         } catch (Exception e) {
183             Slog.w(TAG, "Exception while notifying dex load for package " +
184                     loadingAppInfo.packageName, e);
185         }
186     }
187 
188     @VisibleForTesting
notifyDexLoadInternal(ApplicationInfo loadingAppInfo, Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId, boolean loaderIsIsolatedProcess)189     /*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
190             Map<String, String> classLoaderContextMap, String loaderIsa,
191             int loaderUserId, boolean loaderIsIsolatedProcess) {
192         if (classLoaderContextMap == null) {
193             return;
194         }
195         if (classLoaderContextMap.isEmpty()) {
196             Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
197             return;
198         }
199         if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
200             Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet()
201                     + " in unsupported ISA: " + loaderIsa + "?");
202             return;
203         }
204 
205         // If this load is coming from an isolated process we need to be able to prevent profile
206         // based optimizations. This is because isolated processes are sandboxed and can only read
207         // world readable files, so they need world readable optimization files. An
208         // example of such a package is webview.
209         //
210         // In order to prevent profile optimization we pretend that the load is coming from a
211         // different package, and so we assign a artificial name to the loading package making it
212         // clear that it comes from an isolated process. This blends well with the entire
213         // usedByOthers logic without needing to special handle isolated process in all dexopt
214         // layers.
215         String loadingPackageAmendedName = loadingAppInfo.packageName;
216         if (loaderIsIsolatedProcess) {
217             loadingPackageAmendedName += ISOLATED_PROCESS_PACKAGE_SUFFIX;
218         }
219         for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) {
220             String dexPath = mapping.getKey();
221             // Find the owning package name.
222             DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
223 
224             if (DEBUG) {
225                 Slog.i(TAG, loadingPackageAmendedName
226                         + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
227             }
228 
229             if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
230                 // TODO(calin): extend isUsedByOtherApps check to detect the cases where
231                 // different apps share the same runtime. In that case we should not mark the dex
232                 // file as isUsedByOtherApps. Currently this is a safe approximation.
233                 boolean isUsedByOtherApps =
234                         !loadingPackageAmendedName.equals(searchResult.mOwningPackageName);
235                 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
236                         searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;
237 
238                 if (primaryOrSplit && !isUsedByOtherApps
239                         && !isPlatformPackage(searchResult.mOwningPackageName)) {
240                     // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
241                     // do not record it. This case does not bring any new usable information
242                     // and can be safely skipped.
243                     // Note this is just an optimization that makes things easier to read in the
244                     // package-dex-use file since we don't need to pollute it with redundant info.
245                     // However, we always record system server packages.
246                     continue;
247                 }
248 
249                 if (!primaryOrSplit) {
250                     // Record loading of a DEX file from an app data directory.
251                     mDynamicCodeLogger.recordDex(loaderUserId, dexPath,
252                             searchResult.mOwningPackageName, loadingAppInfo.packageName);
253                 }
254 
255                 String classLoaderContext = mapping.getValue();
256 
257                 // Overwrite the class loader context for system server (instead of merging it).
258                 // We expect system server jars to only change contexts in between OTAs and to
259                 // otherwise be stable.
260                 // Instead of implementing a complex clear-context logic post OTA, it is much
261                 // simpler to always override the context for system server. This way, the context
262                 // will always be up to date and we will avoid merging which could lead to the
263                 // the context being marked as variable and thus making dexopt non-optimal.
264                 boolean overwriteCLC = isPlatformPackage(searchResult.mOwningPackageName);
265 
266                 if (classLoaderContext != null
267                         && VMRuntime.isValidClassLoaderContext(classLoaderContext)) {
268                     // Record dex file usage. If the current usage is a new pattern (e.g. new
269                     // secondary, or UsedByOtherApps), record will return true and we trigger an
270                     // async write to disk to make sure we don't loose the data in case of a reboot.
271                     if (mPackageDexUsage.record(searchResult.mOwningPackageName,
272                             dexPath, loaderUserId, loaderIsa, primaryOrSplit,
273                             loadingPackageAmendedName, classLoaderContext, overwriteCLC)) {
274                         mPackageDexUsage.maybeWriteAsync();
275                     }
276                 }
277             } else {
278                 // If we can't find the owner of the dex we simply do not track it. The impact is
279                 // that the dex file will not be considered for offline optimizations.
280                 if (DEBUG) {
281                     Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
282                 }
283             }
284         }
285     }
286 
287     /**
288      * Check if the dexPath belongs to system server.
289      * System server can load code from different location, so we cast a wide-net here, and
290      * assume that if the paths is on any of the registered system partitions then it can be loaded
291      * by system server.
292      */
isSystemServerDexPathSupportedForOdex(String dexPath)293     private boolean isSystemServerDexPathSupportedForOdex(String dexPath) {
294         ArrayList<PackagePartitions.SystemPartition> partitions =
295                 PackagePartitions.getOrderedPartitions(identity());
296         // First check the apex partition as it's not part of the SystemPartitions.
297         if (dexPath.startsWith("/apex/")) {
298             return true;
299         }
300         for (int i = 0; i < partitions.size(); i++) {
301             if (partitions.get(i).containsPath(dexPath)) {
302                 return true;
303             }
304         }
305         return false;
306     }
307 
308     /**
309      * Read the dex usage from disk and populate the code cache locations.
310      * @param existingPackages a map containing information about what packages
311      *          are available to what users. Only packages in this list will be
312      *          recognized during notifyDexLoad().
313      */
load(Map<Integer, List<PackageInfo>> existingPackages)314     public void load(Map<Integer, List<PackageInfo>> existingPackages) {
315         try {
316             loadInternal(existingPackages);
317         } catch (Exception e) {
318             mPackageDexUsage.clear();
319             mDynamicCodeLogger.clear();
320             Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
321         }
322     }
323 
324     /**
325      * Notifies that a new package was installed for {@code userId}.
326      * {@code userId} must not be {@code UserHandle.USER_ALL}.
327      *
328      * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}.
329      */
notifyPackageInstalled(PackageInfo pi, int userId)330     public void notifyPackageInstalled(PackageInfo pi, int userId) {
331         if (userId == UserHandle.USER_ALL) {
332             throw new IllegalArgumentException(
333                 "notifyPackageInstalled called with USER_ALL");
334         }
335         cachePackageInfo(pi, userId);
336     }
337 
338     /**
339      * Notifies that package {@code packageName} was updated.
340      * This will clear the UsedByOtherApps mark if it exists.
341      */
notifyPackageUpdated(String packageName, String baseCodePath, String[] splitCodePaths)342     public void notifyPackageUpdated(String packageName, String baseCodePath,
343             String[] splitCodePaths) {
344         cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1);
345         // In case there was an update, write the package use info to disk async.
346         // Note that we do the writing here and not in PackageDexUsage in order to be
347         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
348         // multiple updates in PackageDexUsage before writing it).
349         if (mPackageDexUsage.clearUsedByOtherApps(packageName)) {
350             mPackageDexUsage.maybeWriteAsync();
351         }
352     }
353 
354     /**
355      * Notifies that the user {@code userId} data for package {@code packageName}
356      * was destroyed. This will remove all usage info associated with the package
357      * for the given user.
358      * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case
359      * all usage information for the package will be removed.
360      */
notifyPackageDataDestroyed(String packageName, int userId)361     public void notifyPackageDataDestroyed(String packageName, int userId) {
362         // In case there was an update, write the package use info to disk async.
363         // Note that we do the writing here and not in the lower level classes in order to be
364         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
365         // multiple updates in PackageDexUsage before writing it).
366         if (userId == UserHandle.USER_ALL) {
367             if (mPackageDexUsage.removePackage(packageName)) {
368                 mPackageDexUsage.maybeWriteAsync();
369             }
370             mDynamicCodeLogger.removePackage(packageName);
371         } else {
372             if (mPackageDexUsage.removeUserPackage(packageName, userId)) {
373                 mPackageDexUsage.maybeWriteAsync();
374             }
375             mDynamicCodeLogger.removeUserPackage(packageName, userId);
376         }
377     }
378 
379     /**
380      * Caches the code location from the given package info.
381      */
cachePackageInfo(PackageInfo pi, int userId)382     private void cachePackageInfo(PackageInfo pi, int userId) {
383         ApplicationInfo ai = pi.applicationInfo;
384         String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir,
385                 ai.credentialProtectedDataDir};
386         cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs,
387                 dataDirs, userId);
388     }
389 
cachePackageCodeLocation(String packageName, String baseCodePath, String[] splitCodePaths, String[] dataDirs, int userId)390     private void cachePackageCodeLocation(String packageName, String baseCodePath,
391             String[] splitCodePaths, String[] dataDirs, int userId) {
392         synchronized (mPackageCodeLocationsCache) {
393             PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName,
394                     new PackageCodeLocations(packageName, baseCodePath, splitCodePaths));
395             // TODO(calin): We are forced to extend the scope of this synchronization because
396             // the values of the cache (PackageCodeLocations) are updated in place.
397             // Make PackageCodeLocations immutable to simplify the synchronization reasoning.
398             pcl.updateCodeLocation(baseCodePath, splitCodePaths);
399             if (dataDirs != null) {
400                 for (String dataDir : dataDirs) {
401                     // The set of data dirs includes deviceProtectedDataDir and
402                     // credentialProtectedDataDir which might be null for shared
403                     // libraries. Currently we don't track these but be lenient
404                     // and check in case we ever decide to store their usage data.
405                     if (dataDir != null) {
406                         pcl.mergeAppDataDirs(dataDir, userId);
407                     }
408                 }
409             }
410         }
411     }
412 
loadInternal(Map<Integer, List<PackageInfo>> existingPackages)413     private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
414         Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
415         Map<String, Set<String>> packageToCodePaths = new HashMap<>();
416 
417         // Cache the code locations for the installed packages. This allows for
418         // faster lookups (no locks) when finding what package owns the dex file.
419         for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
420             List<PackageInfo> packageInfoList = entry.getValue();
421             int userId = entry.getKey();
422             for (PackageInfo pi : packageInfoList) {
423                 // Cache the code locations.
424                 cachePackageInfo(pi, userId);
425 
426                 // Cache two maps:
427                 //   - from package name to the set of user ids who installed the package.
428                 //   - from package name to the set of code paths.
429                 // We will use it to sync the data and remove obsolete entries from
430                 // mPackageDexUsage.
431                 Set<Integer> users = putIfAbsent(
432                         packageToUsersMap, pi.packageName, new HashSet<>());
433                 users.add(userId);
434 
435                 Set<String> codePaths = putIfAbsent(
436                     packageToCodePaths, pi.packageName, new HashSet<>());
437                 codePaths.add(pi.applicationInfo.sourceDir);
438                 if (pi.applicationInfo.splitSourceDirs != null) {
439                     Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs);
440                 }
441             }
442         }
443 
444         try {
445             mPackageDexUsage.read();
446             List<String> packagesToKeepDataAbout = new ArrayList<>();
447             mPackageDexUsage.syncData(
448                     packageToUsersMap, packageToCodePaths, packagesToKeepDataAbout);
449         } catch (Exception e) {
450             mPackageDexUsage.clear();
451             Slog.w(TAG, "Exception while loading package dex usage. "
452                     + "Starting with a fresh state.", e);
453         }
454 
455         try {
456             mDynamicCodeLogger.readAndSync(packageToUsersMap);
457         } catch (Exception e) {
458             mDynamicCodeLogger.clear();
459             Slog.w(TAG, "Exception while loading package dynamic code usage. "
460                     + "Starting with a fresh state.", e);
461         }
462     }
463 
464     /**
465      * Get the package dex usage for the given package name.
466      * If there is no usage info the method will return a default {@code PackageUseInfo} with
467      * no data about secondary dex files and marked as not being used by other apps.
468      *
469      * Note that no use info means the package was not used or it was used but not by other apps.
470      * Also, note that right now we might prune packages which are not used by other apps.
471      * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
472      * to access the package use.
473      */
getPackageUseInfoOrDefault(String packageName)474     public PackageUseInfo getPackageUseInfoOrDefault(String packageName) {
475         // We do not record packages that have no secondary dex files or that are not used by other
476         // apps. This is an optimization to reduce the amount of data that needs to be written to
477         // disk (apps will not usually be shared so this trims quite a bit the number we record).
478         //
479         // To make this behaviour transparent to the callers which need use information on packages,
480         // DexManager will return this DEFAULT instance from
481         // {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files
482         // and is marked as not being used by other apps. This reflects the intended behaviour when
483         // we don't find the package in the underlying data file.
484         PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName);
485         return useInfo == null ? new PackageUseInfo(packageName) : useInfo;
486     }
487 
488     /**
489      * Return whether or not the manager has usage information on the give package.
490      *
491      * Note that no use info means the package was not used or it was used but not by other apps.
492      * Also, note that right now we might prune packages which are not used by other apps.
493      * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
494      * to access the package use.
495      */
496     @VisibleForTesting
hasInfoOnPackage(String packageName)497     /*package*/ boolean hasInfoOnPackage(String packageName) {
498         return mPackageDexUsage.getPackageUseInfo(packageName) != null;
499     }
500 
501     /**
502      * Perform dexopt on with the given {@code options} on the secondary dex files.
503      * @return true if all secondary dex files were processed successfully (compiled or skipped
504      *         because they don't need to be compiled)..
505      */
dexoptSecondaryDex(DexoptOptions options)506     public boolean dexoptSecondaryDex(DexoptOptions options) {
507         if (isPlatformPackage(options.getPackageName())) {
508             // We could easily redirect to #dexoptSystemServer in this case. But there should be
509             // no-one calling this method directly for system server.
510             // As such we prefer to abort in this case.
511             Slog.wtf(TAG, "System server jars should be optimized with dexoptSystemServer");
512             return false;
513         }
514 
515         PackageDexOptimizer pdo = getPackageDexOptimizer(options);
516         String packageName = options.getPackageName();
517         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
518         if (useInfo.getDexUseInfoMap().isEmpty()) {
519             if (DEBUG) {
520                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
521             }
522             // Nothing to compile, return true.
523             return true;
524         }
525         boolean success = true;
526         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
527             String dexPath = entry.getKey();
528             DexUseInfo dexUseInfo = entry.getValue();
529 
530             PackageInfo pkg;
531             try {
532                 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
533                     dexUseInfo.getOwnerUserId());
534             } catch (RemoteException e) {
535                 throw new AssertionError(e);
536             }
537             // It may be that the package gets uninstalled while we try to compile its
538             // secondary dex files. If that's the case, just ignore.
539             // Note that we don't break the entire loop because the package might still be
540             // installed for other users.
541             if (pkg == null) {
542                 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
543                         + " for user " + dexUseInfo.getOwnerUserId());
544                 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
545                 continue;
546             }
547 
548             int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
549                     dexUseInfo, options);
550             success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
551         }
552         return success;
553     }
554 
555     /**
556      * Performs dexopt on system server dex files.
557      *
558      * <p>Verfifies that the package name is {@link PackageManagerService#PLATFORM_PACKAGE_NAME}.
559      *
560      * @return
561      * <p>PackageDexOptimizer.DEX_OPT_SKIPPED if dexopt was skipped because no system server
562      * files were recorded or if no dexopt was needed.
563      * <p>PackageDexOptimizer.DEX_OPT_FAILED if any dexopt operation failed.
564      * <p>PackageDexOptimizer.DEX_OPT_PERFORMED if all dexopt operations succeeded.
565      */
dexoptSystemServer(DexoptOptions options)566     public int dexoptSystemServer(DexoptOptions options) {
567         if (!isPlatformPackage(options.getPackageName())) {
568             Slog.wtf(TAG, "Non system server package used when trying to dexopt system server:"
569                     + options.getPackageName());
570             return PackageDexOptimizer.DEX_OPT_FAILED;
571         }
572 
573         // Override compiler filter for system server to the expected one.
574         //
575         // We could let the caller do this every time the invoke PackageManagerServer#dexopt.
576         // However, there are a few places were this will need to be done which creates
577         // redundancy and the danger of overlooking the config (and thus generating code that will
578         // waste storage and time).
579         DexoptOptions overriddenOptions = options.overrideCompilerFilter(
580                 SYSTEM_SERVER_COMPILER_FILTER);
581 
582         PackageDexOptimizer pdo = getPackageDexOptimizer(overriddenOptions);
583         String packageName = overriddenOptions.getPackageName();
584         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
585         if (useInfo.getDexUseInfoMap().isEmpty()) {
586             if (DEBUG) {
587                 Slog.d(TAG, "No dex files recorded for system server");
588             }
589             // Nothing to compile, return true.
590             return PackageDexOptimizer.DEX_OPT_SKIPPED;
591         }
592 
593         boolean usageUpdated = false;
594         int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
595         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
596             String dexPath = entry.getKey();
597             DexUseInfo dexUseInfo = entry.getValue();
598             if (!Files.exists(Paths.get(dexPath))) {
599                 if (DEBUG) {
600                     Slog.w(TAG, "A dex file previously loaded by System Server does not exist "
601                             + " anymore: " + dexPath);
602                 }
603                 usageUpdated = mPackageDexUsage.removeDexFile(
604                             packageName, dexPath, dexUseInfo.getOwnerUserId()) || usageUpdated;
605                 continue;
606             }
607 
608             if (dexUseInfo.isUnsupportedClassLoaderContext()
609                     || dexUseInfo.isVariableClassLoaderContext()) {
610                 String debugMsg = dexUseInfo.isUnsupportedClassLoaderContext()
611                         ? "unsupported"
612                         : "variable";
613                 Slog.w(TAG, "Skipping dexopt for system server path loaded with " + debugMsg
614                         + " class loader context: " + dexPath);
615                 continue;
616             }
617 
618             int newResult = pdo.dexoptSystemServerPath(dexPath, dexUseInfo, overriddenOptions);
619 
620             // The end result is:
621             //  - FAILED if any path failed,
622             //  - PERFORMED if at least one path needed compilation,
623             //  - SKIPPED when all paths are up to date
624             if ((result != PackageDexOptimizer.DEX_OPT_FAILED)
625                     && (newResult != PackageDexOptimizer.DEX_OPT_SKIPPED)) {
626                 result = newResult;
627             }
628         }
629 
630         if (usageUpdated) {
631             mPackageDexUsage.maybeWriteAsync();
632         }
633 
634         return result;
635     }
636 
637     /**
638      * Select the dex optimizer based on the force parameter.
639      * Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
640      * the necessary dexopt flags to make sure that compilation is not skipped. This avoid
641      * passing the force flag through the multitude of layers.
642      * Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
643      *       allocate an object here.
644      */
getPackageDexOptimizer(DexoptOptions options)645     private PackageDexOptimizer getPackageDexOptimizer(DexoptOptions options) {
646         return options.isForce()
647                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
648                 : mPackageDexOptimizer;
649     }
650 
651     /**
652      * Reconcile the information we have about the secondary dex files belonging to
653      * {@code packagName} and the actual dex files. For all dex files that were
654      * deleted, update the internal records and delete any generated oat files.
655      */
reconcileSecondaryDexFiles(String packageName)656     public void reconcileSecondaryDexFiles(String packageName) {
657         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
658         if (useInfo.getDexUseInfoMap().isEmpty()) {
659             if (DEBUG) {
660                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
661             }
662             // Nothing to reconcile.
663             return;
664         }
665 
666         boolean updated = false;
667         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
668             String dexPath = entry.getKey();
669             DexUseInfo dexUseInfo = entry.getValue();
670             PackageInfo pkg = null;
671             try {
672                 // Note that we look for the package in the PackageManager just to be able
673                 // to get back the real app uid and its storage kind. These are only used
674                 // to perform extra validation in installd.
675                 // TODO(calin): maybe a bit overkill.
676                 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
677                     dexUseInfo.getOwnerUserId());
678             } catch (RemoteException ignore) {
679                 // Can't happen, DexManager is local.
680             }
681             if (pkg == null) {
682                 // It may be that the package was uninstalled while we process the secondary
683                 // dex files.
684                 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
685                         + " for user " + dexUseInfo.getOwnerUserId());
686                 // Update the usage and continue, another user might still have the package.
687                 updated = mPackageDexUsage.removeUserPackage(
688                         packageName, dexUseInfo.getOwnerUserId()) || updated;
689                 continue;
690             }
691 
692             // Special handle system server files.
693             // We don't need an installd call because we have permissions to check if the file
694             // exists.
695             if (isPlatformPackage(packageName)) {
696                 if (!Files.exists(Paths.get(dexPath))) {
697                     if (DEBUG) {
698                         Slog.w(TAG, "A dex file previously loaded by System Server does not exist "
699                                 + " anymore: " + dexPath);
700                     }
701                     updated = mPackageDexUsage.removeUserPackage(
702                             packageName, dexUseInfo.getOwnerUserId()) || updated;
703                 }
704                 continue;
705             }
706 
707             // This is a regular application.
708             ApplicationInfo info = pkg.applicationInfo;
709             int flags = 0;
710             if (info.deviceProtectedDataDir != null &&
711                     FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
712                 flags |= StorageManager.FLAG_STORAGE_DE;
713             } else if (info.credentialProtectedDataDir!= null &&
714                     FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
715                 flags |= StorageManager.FLAG_STORAGE_CE;
716             } else {
717                 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
718                 updated = mPackageDexUsage.removeDexFile(
719                         packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
720                 continue;
721             }
722 
723             boolean dexStillExists = true;
724             synchronized(mInstallLock) {
725                 try {
726                     String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
727                     dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
728                             info.uid, isas, info.volumeUuid, flags);
729                 } catch (InstallerException e) {
730                     Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
731                             " : " + e.getMessage());
732                 }
733             }
734             if (!dexStillExists) {
735                 updated = mPackageDexUsage.removeDexFile(
736                         packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
737             }
738 
739         }
740         if (updated) {
741             mPackageDexUsage.maybeWriteAsync();
742         }
743     }
744 
745     // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
746     // compilation happening here will use a pessimistic context.
registerDexModule(ApplicationInfo info, String dexPath, boolean isSharedModule, int userId)747     public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
748             boolean isSharedModule, int userId) {
749         // Find the owning package record.
750         DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
751 
752         if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) {
753             return new RegisterDexModuleResult(false, "Package not found");
754         }
755         if (!info.packageName.equals(searchResult.mOwningPackageName)) {
756             return new RegisterDexModuleResult(false, "Dex path does not belong to package");
757         }
758         if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
759                 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) {
760             return new RegisterDexModuleResult(false, "Main apks cannot be registered");
761         }
762 
763         // We found the package. Now record the usage for all declared ISAs.
764         boolean update = false;
765         // If this is a shared module set the loading package to an arbitrary package name
766         // so that we can mark that module as usedByOthers.
767         String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName;
768         for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) {
769             boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
770                     dexPath, userId, isa, /*primaryOrSplit*/ false,
771                     loadingPackage,
772                     PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT,
773                     /*overwriteCLC=*/ false);
774             update |= newUpdate;
775         }
776         if (update) {
777             mPackageDexUsage.maybeWriteAsync();
778         }
779 
780         DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName)
781                 .getDexUseInfoMap().get(dexPath);
782 
783         // Try to optimize the package according to the install reason.
784         DexoptOptions options = new DexoptOptions(info.packageName,
785                 PackageManagerService.REASON_INSTALL, /*flags*/0);
786 
787         int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo,
788                 options);
789 
790         // If we fail to optimize the package log an error but don't propagate the error
791         // back to the app. The app cannot do much about it and the background job
792         // will rety again when it executes.
793         // TODO(calin): there might be some value to return the error here but it may
794         // cause red herrings since that doesn't mean the app cannot use the module.
795         if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
796             Slog.e(TAG, "Failed to optimize dex module " + dexPath);
797         }
798         return new RegisterDexModuleResult(true, "Dex module registered successfully");
799     }
800 
801     /**
802      * Return all packages that contain records of secondary dex files.
803      */
getAllPackagesWithSecondaryDexFiles()804     public Set<String> getAllPackagesWithSecondaryDexFiles() {
805         return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
806     }
807 
808     /**
809      * Retrieves the package which owns the given dexPath.
810      */
getDexPackage( ApplicationInfo loadingAppInfo, String dexPath, int userId)811     private DexSearchResult getDexPackage(
812             ApplicationInfo loadingAppInfo, String dexPath, int userId) {
813         // First, check if the package which loads the dex file actually owns it.
814         // Most of the time this will be true and we can return early.
815         PackageCodeLocations loadingPackageCodeLocations =
816                 new PackageCodeLocations(loadingAppInfo, userId);
817         int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
818         if (outcome != DEX_SEARCH_NOT_FOUND) {
819             // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
820             return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
821         }
822 
823         // The loadingPackage does not own the dex file.
824         // Perform a reverse look-up in the cache to detect if any package has ownership.
825         // Note that we can have false negatives if the cache falls out of date.
826         synchronized (mPackageCodeLocationsCache) {
827             for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
828                 outcome = pcl.searchDex(dexPath, userId);
829                 if (outcome != DEX_SEARCH_NOT_FOUND) {
830                     return new DexSearchResult(pcl.mPackageName, outcome);
831                 }
832             }
833         }
834 
835         // We could not find the owning package amongst regular apps.
836         // If the loading package is system server, see if the dex file resides
837         // on any of the potentially system server owning location and if so,
838         // assuming system server ownership.
839         //
840         // Note: We don't have any way to detect which code paths are actually
841         // owned by system server. We can only assume that such paths are on
842         // system partitions.
843         if (isPlatformPackage(loadingAppInfo.packageName)) {
844             if (isSystemServerDexPathSupportedForOdex(dexPath)) {
845                 // We record system server dex files as secondary dex files.
846                 // The reason is that we only record the class loader context for secondary dex
847                 // files and we expect that all primary apks are loaded with an empty class loader.
848                 // System server dex files may be loaded in non-empty class loader so we need to
849                 // keep track of their context.
850                 return new DexSearchResult(PLATFORM_PACKAGE_NAME, DEX_SEARCH_FOUND_SECONDARY);
851             } else {
852                 Slog.wtf(TAG, "System server loads dex files outside paths supported for odex: "
853                         + dexPath);
854             }
855         }
856 
857         if (DEBUG) {
858             // TODO(calin): Consider checking for /data/data symlink.
859             // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
860             // to load dex files through it.
861             try {
862                 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
863                 if (!dexPath.equals(dexPathReal)) {
864                     Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
865                             dexPath + " dexPathReal=" + dexPathReal);
866                 }
867             } catch (IOException e) {
868                 // Ignore
869             }
870         }
871         // Cache miss. The cache is updated during installs and uninstalls,
872         // so if we get here we're pretty sure the dex path does not exist.
873         return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
874     }
875 
876     /** Returns true if this is the platform package .*/
isPlatformPackage(String packageName)877     private static boolean isPlatformPackage(String packageName) {
878         return PLATFORM_PACKAGE_NAME.equals(packageName);
879     }
880 
putIfAbsent(Map<K,V> map, K key, V newValue)881     private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
882         V existingValue = map.putIfAbsent(key, newValue);
883         return existingValue == null ? newValue : existingValue;
884     }
885 
886     /**
887      * Writes the in-memory package dex usage to disk right away.
888      */
writePackageDexUsageNow()889     public void writePackageDexUsageNow() {
890         mPackageDexUsage.writeNow();
891         mDynamicCodeLogger.writeNow();
892     }
893 
894     /**
895      * Returns whether the given package is in the list of privilaged apps that should run out of
896      * box. This only makes sense if the feature is enabled. Note that when the the OOB list is
897      * empty, all priv apps will run in OOB mode.
898      */
isPackageSelectedToRunOob(String packageName)899     public static boolean isPackageSelectedToRunOob(String packageName) {
900         return isPackageSelectedToRunOob(Arrays.asList(packageName));
901     }
902 
903     /**
904      * Returns whether any of the given packages are in the list of privilaged apps that should run
905      * out of box. This only makes sense if the feature is enabled. Note that when the the OOB list
906      * is empty, all priv apps will run in OOB mode.
907      */
isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess)908     public static boolean isPackageSelectedToRunOob(Collection<String> packageNamesInSameProcess) {
909         return isPackageSelectedToRunOobInternal(
910                 SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false),
911                 SystemProperties.get(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST, "ALL"),
912                 packageNamesInSameProcess);
913     }
914 
915     @VisibleForTesting
isPackageSelectedToRunOobInternal(boolean isEnabled, String whitelist, Collection<String> packageNamesInSameProcess)916     /* package */ static boolean isPackageSelectedToRunOobInternal(boolean isEnabled,
917             String whitelist, Collection<String> packageNamesInSameProcess) {
918         if (!isEnabled) {
919             return false;
920         }
921 
922         if ("ALL".equals(whitelist)) {
923             return true;
924         }
925         for (String oobPkgName : whitelist.split(",")) {
926             if (packageNamesInSameProcess.contains(oobPkgName)) {
927                 return true;
928             }
929         }
930         return false;
931     }
932 
933     /**
934      * Generates log if the archive located at {@code fileName} has uncompressed dex file that can
935      * be direclty mapped.
936      */
auditUncompressedDexInApk(String fileName)937     public static boolean auditUncompressedDexInApk(String fileName) {
938         StrictJarFile jarFile = null;
939         try {
940             jarFile = new StrictJarFile(fileName,
941                     false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
942             Iterator<ZipEntry> it = jarFile.iterator();
943             boolean allCorrect = true;
944             while (it.hasNext()) {
945                 ZipEntry entry = it.next();
946                 if (entry.getName().endsWith(".dex")) {
947                     if (entry.getMethod() != ZipEntry.STORED) {
948                         allCorrect = false;
949                         Slog.w(TAG, "APK " + fileName + " has compressed dex code " +
950                                 entry.getName());
951                     } else if ((entry.getDataOffset() & 0x3) != 0) {
952                         allCorrect = false;
953                         Slog.w(TAG, "APK " + fileName + " has unaligned dex code " +
954                                 entry.getName());
955                     }
956                 }
957             }
958             return allCorrect;
959         } catch (IOException ignore) {
960             Slog.wtf(TAG, "Error when parsing APK " + fileName);
961             return false;
962         } finally {
963             try {
964                 if (jarFile != null) {
965                     jarFile.close();
966                 }
967             } catch (IOException ignore) {}
968         }
969     }
970 
971     /**
972      * Translates install scenarios into compilation reasons.  This process can be influenced
973      * by the state of the device.
974      */
getCompilationReasonForInstallScenario(int installScenario)975     public int getCompilationReasonForInstallScenario(int installScenario) {
976         // Compute the compilation reason from the installation scenario.
977 
978         boolean resourcesAreCritical = areBatteryThermalOrMemoryCritical();
979         switch (installScenario) {
980             case PackageManager.INSTALL_SCENARIO_DEFAULT: {
981                 return PackageManagerService.REASON_INSTALL;
982             }
983             case PackageManager.INSTALL_SCENARIO_FAST: {
984                 return PackageManagerService.REASON_INSTALL_FAST;
985             }
986             case PackageManager.INSTALL_SCENARIO_BULK: {
987                 if (resourcesAreCritical) {
988                     return PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED;
989                 } else {
990                     return PackageManagerService.REASON_INSTALL_BULK;
991                 }
992             }
993             case PackageManager.INSTALL_SCENARIO_BULK_SECONDARY: {
994                 if (resourcesAreCritical) {
995                     return PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
996                 } else {
997                     return PackageManagerService.REASON_INSTALL_BULK_SECONDARY;
998                 }
999             }
1000             default: {
1001                 throw new IllegalArgumentException("Invalid installation scenario");
1002             }
1003         }
1004     }
1005 
1006     /**
1007      * Fetches the battery manager object and caches it if it hasn't been fetched already.
1008      */
getBatteryManager()1009     private BatteryManager getBatteryManager() {
1010         if (mBatteryManager == null && mContext != null) {
1011             mBatteryManager = mContext.getSystemService(BatteryManager.class);
1012         }
1013 
1014         return mBatteryManager;
1015     }
1016 
1017     /**
1018      * Returns true if the battery level, device temperature, or memory usage are considered to be
1019      * in a critical state.
1020      */
areBatteryThermalOrMemoryCritical()1021     private boolean areBatteryThermalOrMemoryCritical() {
1022         BatteryManager batteryManager = getBatteryManager();
1023         boolean isBtmCritical = (batteryManager != null
1024                 && batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS)
1025                     == BatteryManager.BATTERY_STATUS_DISCHARGING
1026                 && batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
1027                     <= mCriticalBatteryLevel)
1028                 || (mPowerManager != null
1029                     && mPowerManager.getCurrentThermalStatus()
1030                         >= PowerManager.THERMAL_STATUS_SEVERE);
1031 
1032         return isBtmCritical;
1033     }
1034 
1035     /**
1036      * Deletes all the optimizations files generated by ART.
1037      * This is best effort, and the method will log but not throw errors
1038      * for individual deletes
1039      *
1040      * @param packageInfo the package information.
1041      * @return the number of freed bytes or -1 if there was an error in the process.
1042      */
deleteOptimizedFiles(ArtPackageInfo packageInfo)1043     public long deleteOptimizedFiles(ArtPackageInfo packageInfo) {
1044         long freedBytes = 0;
1045         boolean hadErrors = false;
1046         for (String codePath : packageInfo.getCodePaths()) {
1047             for (String isa : packageInfo.getInstructionSets()) {
1048                 try {
1049                     freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir());
1050                 } catch (InstallerException e) {
1051                     Log.e(TAG, "Failed deleting oat files for " + codePath, e);
1052                     hadErrors = true;
1053                 }
1054             }
1055         }
1056         return hadErrors ? -1 : freedBytes;
1057     }
1058 
1059     public static class RegisterDexModuleResult {
RegisterDexModuleResult()1060         public RegisterDexModuleResult() {
1061             this(false, null);
1062         }
1063 
RegisterDexModuleResult(boolean success, String message)1064         public RegisterDexModuleResult(boolean success, String message) {
1065             this.success = success;
1066             this.message = message;
1067         }
1068 
1069         public final boolean success;
1070         public final String message;
1071     }
1072 
1073     /**
1074      * Convenience class to store the different locations where a package might
1075      * own code.
1076      */
1077     private static class PackageCodeLocations {
1078         private final String mPackageName;
1079         private String mBaseCodePath;
1080         private final Set<String> mSplitCodePaths;
1081         // Maps user id to the application private directory.
1082         private final Map<Integer, Set<String>> mAppDataDirs;
1083 
PackageCodeLocations(ApplicationInfo ai, int userId)1084         public PackageCodeLocations(ApplicationInfo ai, int userId) {
1085             this(ai.packageName, ai.sourceDir, ai.splitSourceDirs);
1086             mergeAppDataDirs(ai.dataDir, userId);
1087         }
PackageCodeLocations(String packageName, String baseCodePath, String[] splitCodePaths)1088         public PackageCodeLocations(String packageName, String baseCodePath,
1089                 String[] splitCodePaths) {
1090             mPackageName = packageName;
1091             mSplitCodePaths = new HashSet<>();
1092             mAppDataDirs = new HashMap<>();
1093             updateCodeLocation(baseCodePath, splitCodePaths);
1094         }
1095 
updateCodeLocation(String baseCodePath, String[] splitCodePaths)1096         public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) {
1097             mBaseCodePath = baseCodePath;
1098             mSplitCodePaths.clear();
1099             if (splitCodePaths != null) {
1100                 for (String split : splitCodePaths) {
1101                     mSplitCodePaths.add(split);
1102                 }
1103             }
1104         }
1105 
mergeAppDataDirs(String dataDir, int userId)1106         public void mergeAppDataDirs(String dataDir, int userId) {
1107             Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
1108             dataDirs.add(dataDir);
1109         }
1110 
searchDex(String dexPath, int userId)1111         public int searchDex(String dexPath, int userId) {
1112             // First check that this package is installed or active for the given user.
1113             // A missing data dir means the package is not installed.
1114             Set<String> userDataDirs = mAppDataDirs.get(userId);
1115             if (userDataDirs == null) {
1116                 return DEX_SEARCH_NOT_FOUND;
1117             }
1118 
1119             if (mBaseCodePath.equals(dexPath)) {
1120                 return DEX_SEARCH_FOUND_PRIMARY;
1121             }
1122             if (mSplitCodePaths.contains(dexPath)) {
1123                 return DEX_SEARCH_FOUND_SPLIT;
1124             }
1125             for (String dataDir : userDataDirs) {
1126                 if (dexPath.startsWith(dataDir)) {
1127                     return DEX_SEARCH_FOUND_SECONDARY;
1128                 }
1129             }
1130 
1131             return DEX_SEARCH_NOT_FOUND;
1132         }
1133     }
1134 
1135     /**
1136      * Convenience class to store ownership search results.
1137      */
1138     private class DexSearchResult {
1139         private String mOwningPackageName;
1140         private int mOutcome;
1141 
DexSearchResult(String owningPackageName, int outcome)1142         public DexSearchResult(String owningPackageName, int outcome) {
1143             this.mOwningPackageName = owningPackageName;
1144             this.mOutcome = outcome;
1145         }
1146 
1147         @Override
toString()1148         public String toString() {
1149             return mOwningPackageName + "-" + mOutcome;
1150         }
1151     }
1152 }
1153