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