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