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