Merge "Compile secondary dex files in DexManager"
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 61531ae..f0a9ec3 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -490,6 +490,7 @@
      */
     boolean performDexOpt(String packageName, boolean checkProfiles,
             int compileReason, boolean force);
+
     /**
      * Ask the package manager to perform a dex-opt with the given compiler filter.
      *
@@ -500,6 +501,16 @@
             String targetCompilerFilter, boolean force);
 
     /**
+     * Ask the package manager to perform a dex-opt with the given compiler filter on the
+     * secondary dex files belonging to the given package.
+     *
+     * Note: exposed only for the shell command to allow moving packages explicitly to a
+     *       definite state.
+     */
+    boolean performDexOptSecondary(String packageName,
+            String targetCompilerFilter, boolean force);
+
+    /**
      * Ask the package manager to dump profiles associated with a package.
      */
     void dumpProfiles(String packageName);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index fc66bb3..e1b4c84 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -50,6 +50,14 @@
     public static final int DEXOPT_BOOTCOMPLETE   = 1 << 4;
     /** Hint that the dexopt type is profile-guided. */
     public static final int DEXOPT_PROFILE_GUIDED = 1 << 5;
+    /** The compilation is for a secondary dex file. */
+    public static final int DEXOPT_SECONDARY_DEX  = 1 << 6;
+    /** Ignore the result of dexoptNeeded and force compilation. */
+    public static final int DEXOPT_FORCE          = 1 << 7;
+    /** Indicates that the dex file passed to dexopt in on CE storage. */
+    public static final int DEXOPT_STORAGE_CE     = 1 << 8;
+    /** Indicates that the dex file passed to dexopt in on DE storage. */
+    public static final int DEXOPT_STORAGE_DE     = 1 << 9;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 8e201ac..db712ae 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageParser;
 import android.os.Environment;
 import android.os.PowerManager;
@@ -35,6 +36,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import dalvik.system.DexFile;
 
@@ -43,6 +45,10 @@
 import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
 import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
 import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
+import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX;
+import static com.android.server.pm.Installer.DEXOPT_FORCE;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 
@@ -52,13 +58,13 @@
 /**
  * Helper class for running dexopt command on packages.
  */
-class PackageDexOptimizer {
+public class PackageDexOptimizer {
     private static final String TAG = "PackageManager.DexOptimizer";
     static final String OAT_DIR_NAME = "oat";
     // TODO b/19550105 Remove error codes and use exceptions
-    static final int DEX_OPT_SKIPPED = 0;
-    static final int DEX_OPT_PERFORMED = 1;
-    static final int DEX_OPT_FAILED = -1;
+    public static final int DEX_OPT_SKIPPED = 0;
+    public static final int DEX_OPT_PERFORMED = 1;
+    public static final int DEX_OPT_FAILED = -1;
 
     private final Installer mInstaller;
     private final Object mInstallLock;
@@ -100,6 +106,9 @@
             return DEX_OPT_SKIPPED;
         }
         synchronized (mInstallLock) {
+            // During boot the system doesn't need to instantiate and obtain a wake lock.
+            // PowerManager might not be ready, but that doesn't mean that we can't proceed with
+            // dexopt.
             final boolean useLock = mSystemReady;
             if (useLock) {
                 mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
@@ -130,9 +139,11 @@
         final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
 
-        final String compilerFilter = getRealCompilerFilter(pkg, targetCompilerFilter);
+        final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
+                targetCompilerFilter, isUsedByOtherApps(pkg));
         final boolean profileUpdated = checkForProfileUpdates &&
                 isProfileUpdated(pkg, sharedGid, compilerFilter);
+
         // TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
         // paths (b/34169257).
         final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
@@ -201,6 +212,79 @@
     }
 
     /**
+     * Performs dexopt on the secondary dex {@code path} belonging to the app {@code info}.
+     *
+     * @return
+     *      DEX_OPT_FAILED if there was any exception during dexopt
+     *      DEX_OPT_PERFORMED if dexopt was performed successfully on the given path.
+     * NOTE that DEX_OPT_PERFORMED for secondary dex files includes the case when the dex file
+     * didn't need an update. That's because at the moment we don't get more than success/failure
+     * from installd.
+     *
+     * TODO(calin): Consider adding return codes to installd dexopt invocation (rather than
+     * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
+     * that seems wasteful.
+     */
+    public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas,
+            String compilerFilter, boolean isUsedByOtherApps) {
+        synchronized (mInstallLock) {
+            // During boot the system doesn't need to instantiate and obtain a wake lock.
+            // PowerManager might not be ready, but that doesn't mean that we can't proceed with
+            // dexopt.
+            final boolean useLock = mSystemReady;
+            if (useLock) {
+                mDexoptWakeLock.setWorkSource(new WorkSource(info.uid));
+                mDexoptWakeLock.acquire();
+            }
+            try {
+                return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter,
+                        isUsedByOtherApps);
+            } finally {
+                if (useLock) {
+                    mDexoptWakeLock.release();
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mInstallLock")
+    private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
+            String compilerFilter, boolean isUsedByOtherApps) {
+        int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX;
+        // Check the app storage and add the appropriate flags.
+        if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+            dexoptFlags |= DEXOPT_STORAGE_DE;
+        } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+            dexoptFlags |= DEXOPT_STORAGE_CE;
+        } else {
+            Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
+            return DEX_OPT_FAILED;
+        }
+        compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
+        Log.d(TAG, "Running dexopt on: " + path
+                + " pkg=" + info.packageName + " isa=" + isas
+                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
+                + " target-filter=" + compilerFilter);
+
+        try {
+            for (String isa : isas) {
+                // Reuse the same dexopt path as for the primary apks. We don't need all the
+                // arguments as some (dexopNeeded and oatDir) will be computed by installd because
+                // system server cannot read untrusted app content.
+                // TODO(calin): maybe add a separate call.
+                mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
+                        /*oatDir*/ null, dexoptFlags,
+                        compilerFilter, info.volumeUuid, /*sharedLibrariesPath*/ null);
+            }
+
+            return DEX_OPT_PERFORMED;
+        } catch (InstallerException e) {
+            Slog.w(TAG, "Failed to dexopt", e);
+            return DEX_OPT_FAILED;
+        }
+    }
+
+    /**
      * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
      * optimize or not (and in what way).
      */
@@ -246,8 +330,9 @@
      * The target filter will be updated if the package code is used by other apps
      * or if it has the safe mode flag set.
      */
-    private String getRealCompilerFilter(PackageParser.Package pkg, String targetCompilerFilter) {
-        int flags = pkg.applicationInfo.flags;
+    private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter,
+            boolean isUsedByOtherApps) {
+        int flags = info.flags;
         boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         if (vmSafeMode) {
             // For the compilation, it doesn't really matter what we return here because installd
@@ -259,7 +344,7 @@
             return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
         }
 
-        if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps(pkg)) {
+        if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
             // If the dex files is used by other apps, we cannot use profile-guided compilation.
             return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
         }
@@ -272,12 +357,16 @@
      * filter.
      */
     private int getDexFlags(PackageParser.Package pkg, String compilerFilter) {
-        int flags = pkg.applicationInfo.flags;
+        return getDexFlags(pkg.applicationInfo, compilerFilter);
+    }
+
+    private int getDexFlags(ApplicationInfo info, String compilerFilter) {
+        int flags = info.flags;
         boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
         // Profile guide compiled oat files should not be public.
         boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
-        boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
+        boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter;
         int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
         int dexFlags =
                 (isPublic ? DEXOPT_PUBLIC : 0)
@@ -437,6 +526,19 @@
         if ((flags & DEXOPT_SAFEMODE) == DEXOPT_SAFEMODE) {
             flagsList.add("safemode");
         }
+        if ((flags & DEXOPT_SECONDARY_DEX) == DEXOPT_SECONDARY_DEX) {
+            flagsList.add("secondary");
+        }
+        if ((flags & DEXOPT_FORCE) == DEXOPT_FORCE) {
+            flagsList.add("force");
+        }
+        if ((flags & DEXOPT_STORAGE_CE) == DEXOPT_STORAGE_CE) {
+            flagsList.add("storage_ce");
+        }
+        if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) {
+            flagsList.add("storage_de");
+        }
+
         return String.join(",", flagsList);
     }
 
@@ -461,5 +563,12 @@
             // TODO: The return value is wrong when patchoat is needed.
             return DexFile.DEX2OAT_FROM_SCRATCH;
         }
+
+        @Override
+        protected int adjustDexoptFlags(int flags) {
+            // Add DEXOPT_FORCE flag to signal installd that it should force compilation
+            // and discard dexoptanalyzer result.
+            return flags | DEXOPT_FORCE;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b0512d4..85c1152 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2227,7 +2227,7 @@
         mInstaller = installer;
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
-        mDexManager = new DexManager();
+        mDexManager = new DexManager(this, mPackageDexOptimizer);
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -8226,6 +8226,15 @@
                 targetCompilerFilter, getOrCreateCompilerPackageStats(p));
     }
 
+    // Performs dexopt on the used secondary dex files belonging to the given package.
+    // Returns true if all dex files were process successfully (which could mean either dexopt or
+    // skip). Returns false if any of the files caused errors.
+    @Override
+    public boolean performDexOptSecondary(String packageName, String compilerFilter,
+            boolean force) {
+        return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force);
+    }
+
     List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
         if (p.usesLibraries != null || p.usesOptionalLibraries != null
                 || p.usesStaticLibraries != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 8a3f48e..9c9a671 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -23,7 +23,7 @@
 /**
  * Manage (retrieve) mappings from compilation reason to compilation filter.
  */
-class PackageManagerServiceCompilerMapping {
+public class PackageManagerServiceCompilerMapping {
     // Names for compilation reasons.
     static final String REASON_STRINGS[] = {
             "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "nsys-library", "shared-apk",
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2f8d749..4078245 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -306,6 +306,7 @@
         String compilerFilter = null;
         String compilationReason = null;
         String checkProfilesRaw = null;
+        boolean secondaryDex = false;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -333,6 +334,9 @@
                     clearProfileData = true;
                     compilationReason = "install";
                     break;
+                case "--secondary-dex":
+                    secondaryDex = true;
+                    break;
                 default:
                     pw.println("Error: Unknown option: " + opt);
                     return 1;
@@ -405,8 +409,11 @@
                 mInterface.clearApplicationProfileData(packageName);
             }
 
-            boolean result = mInterface.performDexOptMode(packageName,
-                    checkProfiles, targetCompilerFilter, forceCompilation);
+            boolean result = secondaryDex
+                    ? mInterface.performDexOptSecondary(packageName,
+                            targetCompilerFilter, forceCompilation)
+                    : mInterface.performDexOptMode(packageName,
+                            checkProfiles, targetCompilerFilter, forceCompilation);
             if (!result) {
                 failedPackages.add(packageName);
             }
@@ -1515,6 +1522,7 @@
         }
         pw.println("      --reset: restore package to its post-install state");
         pw.println("      --check-prof (true | false): look at profiles when doing dexopt?");
+        pw.println("      --secondary-dex: copmile app secondary dex files");
         pw.println("  list features");
         pw.println("    Prints all features of the system.");
         pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index e809213..c5f1646 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,13 +16,16 @@
 
 package com.android.server.pm.dex;
 
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageParser;
-import android.content.pm.ApplicationInfo;
-
+import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.pm.PackageDexOptimizer;
 import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.PackageManagerServiceCompilerMapping;
 
 import java.io.File;
 import java.io.IOException;
@@ -32,6 +35,9 @@
 import java.util.Map;
 import java.util.Set;
 
+import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
 /**
  * This class keeps track of how dex files are used.
  * Every time it gets a notification about a dex file being loaded it tracks
@@ -54,15 +60,20 @@
     // encode and save the dex usage data.
     private final PackageDexUsage mPackageDexUsage;
 
+    private final IPackageManager mPackageManager;
+    private final PackageDexOptimizer mPackageDexOptimizer;
+
     // Possible outcomes of a dex search.
     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
     private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
     private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
     private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
 
-    public DexManager() {
+    public DexManager(IPackageManager pms, PackageDexOptimizer pdo) {
       mPackageCodeLocationsCache = new HashMap<>();
       mPackageDexUsage = new PackageDexUsage();
+      mPackageManager = pms;
+      mPackageDexOptimizer = pdo;
     }
 
     /**
@@ -199,11 +210,62 @@
      * Get the package dex usage for the given package name.
      * @return the package data or null if there is no data available for this package.
      */
-    public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) {
+    public PackageUseInfo getPackageUseInfo(String packageName) {
         return mPackageDexUsage.getPackageUseInfo(packageName);
     }
 
     /**
+     * Perform dexopt on the package {@code packageName} secondary dex files.
+     * @return true if all secondary dex files were processed successfully (compiled or skipped
+     *         because they don't need to be compiled)..
+     */
+    public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
+        // Select the dex optimizer based on the force parameter.
+        // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
+        // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
+        // passing the force flag through the multitude of layers.
+        // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+        //       allocate an object here.
+        PackageDexOptimizer pdo = force
+                ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
+                : mPackageDexOptimizer;
+        PackageUseInfo useInfo = getPackageUseInfo(packageName);
+        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+            if (DEBUG) {
+                Slog.d(TAG, "No secondary dex use for package:" + packageName);
+            }
+            // Nothing to compile, return true.
+            return true;
+        }
+        boolean success = true;
+        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
+            String dexPath = entry.getKey();
+            DexUseInfo dexUseInfo = entry.getValue();
+            PackageInfo pkg = null;
+            try {
+                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
+                    dexUseInfo.getOwnerUserId());
+            } catch (RemoteException e) {
+                throw new AssertionError(e);
+            }
+            // It may be that the package gets uninstalled while we try to compile its
+            // secondary dex files. If that's the case, just ignore.
+            // Note that we don't break the entire loop because the package might still be
+            // installed for other users.
+            if (pkg == null) {
+                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
+                        + " for user " + dexUseInfo.getOwnerUserId());
+                // Skip over it, another user might still have the package.
+                continue;
+            }
+            int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
+                    dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
+            success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
+        }
+        return success;
+    }
+
+    /**
      * Retrieves the package which owns the given dexPath.
      */
     private DexSearchResult getDexPackage(
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 2d07e65..4d76312 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -73,8 +73,7 @@
         mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0);
         mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1);
 
-
-        mDexManager = new DexManager();
+        mDexManager = new DexManager(null, null);
 
         // Foo and Bar are available to user0.
         // Only Bar is available to user1;