Frameworks/base: Add A/B OTA preopting

Add an OTA Dexopt service. Refactor package manager service and
package dex optimizer to reuse some code. Add knowledge about
OTA flag to installer.

Bug: 25612095
Change-Id: I7dd6bb468fea44b9d3acf0ac7d7404fb02d0f30a
diff --git a/Android.mk b/Android.mk
index ad164e20..d68da97 100644
--- a/Android.mk
+++ b/Android.mk
@@ -136,6 +136,7 @@
 	core/java/android/content/ISyncStatusObserver.aidl \
 	core/java/android/content/pm/ILauncherApps.aidl \
 	core/java/android/content/pm/IOnAppsChangedListener.aidl \
+	core/java/android/content/pm/IOtaDexopt.aidl \
 	core/java/android/content/pm/IPackageDataObserver.aidl \
 	core/java/android/content/pm/IPackageDeleteObserver.aidl \
 	core/java/android/content/pm/IPackageDeleteObserver2.aidl \
diff --git a/core/java/android/content/pm/IOtaDexopt.aidl b/core/java/android/content/pm/IOtaDexopt.aidl
new file mode 100644
index 0000000..8f38d6f
--- /dev/null
+++ b/core/java/android/content/pm/IOtaDexopt.aidl
@@ -0,0 +1,49 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content.pm;
+
+/**
+ * A/B OTA dexopting service.
+ *
+ * {@hide}
+ */
+interface IOtaDexopt {
+    /**
+     * Prepare for A/B OTA dexopt. Initialize internal structures.
+     *
+     * Calls to the other methods are only valid after a call to prepare. You may not call
+     * prepare twice without a cleanup call.
+     */
+    void prepare();
+
+    /**
+     * Clean up all internal state.
+     */
+    void cleanup();
+
+    /**
+     * Check whether all updates have been performed.
+     */
+    boolean isDone();
+
+    /**
+     * Optimize the next package. Note: this command is synchronous, that is, only returns after
+     * the package has been dexopted (or dexopting failed).
+     */
+    void dexoptNextPackage();
+}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 5d97afa..dac89ec 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -49,6 +49,8 @@
     public static final int DEXOPT_BOOTCOMPLETE = 1 << 4;
     /** Do not compile, only extract bytecode into an OAT file */
     public static final int DEXOPT_EXTRACTONLY  = 1 << 5;
+    /** This is an OTA update dexopt */
+    public static final int DEXOPT_OTA          = 1 << 6;
 
     /** @hide */
     @IntDef(flag = true, value = {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
new file mode 100644
index 0000000..da62a2d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IOtaDexopt;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
+import android.content.pm.ResolveInfo;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import dalvik.system.DexFile;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static com.android.server.pm.Installer.DEXOPT_OTA;
+
+/**
+ * A service for A/B OTA dexopting.
+ *
+ * {@hide}
+ */
+public class OtaDexoptService extends IOtaDexopt.Stub {
+    private final static String TAG = "OTADexopt";
+    private final static boolean DEBUG_DEXOPT = true;
+    // Apps used in the last 7 days.
+    private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60;
+
+    private final Context mContext;
+    private final PackageDexOptimizer mPackageDexOptimizer;
+    private final PackageManagerService mPackageManagerService;
+
+    // TODO: Evaluate the need for WeakReferences here.
+    private List<PackageParser.Package> mDexoptPackages;
+
+    public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
+        this.mContext = context;
+        this.mPackageManagerService = packageManagerService;
+
+        // Use the package manager install and install lock here for the OTA dex optimizer.
+        mPackageDexOptimizer = new OTADexoptPackageDexOptimizer(packageManagerService.mInstaller,
+                packageManagerService.mInstallLock, context);
+    }
+
+    public static OtaDexoptService main(Context context,
+            PackageManagerService packageManagerService) {
+        OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
+        ServiceManager.addService("otadexopt", ota);
+
+        return ota;
+    }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ResultReceiver resultReceiver) throws RemoteException {
+        (new OtaDexoptShellCommand(this)).exec(
+                this, in, out, err, args, resultReceiver);
+    }
+
+    @Override
+    public synchronized void prepare() throws RemoteException {
+        if (mDexoptPackages != null) {
+            throw new IllegalStateException("already called prepare()");
+        }
+
+        mDexoptPackages = new LinkedList<>();
+
+        ArrayList<PackageParser.Package> pkgs;
+        synchronized (mPackageManagerService.mPackages) {
+            pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values());
+        }
+
+        // Sort apps by importance for dexopt ordering. Important apps are given more priority
+        // in case the device runs out of space.
+
+        // Give priority to core apps.
+        for (PackageParser.Package pkg : pkgs) {
+            if (pkg.coreApp) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName);
+                }
+                mDexoptPackages.add(pkg);
+            }
+        }
+        pkgs.removeAll(mDexoptPackages);
+
+        // Give priority to system apps that listen for pre boot complete.
+        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+        ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
+        for (PackageParser.Package pkg : pkgs) {
+            if (pkgNames.contains(pkg.packageName)) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " +
+                            pkg.packageName);
+                }
+                mDexoptPackages.add(pkg);
+            }
+        }
+        pkgs.removeAll(mDexoptPackages);
+
+        // Filter out packages that aren't recently used, add all remaining apps.
+        // TODO: add a property to control this?
+        if (mPackageManagerService.isHistoricalPackageUsageAvailable()) {
+            filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000);
+        }
+        mDexoptPackages.addAll(pkgs);
+
+        // Now go ahead and also add the libraries required for these packages.
+        // TODO: Think about interleaving things.
+        Set<PackageParser.Package> dependencies = new HashSet<>();
+        for (PackageParser.Package p : mDexoptPackages) {
+            dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p));
+        }
+        if (!dependencies.isEmpty()) {
+            dependencies.removeAll(mDexoptPackages);
+        }
+        mDexoptPackages.addAll(dependencies);
+
+        if (DEBUG_DEXOPT) {
+            StringBuilder sb = new StringBuilder();
+            for (PackageParser.Package pkg : mDexoptPackages) {
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append(pkg.packageName);
+            }
+            Log.i(TAG, "Packages to be optimized: " + sb.toString());
+        }
+    }
+
+    @Override
+    public synchronized void cleanup() throws RemoteException {
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "Cleaning up OTA Dexopt state.");
+        }
+        mDexoptPackages = null;
+    }
+
+    @Override
+    public synchronized boolean isDone() throws RemoteException {
+        if (mDexoptPackages == null) {
+            throw new IllegalStateException("done() called before prepare()");
+        }
+
+        return mDexoptPackages.isEmpty();
+    }
+
+    @Override
+    public synchronized void dexoptNextPackage() throws RemoteException {
+        if (mDexoptPackages == null) {
+            throw new IllegalStateException("dexoptNextPackage() called before prepare()");
+        }
+        if (mDexoptPackages.isEmpty()) {
+            // Tolerate repeated calls.
+            return;
+        }
+
+        PackageParser.Package nextPackage = mDexoptPackages.remove(0);
+
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt.");
+        }
+
+        // Check for low space.
+        // TODO: If apps are not installed in the internal /data partition, we should compare
+        //       against that storage's free capacity.
+        File dataDir = Environment.getDataDirectory();
+        long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
+        if (lowThreshold == 0) {
+            throw new IllegalStateException("Invalid low memory threshold");
+        }
+        long usableSpace = dataDir.getUsableSpace();
+        if (usableSpace < lowThreshold) {
+            Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " +
+                    usableSpace);
+            return;
+        }
+
+        mPackageDexOptimizer.performDexOpt(nextPackage, null /* ISAs */, false /* useProfiles */,
+                false /* extractOnly */);
+    }
+
+    private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
+        List<ResolveInfo> ris = null;
+        try {
+            ris = AppGlobals.getPackageManager().queryIntentReceivers(
+                    intent, null, 0, userId);
+        } catch (RemoteException e) {
+        }
+        ArraySet<String> pkgNames = new ArraySet<String>(ris == null ? 0 : ris.size());
+        if (ris != null) {
+            for (ResolveInfo ri : ris) {
+                pkgNames.add(ri.activityInfo.packageName);
+            }
+        }
+        return pkgNames;
+    }
+
+    private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs,
+            long dexOptLRUThresholdInMills) {
+        // Filter out packages that aren't recently used.
+        int total = pkgs.size();
+        int skipped = 0;
+        long now = System.currentTimeMillis();
+        for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
+            PackageParser.Package pkg = i.next();
+            long then = pkg.mLastPackageUsageTimeInMills;
+            if (then + dexOptLRUThresholdInMills < now) {
+                if (DEBUG_DEXOPT) {
+                    Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
+                          ((then == 0) ? "never" : new Date(then)));
+                }
+                i.remove();
+                skipped++;
+            }
+        }
+        if (DEBUG_DEXOPT) {
+            Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
+        }
+    }
+
+    private static class OTADexoptPackageDexOptimizer extends
+            PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
+
+        public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
+                Context context) {
+            super(installer, installLock, context, "*otadexopt*");
+        }
+
+        @Override
+        protected int adjustDexoptFlags(int dexoptFlags) {
+            // Add the OTA flag.
+            return dexoptFlags | DEXOPT_OTA;
+        }
+
+        @Override
+        protected void recordSuccessfulDexopt(Package pkg, String instructionSet) {
+            // Never record the dexopt, as it's in the B partition.
+        }
+
+    }
+}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
new file mode 100644
index 0000000..ea9cf17
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.IOtaDexopt;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+class OtaDexoptShellCommand extends ShellCommand {
+    final IOtaDexopt mInterface;
+
+    OtaDexoptShellCommand(OtaDexoptService service) {
+        mInterface = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(null);
+        }
+
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch(cmd) {
+                case "prepare":
+                    return runOtaPrepare();
+                case "cleanup":
+                    return runOtaCleanup();
+                case "done":
+                    return runOtaDone();
+                case "step":
+                    return runOtaStep();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private int runOtaPrepare() throws RemoteException {
+        mInterface.prepare();
+        getOutPrintWriter().println("Success");
+        return 0;
+    }
+
+    private int runOtaCleanup() throws RemoteException {
+        mInterface.cleanup();
+        return 0;
+    }
+
+    private int runOtaDone() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        if (mInterface.isDone()) {
+            pw.println("OTA complete.");
+        } else {
+            pw.println("OTA incomplete.");
+        }
+        return 0;
+    }
+
+    private int runOtaStep() throws RemoteException {
+        mInterface.dexoptNextPackage();
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("OTA Dexopt (ota) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("");
+        pw.println("  prepare");
+        pw.println("    Prepare an OTA dexopt pass, collecting all packages.");
+        pw.println("  done");
+        pw.println("    Replies whether the OTA is complete or not.");
+        pw.println("  step");
+        pw.println("    OTA dexopt the next package.");
+        pw.println("  cleanup");
+        pw.println("    Clean up internal states. Ends an OTA session.");
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fe0f141..64af213 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.WorkSource;
@@ -48,7 +49,7 @@
 /**
  * Helper class for running dexopt command on packages.
  */
-final class PackageDexOptimizer {
+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
@@ -57,16 +58,28 @@
     static final int DEX_OPT_DEFERRED = 2;
     static final int DEX_OPT_FAILED = -1;
 
-    private final PackageManagerService mPackageManagerService;
+    private static final boolean DEBUG_DEXOPT = PackageManagerService.DEBUG_DEXOPT;
+
+    private final Installer mInstaller;
+    private final Object mInstallLock;
 
     private final PowerManager.WakeLock mDexoptWakeLock;
     private volatile boolean mSystemReady;
 
-    PackageDexOptimizer(PackageManagerService packageManagerService) {
-        this.mPackageManagerService = packageManagerService;
-        PowerManager powerManager = (PowerManager)packageManagerService.mContext.getSystemService(
-                Context.POWER_SERVICE);
-        mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*dexopt*");
+    PackageDexOptimizer(Installer installer, Object installLock, Context context,
+            String wakeLockTag) {
+        this.mInstaller = installer;
+        this.mInstallLock = installLock;
+
+        PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
+    }
+
+    protected PackageDexOptimizer(PackageDexOptimizer from) {
+        this.mInstaller = from.mInstaller;
+        this.mInstallLock = from.mInstallLock;
+        this.mDexoptWakeLock = from.mDexoptWakeLock;
+        this.mSystemReady = from.mSystemReady;
     }
 
     static boolean canOptimizePackage(PackageParser.Package pkg) {
@@ -77,27 +90,19 @@
      * Performs dexopt on all code paths and libraries of the specified package for specified
      * instruction sets.
      *
-     * <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on
-     * {@link PackageManagerService#mInstallLock}.
+     * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
+     * synchronized on {@link #mInstallLock}.
      */
-    int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
-            boolean inclDependencies, boolean useProfiles, boolean extractOnly, boolean force) {
-        ArraySet<String> done;
-        if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
-            done = new ArraySet<String>();
-            done.add(pkg.packageName);
-        } else {
-            done = null;
-        }
-        synchronized (mPackageManagerService.mInstallLock) {
+    int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean useProfiles,
+            boolean extractOnly) {
+        synchronized (mInstallLock) {
             final boolean useLock = mSystemReady;
             if (useLock) {
                 mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
                 mDexoptWakeLock.acquire();
             }
             try {
-                return performDexOptLI(pkg, instructionSets, done, useProfiles,
-                        extractOnly, force);
+                return performDexOptLI(pkg, instructionSets, useProfiles, extractOnly);
             } finally {
                 if (useLock) {
                     mDexoptWakeLock.release();
@@ -106,21 +111,42 @@
         }
     }
 
+    /**
+     * Determine whether the package should be skipped for the given instruction set. A return
+     * value of true means the package will be skipped. A return value of false means that the
+     * package will be further investigated, and potentially compiled.
+     */
+    protected boolean shouldSkipBasedOnISA(PackageParser.Package pkg, String instructionSet) {
+        return pkg.mDexOptPerformed.contains(instructionSet);
+    }
+
+    /**
+     * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
+     * optimize or not (and in what way).
+     */
+    protected int adjustDexoptNeeded(int dexoptNeeded) {
+        return dexoptNeeded;
+    }
+
+    /**
+     * Adjust the given dexopt flags that will be passed to the installer.
+     */
+    protected int adjustDexoptFlags(int dexoptFlags) {
+        return dexoptFlags;
+    }
+
+    /**
+     * Update the package status after a successful compilation.
+     */
+    protected void recordSuccessfulDexopt(PackageParser.Package pkg, String instructionSet) {
+        pkg.mDexOptPerformed.add(instructionSet);
+    }
+
     private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
-            ArraySet<String> done, boolean useProfiles, boolean extractOnly, boolean force) {
+            boolean useProfiles, boolean extractOnly) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
 
-        if (done != null) {
-            done.add(pkg.packageName);
-            if (pkg.usesLibraries != null) {
-                performDexOptLibsLI(pkg.usesLibraries, instructionSets, done);
-            }
-            if (pkg.usesOptionalLibraries != null) {
-                performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, done);
-            }
-        }
-
         if (!canOptimizePackage(pkg)) {
             return DEX_OPT_SKIPPED;
         }
@@ -128,34 +154,26 @@
         final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
         final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
 
-        if (useProfiles) {
-            // If we do a profile guided compilation then we might recompile
-            // the same package if more profile information is available.
-            force = true;
-        }
-
         final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
         boolean performedDexOpt = false;
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
         for (String dexCodeInstructionSet : dexCodeInstructionSets) {
-            if (!force && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+            if (!useProfiles && shouldSkipBasedOnISA(pkg, dexCodeInstructionSet)) {
+                // Skip only if we do not use profiles since they might trigger a recompilation.
                 continue;
             }
 
             for (String path : paths) {
                 int dexoptNeeded;
 
-                if (force) {
-                    dexoptNeeded = DexFile.DEX2OAT_NEEDED;
-                } else {
-                    try {
-                        dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
-                                dexCodeInstructionSet, /* defer */false);
-                    } catch (IOException ioe) {
-                        Slog.w(TAG, "IOException reading apk: " + path, ioe);
-                        return DEX_OPT_FAILED;
-                    }
+                try {
+                    dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
+                            dexCodeInstructionSet, /* defer */false);
+                } catch (IOException ioe) {
+                    Slog.w(TAG, "IOException reading apk: " + path, ioe);
+                    return DEX_OPT_FAILED;
                 }
+                dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
 
                 if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
                     // No dexopt needed and we don't use profiles. Nothing to do.
@@ -174,21 +192,22 @@
                     throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
                 }
 
+
                 Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
                         + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
                         + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
                         + " extractOnly=" + extractOnly + " oatDir = " + oatDir);
                 final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
-                final int dexFlags =
+                final int dexFlags = adjustDexoptFlags(
                         (!pkg.isForwardLocked() ? DEXOPT_PUBLIC : 0)
                         | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
                         | (debuggable ? DEXOPT_DEBUGGABLE : 0)
                         | (extractOnly ? DEXOPT_EXTRACTONLY : 0)
-                        | DEXOPT_BOOTCOMPLETE;
+                        | DEXOPT_BOOTCOMPLETE);
+
                 try {
-                    mPackageManagerService.mInstaller.dexopt(path, sharedGid,
-                            pkg.packageName, dexCodeInstructionSet, dexoptNeeded, oatDir,
-                            dexFlags, pkg.volumeUuid, useProfiles);
+                    mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
+                            dexoptNeeded, oatDir, dexFlags, pkg.volumeUuid, useProfiles);
                     performedDexOpt = true;
                 } catch (InstallerException e) {
                     Slog.w(TAG, "Failed to dexopt", e);
@@ -201,7 +220,7 @@
                 // it isn't required. We therefore mark that this package doesn't need dexopt unless
                 // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
                 // it.
-                pkg.mDexOptPerformed.add(dexCodeInstructionSet);
+                recordSuccessfulDexopt(pkg, dexCodeInstructionSet);
             }
         }
 
@@ -232,8 +251,7 @@
         if (codePath.isDirectory()) {
             File oatDir = getOatDir(codePath);
             try {
-                mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
-                        dexInstructionSet);
+                mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
             } catch (InstallerException e) {
                 Slog.w(TAG, "Failed to create oat dir", e);
                 return null;
@@ -247,21 +265,36 @@
         return new File(codePath, OAT_DIR_NAME);
     }
 
-    private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets,
-            ArraySet<String> done) {
-        for (String libName : libs) {
-            PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary(
-                    libName);
-            if (libPkg != null && !done.contains(libName)) {
-                // TODO: Analyze and investigate if we (should) profile libraries.
-                // Currently this will do a full compilation of the library.
-                performDexOptLI(libPkg, instructionSets, done, /*useProfiles*/ false,
-                        /* extractOnly */ false, /* force */ false);
-            }
-        }
-    }
-
     void systemReady() {
         mSystemReady = true;
     }
+
+    /**
+     * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a
+     * dexopt path.
+     */
+    public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer {
+
+        public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
+                Context context, String wakeLockTag) {
+            super(installer, installLock, context, wakeLockTag);
+        }
+
+        public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
+            super(from);
+        }
+
+        @Override
+        protected boolean shouldSkipBasedOnISA(Package pkg, String instructionSet) {
+            // Forced compilation, never skip.
+            return false;
+        }
+
+        @Override
+        protected int adjustDexoptNeeded(int dexoptNeeded) {
+            // Ensure compilation, no matter the current state.
+            // TODO: The return value is wrong when patchoat is needed.
+            return DexFile.DEX2OAT_NEEDED;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b6905b4..2427c5f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -139,6 +139,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.Package;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageStats;
@@ -263,6 +264,7 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
@@ -277,6 +279,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -313,7 +316,12 @@
     private static final boolean DEBUG_INTENT_MATCHING = false;
     private static final boolean DEBUG_PACKAGE_SCANNING = false;
     private static final boolean DEBUG_VERIFY = false;
-    private static final boolean DEBUG_DEXOPT = false;
+
+    // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
+    // and PackageDexOptimizer. All these classes have their own flag to allow switching a single
+    // user, but by default initialize to this.
+    static final boolean DEBUG_DEXOPT = false;
+
     private static final boolean DEBUG_ABI_SELECTION = false;
     private static final boolean DEBUG_EPHEMERAL = false;
     private static final boolean DEBUG_TRIAGED_MISSING = false;
@@ -1990,7 +1998,8 @@
         }
 
         mInstaller = installer;
-        mPackageDexOptimizer = new PackageDexOptimizer(this);
+        mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
+                "*dexopt*");
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -3377,19 +3386,6 @@
         return null;
     }
 
-    /**
-     * @hide
-     */
-    PackageParser.Package findSharedNonSystemLibrary(String libName) {
-        synchronized (mPackages) {
-            PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
-            if (lib != null && lib.apk != null) {
-                return mPackages.get(lib.apk);
-            }
-        }
-        return null;
-    }
-
     @Override
     public FeatureInfo[] getSystemAvailableFeatures() {
         Collection<FeatureInfo> featSet;
@@ -6718,8 +6714,8 @@
         try {
             synchronized (mInstallLock) {
                 final String[] instructionSets = new String[] { targetInstructionSet };
-                int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
-                        true /* inclDependencies */, useProfiles, extractOnly, force);
+                int result = performDexOptInternalWithDependenciesLI(p, instructionSets,
+                        useProfiles, extractOnly, force);
                 return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
             }
         } finally {
@@ -6739,6 +6735,80 @@
         return pkgs;
     }
 
+    private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
+            String instructionSets[], boolean useProfiles, boolean extractOnly, boolean force) {
+        // Select the dex optimizer based on the force parameter.
+        // 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;
+
+        // Optimize all dependencies first. Note: we ignore the return value and march on
+        // on errors.
+        Collection<PackageParser.Package> deps = findSharedNonSystemLibraries(p);
+        if (!deps.isEmpty()) {
+            for (PackageParser.Package depPackage : deps) {
+                // TODO: Analyze and investigate if we (should) profile libraries.
+                // Currently this will do a full compilation of the library.
+                pdo.performDexOpt(depPackage, instructionSets, false /* useProfiles */,
+                        false /* extractOnly */);
+            }
+        }
+
+        return pdo.performDexOpt(p, instructionSets, useProfiles, extractOnly);
+    }
+
+    Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
+        if (p.usesLibraries != null || p.usesOptionalLibraries != null) {
+            ArrayList<PackageParser.Package> retValue = new ArrayList<>();
+            Set<String> collectedNames = new HashSet<>();
+            findSharedNonSystemLibrariesRecursive(p, retValue, collectedNames);
+
+            retValue.remove(p);
+
+            return retValue;
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    private void findSharedNonSystemLibrariesRecursive(PackageParser.Package p,
+            Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+        if (!collectedNames.contains(p.packageName)) {
+            collectedNames.add(p.packageName);
+            collected.add(p);
+
+            if (p.usesLibraries != null) {
+                findSharedNonSystemLibrariesRecursive(p.usesLibraries, collected, collectedNames);
+            }
+            if (p.usesOptionalLibraries != null) {
+                findSharedNonSystemLibrariesRecursive(p.usesOptionalLibraries, collected,
+                        collectedNames);
+            }
+        }
+    }
+
+    private void findSharedNonSystemLibrariesRecursive(Collection<String> libs,
+            Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+        for (String libName : libs) {
+            PackageParser.Package libPkg = findSharedNonSystemLibrary(libName);
+            if (libPkg != null) {
+                findSharedNonSystemLibrariesRecursive(libPkg, collected, collectedNames);
+            }
+        }
+    }
+
+    private PackageParser.Package findSharedNonSystemLibrary(String libName) {
+        synchronized (mPackages) {
+            PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
+            if (lib != null && lib.apk != null) {
+                return mPackages.get(lib.apk);
+            }
+        }
+        return null;
+    }
+
     public void shutdown() {
         mPackageUsage.write(true);
     }
@@ -6763,9 +6833,8 @@
 
             // Whoever is calling forceDexOpt wants a fully compiled package.
             // Don't use profiles since that may cause compilation to be skipped.
-            final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
-                    true /* inclDependencies */, false /* useProfiles */,
-                    false /* extractOnly */, true /* force */);
+            final int res = performDexOptInternalWithDependenciesLI(pkg, instructionSets,
+                    false /* useProfiles */, false /* extractOnly */, true /* force */);
 
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -13032,8 +13101,7 @@
                 // Do not run PackageDexOptimizer through the local performDexOpt
                 // method because `pkg` is not in `mPackages` yet.
                 int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */,
-                        false /* inclDependencies */, false /* useProfiles */,
-                        true /* extractOnly */, false /* force */);
+                        false /* useProfiles */, true /* extractOnly */);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
                     String msg = "Extracking package failed for " + pkgName;
@@ -18033,4 +18101,8 @@
                     "Cannot call " + tag + " from UID " + callingUid);
         }
     }
+
+    boolean isHistoricalPackageUsageAvailable() {
+        return mPackageUsage.isHistoricalPackageUsageAvailable();
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 254a37a..b64db57 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -80,6 +80,7 @@
 import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.Installer;
 import com.android.server.pm.LauncherAppsService;
+import com.android.server.pm.OtaDexoptService;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerService;
 import com.android.server.power.PowerManagerService;
@@ -1097,6 +1098,18 @@
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
+                // Manages A/B OTA dexopting.
+                boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
+                        false);
+                if (!disableOtaDexopt) {
+                    traceBeginAndSlog("StartOtaDexOptService");
+                    try {
+                        OtaDexoptService.main(mSystemContext, mPackageManagerService);
+                    } catch (Throwable e) {
+                        reportWtf("starting BackgroundDexOptService", e);
+                    }
+                    Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+                }
             }
 
             mSystemServiceManager.startService(LauncherAppsService.class);