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);