Merge "update dexopt to support cancellation"
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 8fd545f..7e002bf 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -487,9 +487,42 @@
         }
     }
 
-    public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
+    /**
+     * Runs dex optimization.
+     *
+     * @param apkPath Path of target APK
+     * @param uid UID of the package
+     * @param pkgName Name of the package
+     * @param instructionSet Target instruction set to run dex optimization.
+     * @param dexoptNeeded Necessary dex optimization for this request. Check
+     *        {@link dalvik.system.DexFile#NO_DEXOPT_NEEDED},
+     *        {@link dalvik.system.DexFile#DEX2OAT_FROM_SCRATCH},
+     *        {@link dalvik.system.DexFile#DEX2OAT_FOR_BOOT_IMAGE}, and
+     *        {@link dalvik.system.DexFile#DEX2OAT_FOR_FILTER}.
+     * @param outputPath Output path of generated dex optimization.
+     * @param dexFlags Check {@code DEXOPT_*} for allowed flags.
+     * @param compilerFilter Compiler filter like "verify", "speed-profile". Check
+     *                       {@code art/libartbase/base/compiler_filter.cc} for full list.
+     * @param volumeUuid UUID of the volume where the package data is stored. {@code null}
+     *                   represents internal storage.
+     * @param classLoaderContext This encodes the class loader chain (class loader type + class
+     *                           path) in a format compatible to dex2oat. Check
+     *                           {@code DexoptUtils.processContextForDexLoad} for further details.
+     * @param seInfo Selinux context to set for generated outputs.
+     * @param downgrade If set, allows downgrading {@code compilerFilter}. If downgrading is not
+     *                  allowed and requested {@code compilerFilter} is considered as downgrade,
+     *                  the request will be ignored.
+     * @param targetSdkVersion Target SDK version of the package.
+     * @param profileName Name of reference profile file.
+     * @param dexMetadataPath Specifies the location of dex metadata file.
+     * @param compilationReason Specifies the reason for the compilation like "install".
+     * @return {@code true} if {@code dexopt} is completed. {@code false} if it was cancelled.
+     *
+     * @throws InstallerException if {@code dexopt} fails.
+     */
+    public boolean dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
-            String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
+            String compilerFilter, @Nullable String volumeUuid, @Nullable String classLoaderContext,
             @Nullable String seInfo, boolean downgrade, int targetSdkVersion,
             @Nullable String profileName, @Nullable String dexMetadataPath,
             @Nullable String compilationReason) throws InstallerException {
@@ -497,10 +530,10 @@
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
         BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
-        if (!checkBeforeRemote()) return;
+        if (!checkBeforeRemote()) return false;
         try {
-            mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
-                    dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
+            return mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
+                    dexFlags, compilerFilter, volumeUuid, classLoaderContext, seInfo, downgrade,
                     targetSdkVersion, profileName, dexMetadataPath, compilationReason);
         } catch (Exception e) {
             throw InstallerException.from(e);
@@ -508,6 +541,23 @@
     }
 
     /**
+     * Enables or disables dex optimization blocking.
+     *
+     * <p> Enabling blocking will also involve cancelling pending dexopt call and killing child
+     * processes forked from installd to run dexopt. The pending dexopt call will return false
+     * when it is cancelled.
+     *
+     * @param block set to true to enable blocking / false to disable blocking.
+     */
+    public void controlDexOptBlocking(boolean block) {
+        try {
+            mInstalld.controlDexOptBlocking(block);
+        } catch (Exception e) {
+            Slog.w(TAG, "blockDexOpt failed", e);
+        }
+    }
+
+    /**
      * Analyzes the ART profiles of the given package, possibly merging the information
      * into the reference profile. Returns whether or not we should optimize the package
      * based on how much information is in the profile.
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index f968daf..862cb6f 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -288,7 +288,7 @@
              *       frameworks/native/cmds/installd/otapreopt.cpp.
              */
             @Override
-            public void dexopt(String apkPath, int uid, @Nullable String pkgName,
+            public boolean dexopt(String apkPath, int uid, @Nullable String pkgName,
                     String instructionSet, int dexoptNeeded, @Nullable String outputPath,
                     int dexFlags, String compilerFilter, @Nullable String volumeUuid,
                     @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
@@ -320,6 +320,9 @@
                 encodeParameter(builder, dexoptCompilationReason);
 
                 commands.add(builder.toString());
+
+                // Cancellation cannot happen for OtaDexOpt. Returns true always.
+                return true;
             }
 
             /**
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index dd22fd6..7739f2f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -43,6 +43,7 @@
 import static dalvik.system.DexFile.getSafeModeCompilerFilter;
 import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -79,6 +80,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -92,16 +95,40 @@
     private static final String TAG = "PackageDexOptimizer";
     static final String OAT_DIR_NAME = "oat";
     // TODO b/19550105 Remove error codes and use exceptions
+    /** No need to run dexopt and it was skipped */
     public static final int DEX_OPT_SKIPPED = 0;
+    /** Dexopt was completed */
     public static final int DEX_OPT_PERFORMED = 1;
+    /**
+     * Cancelled while running it. This is not an error case as cancel was requested
+     * from the client.
+     */
+    public static final int DEX_OPT_CANCELLED = 2;
+    /** Failed to run dexopt */
     public static final int DEX_OPT_FAILED = -1;
+
+    @IntDef(prefix = {"DEX_OPT_"}, value = {
+            DEX_OPT_SKIPPED,
+            DEX_OPT_PERFORMED,
+            DEX_OPT_CANCELLED,
+            DEX_OPT_FAILED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DexOptResult {
+    }
+
     // One minute over PM WATCHDOG_TIMEOUT
     private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
 
-    @GuardedBy("mInstallLock")
-    private final Installer mInstaller;
     private final Object mInstallLock;
 
+    /**
+     * This should be accessed only through {@link #getInstallerLI()} with {@link #mInstallLock}
+     * or {@link #getInstallerWithoutLock()} without the lock. Check both methods for further
+     * details on when to use each of them.
+     */
+    private final Installer mInstaller;
+
     @GuardedBy("mInstallLock")
     private final PowerManager.WakeLock mDexoptWakeLock;
     private volatile boolean mSystemReady;
@@ -144,6 +171,7 @@
      * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
      * synchronized on {@link #mInstallLock}.
      */
+    @DexOptResult
     int performDexOpt(AndroidPackage pkg, @NonNull PackageSetting pkgSetting,
             String[] instructionSets, CompilerStats.PackageStats packageStats,
             PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
@@ -170,10 +198,20 @@
     }
 
     /**
+     * Cancels currently running dex optimization.
+     */
+    void controlDexOptBlocking(boolean block) {
+        // This method should not hold mInstallLock as cancelling should be possible while
+        // the lock is held by other thread running performDexOpt.
+        getInstallerWithoutLock().controlDexOptBlocking(block);
+    }
+
+    /**
      * Performs dexopt on all code paths of the given package.
      * It assumes the install lock is held.
      */
     @GuardedBy("mInstallLock")
+    @DexOptResult
     private int performDexOptLI(AndroidPackage pkg, @NonNull PackageSetting pkgSetting,
             String[] targetInstructionSets, CompilerStats.PackageStats packageStats,
             PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
@@ -269,7 +307,6 @@
                         profileAnalysisResult, classLoaderContexts[i], dexoptFlags, sharedGid,
                         packageStats, options.isDowngrade(), profileName, dexMetadataPath,
                         options.getCompilationReason());
-
                 // OTAPreopt doesn't have stats so don't report in that case.
                 if (packageStats != null) {
                     Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics");
@@ -293,6 +330,14 @@
                     }
                 }
 
+                // Should stop the operation immediately.
+                if (newResult == DEX_OPT_CANCELLED) {
+                    // Even for the cancellation, return failed if has failed.
+                    if (result == DEX_OPT_FAILED) {
+                        return result;
+                    }
+                    return newResult;
+                }
                 // The end result is:
                 //  - FAILED if any path failed,
                 //  - PERFORMED if at least one path needed compilation,
@@ -314,6 +359,7 @@
      *      DEX_OPT_SKIPPED if the path does not need to be deopt-ed.
      */
     @GuardedBy("mInstallLock")
+    @DexOptResult
     private int dexOptPath(AndroidPackage pkg, @NonNull PackageSetting pkgSetting, String path,
             String isa, String compilerFilter, int profileAnalysisResult, String classLoaderContext,
             int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
@@ -340,12 +386,14 @@
             // installd only uses downgrade flag for secondary dex files and ignores it for
             // primary dex files.
             String seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
-            mInstaller.dexopt(path, uid, pkg.getPackageName(), isa, dexoptNeeded, oatDir,
-                    dexoptFlags, compilerFilter, pkg.getVolumeUuid(), classLoaderContext,
-                    seInfo, false /* downgrade*/, pkg.getTargetSdkVersion(),
-                    profileName, dexMetadataPath,
+            boolean completed = getInstallerLI().dexopt(path, uid, pkg.getPackageName(), isa,
+                    dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.getVolumeUuid(),
+                    classLoaderContext, seInfo, /* downgrade= */ false ,
+                    pkg.getTargetSdkVersion(), profileName, dexMetadataPath,
                     getAugmentedReasonName(compilationReason, dexMetadataPath != null));
-
+            if (!completed) {
+                return DEX_OPT_CANCELLED;
+            }
             if (packageStats != null) {
                 long endTime = System.currentTimeMillis();
                 packageStats.setCompileTime(path, (int)(endTime - startTime));
@@ -360,6 +408,7 @@
     /**
      * Perform dexopt (if needed) on a system server code path).
      */
+    @DexOptResult
     public int dexoptSystemServerPath(
             String dexPath, PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
         int dexoptFlags = DEXOPT_PUBLIC
@@ -380,23 +429,28 @@
                 continue;
             }
             try {
-                mInstaller.dexopt(
-                        dexPath,
-                        android.os.Process.SYSTEM_UID,
-                        /* packageName= */ "android",
-                        isa,
-                        dexoptNeeded,
-                        /* oatDir= */ null,
-                        dexoptFlags,
-                        options.getCompilerFilter(),
-                        StorageManager.UUID_PRIVATE_INTERNAL,
-                        dexUseInfo.getClassLoaderContext(),
-                        /* seInfo= */ null,
-                        /* downgrade= */ false ,
-                        /* targetSdk= */ 0,
-                        /* profileName */ null,
-                        /* dexMetadataPath */ null,
-                        getReasonName(options.getCompilationReason()));
+                synchronized (mInstallLock) {
+                    boolean completed = getInstallerLI().dexopt(
+                            dexPath,
+                            android.os.Process.SYSTEM_UID,
+                            /* pkgName= */ "android",
+                            isa,
+                            dexoptNeeded,
+                            /* outputPath= */ null,
+                            dexoptFlags,
+                            options.getCompilerFilter(),
+                            StorageManager.UUID_PRIVATE_INTERNAL,
+                            dexUseInfo.getClassLoaderContext(),
+                            /* seInfo= */ null,
+                            /* downgrade= */ false,
+                            /* targetSdkVersion= */ 0,
+                            /* profileName= */ null,
+                            /* dexMetadataPath= */ null,
+                            getReasonName(options.getCompilationReason()));
+                    if (!completed) {
+                        return DEX_OPT_CANCELLED;
+                    }
+                }
             } catch (InstallerException e) {
                 Slog.w(TAG, "Failed to dexopt", e);
                 return DEX_OPT_FAILED;
@@ -426,6 +480,7 @@
      * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
      * that seems wasteful.
      */
+    @DexOptResult
     public int dexOptSecondaryDexPath(ApplicationInfo info, String path,
             PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
         if (info.uid == -1) {
@@ -475,6 +530,7 @@
     }
 
     @GuardedBy("mInstallLock")
+    @DexOptResult
     private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
             PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
         if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
@@ -523,11 +579,15 @@
                 // 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,
+                boolean completed = getInstallerLI().dexopt(path, info.uid, info.packageName,
+                        isa, /* dexoptNeeded= */ 0,
+                        /* outputPath= */ null, dexoptFlags,
                         compilerFilter, info.volumeUuid, classLoaderContext, info.seInfo,
-                        options.isDowngrade(), info.targetSdkVersion, /*profileName*/ null,
-                        /*dexMetadataPath*/ null, getReasonName(reason));
+                        options.isDowngrade(), info.targetSdkVersion, /* profileName= */ null,
+                        /* dexMetadataPath= */ null, getReasonName(reason));
+                if (!completed) {
+                    return DEX_OPT_CANCELLED;
+                }
             }
 
             return DEX_OPT_PERFORMED;
@@ -810,7 +870,9 @@
         }
         // Merge profiles. It returns whether or not there was an updated in the profile info.
         try {
-            return mInstaller.mergeProfiles(uid, pkg.getPackageName(), profileName);
+            synchronized (mInstallLock) {
+                return getInstallerLI().mergeProfiles(uid, pkg.getPackageName(), profileName);
+            }
         } catch (InstallerException e) {
             Slog.w(TAG, "Failed to merge profiles", e);
             // We don't need to optimize if we failed to merge.
@@ -921,4 +983,21 @@
             return flags | DEXOPT_FORCE;
         }
     }
+
+    /**
+     * Returns {@link #mInstaller} with {@link #mInstallLock}. This should be used for all
+     * {@link #mInstaller} access unless {@link #getInstallerWithoutLock()} is allowed.
+     */
+    @GuardedBy("mInstallLock")
+    private Installer getInstallerLI() {
+        return mInstaller;
+    }
+
+    /**
+     * Returns {@link #mInstaller} without lock. This should be used only inside
+     * {@link #controlDexOptBlocking(boolean)}.
+     */
+    private Installer getInstallerWithoutLock() {
+        return mInstaller;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 89cb7fe..b2035b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11032,6 +11032,9 @@
                 case PackageDexOptimizer.DEX_OPT_SKIPPED:
                     numberOfPackagesSkipped++;
                     break;
+                case PackageDexOptimizer.DEX_OPT_CANCELLED:
+                    // ignore this case
+                    break;
                 case PackageDexOptimizer.DEX_OPT_FAILED:
                     numberOfPackagesFailed++;
                     break;
@@ -11177,12 +11180,18 @@
         }
     }
 
+    /*package*/ void controlDexOptBlocking(boolean block) {
+        mPackageDexOptimizer.controlDexOptBlocking(block);
+    }
+
     /**
      * Perform dexopt on the given package and return one of following result:
      *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
      *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
+     *  {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
      *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
      */
+    @PackageDexOptimizer.DexOptResult
     /* package */ int performDexOptWithStatus(DexoptOptions options) {
         return performDexOptTraced(options);
     }
@@ -11307,8 +11316,6 @@
         mDexManager.reconcileSecondaryDexFiles(packageName);
     }
 
-    // TODO(calin): this is only needed for BackgroundDexOptService. Find a cleaner way to inject
-    // a reference there.
     /*package*/ DexManager getDexManager() {
         return mDexManager;
     }