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