Home | History | Annotate | Download | only in dex
      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 android.content.pm.ApplicationInfo;
     20 import android.content.pm.IPackageManager;
     21 import android.content.pm.PackageInfo;
     22 import android.os.FileUtils;
     23 import android.os.RemoteException;
     24 import android.os.storage.StorageManager;
     25 import android.os.UserHandle;
     26 
     27 import android.util.Slog;
     28 
     29 import com.android.internal.annotations.GuardedBy;
     30 import com.android.server.pm.Installer;
     31 import com.android.server.pm.Installer.InstallerException;
     32 import com.android.server.pm.PackageDexOptimizer;
     33 import com.android.server.pm.PackageManagerServiceUtils;
     34 import com.android.server.pm.PackageManagerServiceCompilerMapping;
     35 
     36 import java.io.File;
     37 import java.io.IOException;
     38 import java.util.List;
     39 import java.util.HashMap;
     40 import java.util.HashSet;
     41 import java.util.Map;
     42 import java.util.Set;
     43 
     44 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
     45 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
     46 
     47 /**
     48  * This class keeps track of how dex files are used.
     49  * Every time it gets a notification about a dex file being loaded it tracks
     50  * its owning package and records it in PackageDexUsage (package-dex-usage.list).
     51  *
     52  * TODO(calin): Extract related dexopt functionality from PackageManagerService
     53  * into this class.
     54  */
     55 public class DexManager {
     56     private static final String TAG = "DexManager";
     57 
     58     private static final boolean DEBUG = false;
     59 
     60     // Maps package name to code locations.
     61     // It caches the code locations for the installed packages. This allows for
     62     // faster lookups (no locks) when finding what package owns the dex file.
     63     @GuardedBy("mPackageCodeLocationsCache")
     64     private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache;
     65 
     66     // PackageDexUsage handles the actual I/O operations. It is responsible to
     67     // encode and save the dex usage data.
     68     private final PackageDexUsage mPackageDexUsage;
     69 
     70     private final IPackageManager mPackageManager;
     71     private final PackageDexOptimizer mPackageDexOptimizer;
     72     private final Object mInstallLock;
     73     @GuardedBy("mInstallLock")
     74     private final Installer mInstaller;
     75 
     76     // Possible outcomes of a dex search.
     77     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
     78     private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
     79     private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
     80     private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
     81 
     82     public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
     83             Installer installer, Object installLock) {
     84       mPackageCodeLocationsCache = new HashMap<>();
     85       mPackageDexUsage = new PackageDexUsage();
     86       mPackageManager = pms;
     87       mPackageDexOptimizer = pdo;
     88       mInstaller = installer;
     89       mInstallLock = installLock;
     90     }
     91 
     92     /**
     93      * Notify about dex files loads.
     94      * Note that this method is invoked when apps load dex files and it should
     95      * return as fast as possible.
     96      *
     97      * @param loadingAppInfo the package performing the load
     98      * @param dexPaths the list of dex files being loaded
     99      * @param loaderIsa the ISA of the app loading the dex files
    100      * @param loaderUserId the user id which runs the code loading the dex files
    101      */
    102     public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths,
    103             String loaderIsa, int loaderUserId) {
    104         try {
    105             notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId);
    106         } catch (Exception e) {
    107             Slog.w(TAG, "Exception while notifying dex load for package " +
    108                     loadingAppInfo.packageName, e);
    109         }
    110     }
    111 
    112     private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths,
    113             String loaderIsa, int loaderUserId) {
    114         if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
    115             Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " +
    116                     loaderIsa + "?");
    117             return;
    118         }
    119 
    120         for (String dexPath : dexPaths) {
    121             // Find the owning package name.
    122             DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
    123 
    124             if (DEBUG) {
    125                 Slog.i(TAG, loadingAppInfo.packageName
    126                     + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath);
    127             }
    128 
    129             if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) {
    130                 // TODO(calin): extend isUsedByOtherApps check to detect the cases where
    131                 // different apps share the same runtime. In that case we should not mark the dex
    132                 // file as isUsedByOtherApps. Currently this is a safe approximation.
    133                 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals(
    134                         searchResult.mOwningPackageName);
    135                 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
    136                         searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT;
    137 
    138                 if (primaryOrSplit && !isUsedByOtherApps) {
    139                     // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
    140                     // do not record it. This case does not bring any new usable information
    141                     // and can be safely skipped.
    142                     continue;
    143                 }
    144 
    145                 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
    146                 // or UsedBytOtherApps), record will return true and we trigger an async write
    147                 // to disk to make sure we don't loose the data in case of a reboot.
    148                 if (mPackageDexUsage.record(searchResult.mOwningPackageName,
    149                         dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) {
    150                     mPackageDexUsage.maybeWriteAsync();
    151                 }
    152             } else {
    153                 // This can happen in a few situations:
    154                 // - bogus dex loads
    155                 // - recent installs/uninstalls that we didn't detect.
    156                 // - new installed splits
    157                 // If we can't find the owner of the dex we simply do not track it. The impact is
    158                 // that the dex file will not be considered for offline optimizations.
    159                 // TODO(calin): add hooks for move/uninstall notifications to
    160                 // capture package moves or obsolete packages.
    161                 if (DEBUG) {
    162                     Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
    163                 }
    164             }
    165         }
    166     }
    167 
    168     /**
    169      * Read the dex usage from disk and populate the code cache locations.
    170      * @param existingPackages a map containing information about what packages
    171      *          are available to what users. Only packages in this list will be
    172      *          recognized during notifyDexLoad().
    173      */
    174     public void load(Map<Integer, List<PackageInfo>> existingPackages) {
    175         try {
    176             loadInternal(existingPackages);
    177         } catch (Exception e) {
    178             mPackageDexUsage.clear();
    179             Slog.w(TAG, "Exception while loading package dex usage. " +
    180                     "Starting with a fresh state.", e);
    181         }
    182     }
    183 
    184     /**
    185      * Notifies that a new package was installed for {@code userId}.
    186      * {@code userId} must not be {@code UserHandle.USER_ALL}.
    187      *
    188      * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}.
    189      */
    190     public void notifyPackageInstalled(PackageInfo pi, int userId) {
    191         if (userId == UserHandle.USER_ALL) {
    192             throw new IllegalArgumentException(
    193                 "notifyPackageInstalled called with USER_ALL");
    194         }
    195         cachePackageInfo(pi, userId);
    196     }
    197 
    198     /**
    199      * Notifies that package {@code packageName} was updated.
    200      * This will clear the UsedByOtherApps mark if it exists.
    201      */
    202     public void notifyPackageUpdated(String packageName, String baseCodePath,
    203             String[] splitCodePaths) {
    204         cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1);
    205         // In case there was an update, write the package use info to disk async.
    206         // Note that we do the writing here and not in PackageDexUsage in order to be
    207         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
    208         // multiple updates in PackageDexUsage before writing it).
    209         if (mPackageDexUsage.clearUsedByOtherApps(packageName)) {
    210             mPackageDexUsage.maybeWriteAsync();
    211         }
    212     }
    213 
    214     /**
    215      * Notifies that the user {@code userId} data for package {@code packageName}
    216      * was destroyed. This will remove all usage info associated with the package
    217      * for the given user.
    218      * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case
    219      * all usage information for the package will be removed.
    220      */
    221     public void notifyPackageDataDestroyed(String packageName, int userId) {
    222         boolean updated = userId == UserHandle.USER_ALL
    223             ? mPackageDexUsage.removePackage(packageName)
    224             : mPackageDexUsage.removeUserPackage(packageName, userId);
    225         // In case there was an update, write the package use info to disk async.
    226         // Note that we do the writing here and not in PackageDexUsage in order to be
    227         // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
    228         // multiple updates in PackageDexUsage before writing it).
    229         if (updated) {
    230             mPackageDexUsage.maybeWriteAsync();
    231         }
    232     }
    233 
    234     /**
    235      * Caches the code location from the given package info.
    236      */
    237     private void cachePackageInfo(PackageInfo pi, int userId) {
    238         ApplicationInfo ai = pi.applicationInfo;
    239         String[] dataDirs = new String[] {ai.dataDir, ai.deviceProtectedDataDir,
    240                 ai.credentialProtectedDataDir};
    241         cachePackageCodeLocation(pi.packageName, ai.sourceDir, ai.splitSourceDirs,
    242                 dataDirs, userId);
    243     }
    244 
    245     private void cachePackageCodeLocation(String packageName, String baseCodePath,
    246             String[] splitCodePaths, String[] dataDirs, int userId) {
    247         synchronized (mPackageCodeLocationsCache) {
    248             PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName,
    249                     new PackageCodeLocations(packageName, baseCodePath, splitCodePaths));
    250             // TODO(calin): We are forced to extend the scope of this synchronization because
    251             // the values of the cache (PackageCodeLocations) are updated in place.
    252             // Make PackageCodeLocations immutable to simplify the synchronization reasoning.
    253             pcl.updateCodeLocation(baseCodePath, splitCodePaths);
    254             if (dataDirs != null) {
    255                 for (String dataDir : dataDirs) {
    256                     // The set of data dirs includes deviceProtectedDataDir and
    257                     // credentialProtectedDataDir which might be null for shared
    258                     // libraries. Currently we don't track these but be lenient
    259                     // and check in case we ever decide to store their usage data.
    260                     if (dataDir != null) {
    261                         pcl.mergeAppDataDirs(dataDir, userId);
    262                     }
    263                 }
    264             }
    265         }
    266     }
    267 
    268     private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
    269         Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
    270         // Cache the code locations for the installed packages. This allows for
    271         // faster lookups (no locks) when finding what package owns the dex file.
    272         for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) {
    273             List<PackageInfo> packageInfoList = entry.getValue();
    274             int userId = entry.getKey();
    275             for (PackageInfo pi : packageInfoList) {
    276                 // Cache the code locations.
    277                 cachePackageInfo(pi, userId);
    278 
    279                 // Cache a map from package name to the set of user ids who installed the package.
    280                 // We will use it to sync the data and remove obsolete entries from
    281                 // mPackageDexUsage.
    282                 Set<Integer> users = putIfAbsent(
    283                         packageToUsersMap, pi.packageName, new HashSet<>());
    284                 users.add(userId);
    285             }
    286         }
    287 
    288         mPackageDexUsage.read();
    289         mPackageDexUsage.syncData(packageToUsersMap);
    290     }
    291 
    292     /**
    293      * Get the package dex usage for the given package name.
    294      * @return the package data or null if there is no data available for this package.
    295      */
    296     public PackageUseInfo getPackageUseInfo(String packageName) {
    297         return mPackageDexUsage.getPackageUseInfo(packageName);
    298     }
    299 
    300     /**
    301      * Perform dexopt on the package {@code packageName} secondary dex files.
    302      * @return true if all secondary dex files were processed successfully (compiled or skipped
    303      *         because they don't need to be compiled)..
    304      */
    305     public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force) {
    306         return dexoptSecondaryDex(packageName,
    307                 PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason),
    308                 force);
    309     }
    310 
    311     /**
    312      * Perform dexopt on the package {@code packageName} secondary dex files.
    313      * @return true if all secondary dex files were processed successfully (compiled or skipped
    314      *         because they don't need to be compiled)..
    315      */
    316     public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
    317         // Select the dex optimizer based on the force parameter.
    318         // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
    319         // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
    320         // passing the force flag through the multitude of layers.
    321         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
    322         //       allocate an object here.
    323         PackageDexOptimizer pdo = force
    324                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
    325                 : mPackageDexOptimizer;
    326         PackageUseInfo useInfo = getPackageUseInfo(packageName);
    327         if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
    328             if (DEBUG) {
    329                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
    330             }
    331             // Nothing to compile, return true.
    332             return true;
    333         }
    334         boolean success = true;
    335         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
    336             String dexPath = entry.getKey();
    337             DexUseInfo dexUseInfo = entry.getValue();
    338             PackageInfo pkg = null;
    339             try {
    340                 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
    341                     dexUseInfo.getOwnerUserId());
    342             } catch (RemoteException e) {
    343                 throw new AssertionError(e);
    344             }
    345             // It may be that the package gets uninstalled while we try to compile its
    346             // secondary dex files. If that's the case, just ignore.
    347             // Note that we don't break the entire loop because the package might still be
    348             // installed for other users.
    349             if (pkg == null) {
    350                 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
    351                         + " for user " + dexUseInfo.getOwnerUserId());
    352                 mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
    353                 continue;
    354             }
    355 
    356             int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
    357                     dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
    358             success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
    359         }
    360         return success;
    361     }
    362 
    363     /**
    364      * Reconcile the information we have about the secondary dex files belonging to
    365      * {@code packagName} and the actual dex files. For all dex files that were
    366      * deleted, update the internal records and delete any generated oat files.
    367      */
    368     public void reconcileSecondaryDexFiles(String packageName) {
    369         PackageUseInfo useInfo = getPackageUseInfo(packageName);
    370         if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
    371             if (DEBUG) {
    372                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
    373             }
    374             // Nothing to reconcile.
    375             return;
    376         }
    377 
    378         boolean updated = false;
    379         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
    380             String dexPath = entry.getKey();
    381             DexUseInfo dexUseInfo = entry.getValue();
    382             PackageInfo pkg = null;
    383             try {
    384                 // Note that we look for the package in the PackageManager just to be able
    385                 // to get back the real app uid and its storage kind. These are only used
    386                 // to perform extra validation in installd.
    387                 // TODO(calin): maybe a bit overkill.
    388                 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
    389                     dexUseInfo.getOwnerUserId());
    390             } catch (RemoteException ignore) {
    391                 // Can't happen, DexManager is local.
    392             }
    393             if (pkg == null) {
    394                 // It may be that the package was uninstalled while we process the secondary
    395                 // dex files.
    396                 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
    397                         + " for user " + dexUseInfo.getOwnerUserId());
    398                 // Update the usage and continue, another user might still have the package.
    399                 updated = mPackageDexUsage.removeUserPackage(
    400                         packageName, dexUseInfo.getOwnerUserId()) || updated;
    401                 continue;
    402             }
    403             ApplicationInfo info = pkg.applicationInfo;
    404             int flags = 0;
    405             if (info.deviceProtectedDataDir != null &&
    406                     FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
    407                 flags |= StorageManager.FLAG_STORAGE_DE;
    408             } else if (info.credentialProtectedDataDir!= null &&
    409                     FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
    410                 flags |= StorageManager.FLAG_STORAGE_CE;
    411             } else {
    412                 Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
    413                 updated = mPackageDexUsage.removeDexFile(
    414                         packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
    415                 continue;
    416             }
    417 
    418             boolean dexStillExists = true;
    419             synchronized(mInstallLock) {
    420                 try {
    421                     String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
    422                     dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
    423                             pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
    424                 } catch (InstallerException e) {
    425                     Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
    426                             " : " + e.getMessage());
    427                 }
    428             }
    429             if (!dexStillExists) {
    430                 updated = mPackageDexUsage.removeDexFile(
    431                         packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
    432             }
    433 
    434         }
    435         if (updated) {
    436             mPackageDexUsage.maybeWriteAsync();
    437         }
    438     }
    439 
    440     /**
    441      * Return all packages that contain records of secondary dex files.
    442      */
    443     public Set<String> getAllPackagesWithSecondaryDexFiles() {
    444         return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
    445     }
    446 
    447     /**
    448      * Return true if the profiling data collected for the given app indicate
    449      * that the apps's APK has been loaded by another app.
    450      * Note that this returns false for all apps without any collected profiling data.
    451     */
    452     public boolean isUsedByOtherApps(String packageName) {
    453         PackageUseInfo useInfo = getPackageUseInfo(packageName);
    454         if (useInfo == null) {
    455             // No use info, means the package was not used or it was used but not by other apps.
    456             // Note that right now we might prune packages which are not used by other apps.
    457             // TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
    458             // to access the package use.
    459             return false;
    460         }
    461         return useInfo.isUsedByOtherApps();
    462     }
    463 
    464     /**
    465      * Retrieves the package which owns the given dexPath.
    466      */
    467     private DexSearchResult getDexPackage(
    468             ApplicationInfo loadingAppInfo, String dexPath, int userId) {
    469         // Ignore framework code.
    470         // TODO(calin): is there a better way to detect it?
    471         if (dexPath.startsWith("/system/framework/")) {
    472             return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND);
    473         }
    474 
    475         // First, check if the package which loads the dex file actually owns it.
    476         // Most of the time this will be true and we can return early.
    477         PackageCodeLocations loadingPackageCodeLocations =
    478                 new PackageCodeLocations(loadingAppInfo, userId);
    479         int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId);
    480         if (outcome != DEX_SEARCH_NOT_FOUND) {
    481             // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level.
    482             return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome);
    483         }
    484 
    485         // The loadingPackage does not own the dex file.
    486         // Perform a reverse look-up in the cache to detect if any package has ownership.
    487         // Note that we can have false negatives if the cache falls out of date.
    488         synchronized (mPackageCodeLocationsCache) {
    489             for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) {
    490                 outcome = pcl.searchDex(dexPath, userId);
    491                 if (outcome != DEX_SEARCH_NOT_FOUND) {
    492                     return new DexSearchResult(pcl.mPackageName, outcome);
    493                 }
    494             }
    495         }
    496 
    497         if (DEBUG) {
    498             // TODO(calin): Consider checking for /data/data symlink.
    499             // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps
    500             // to load dex files through it.
    501             try {
    502                 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
    503                 if (dexPathReal != dexPath) {
    504                     Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
    505                             dexPath + " dexPathReal=" + dexPathReal);
    506                 }
    507             } catch (IOException e) {
    508                 // Ignore
    509             }
    510         }
    511         // Cache miss. The cache is updated during installs and uninstalls,
    512         // so if we get here we're pretty sure the dex path does not exist.
    513         return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND);
    514     }
    515 
    516     private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) {
    517         V existingValue = map.putIfAbsent(key, newValue);
    518         return existingValue == null ? newValue : existingValue;
    519     }
    520 
    521     /**
    522      * Convenience class to store the different locations where a package might
    523      * own code.
    524      */
    525     private static class PackageCodeLocations {
    526         private final String mPackageName;
    527         private String mBaseCodePath;
    528         private final Set<String> mSplitCodePaths;
    529         // Maps user id to the application private directory.
    530         private final Map<Integer, Set<String>> mAppDataDirs;
    531 
    532         public PackageCodeLocations(ApplicationInfo ai, int userId) {
    533             this(ai.packageName, ai.sourceDir, ai.splitSourceDirs);
    534             mergeAppDataDirs(ai.dataDir, userId);
    535         }
    536         public PackageCodeLocations(String packageName, String baseCodePath,
    537                 String[] splitCodePaths) {
    538             mPackageName = packageName;
    539             mSplitCodePaths = new HashSet<>();
    540             mAppDataDirs = new HashMap<>();
    541             updateCodeLocation(baseCodePath, splitCodePaths);
    542         }
    543 
    544         public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) {
    545             mBaseCodePath = baseCodePath;
    546             mSplitCodePaths.clear();
    547             if (splitCodePaths != null) {
    548                 for (String split : splitCodePaths) {
    549                     mSplitCodePaths.add(split);
    550                 }
    551             }
    552         }
    553 
    554         public void mergeAppDataDirs(String dataDir, int userId) {
    555             Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>());
    556             dataDirs.add(dataDir);
    557         }
    558 
    559         public int searchDex(String dexPath, int userId) {
    560             // First check that this package is installed or active for the given user.
    561             // A missing data dir means the package is not installed.
    562             Set<String> userDataDirs = mAppDataDirs.get(userId);
    563             if (userDataDirs == null) {
    564                 return DEX_SEARCH_NOT_FOUND;
    565             }
    566 
    567             if (mBaseCodePath.equals(dexPath)) {
    568                 return DEX_SEARCH_FOUND_PRIMARY;
    569             }
    570             if (mSplitCodePaths.contains(dexPath)) {
    571                 return DEX_SEARCH_FOUND_SPLIT;
    572             }
    573             for (String dataDir : userDataDirs) {
    574                 if (dexPath.startsWith(dataDir)) {
    575                     return DEX_SEARCH_FOUND_SECONDARY;
    576                 }
    577             }
    578 
    579             return DEX_SEARCH_NOT_FOUND;
    580         }
    581     }
    582 
    583     /**
    584      * Convenience class to store ownership search results.
    585      */
    586     private class DexSearchResult {
    587         private String mOwningPackageName;
    588         private int mOutcome;
    589 
    590         public DexSearchResult(String owningPackageName, int outcome) {
    591             this.mOwningPackageName = owningPackageName;
    592             this.mOutcome = outcome;
    593         }
    594 
    595         @Override
    596         public String toString() {
    597             return mOwningPackageName + "-" + mOutcome;
    598         }
    599     }
    600 
    601 
    602 }
    603