Home | History | Annotate | Download | only in dex
      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 
     17 package com.android.server.pm.dex;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.Context;
     21 import android.content.pm.ApplicationInfo;
     22 import android.content.pm.IPackageManager;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.PackageParser;
     25 import android.database.ContentObserver;
     26 import android.os.Build;
     27 import android.os.FileUtils;
     28 import android.os.RemoteException;
     29 import android.os.storage.StorageManager;
     30 import android.os.SystemProperties;
     31 import android.os.UserHandle;
     32 import android.provider.Settings.Global;
     33 import android.util.Slog;
     34 import android.util.jar.StrictJarFile;
     35 
     36 import com.android.internal.annotations.GuardedBy;
     37 import com.android.internal.util.ArrayUtils;
     38 import com.android.server.pm.Installer;
     39 import com.android.server.pm.Installer.InstallerException;
     40 import com.android.server.pm.PackageDexOptimizer;
     41 import com.android.server.pm.PackageManagerService;
     42 import com.android.server.pm.PackageManagerServiceUtils;
     43 import com.android.server.pm.PackageManagerServiceCompilerMapping;
     44 
     45 import java.io.File;
     46 import java.io.IOException;
     47 import java.util.Arrays;
     48 import java.util.Collection;
     49 import java.util.Collections;
     50 import java.util.Iterator;
     51 import java.util.List;
     52 import java.util.HashMap;
     53 import java.util.HashSet;
     54 import java.util.Map;
     55 import java.util.Set;
     56 import java.util.zip.ZipEntry;
     57 
     58 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
     59 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
     60 import 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  */
     70 public 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