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