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