diff --git a/core/java/android/content/pm/IOtaDexopt.aidl b/core/java/android/content/pm/IOtaDexopt.aidl index 786a77f641107..467bd5f5e82bd 100644 --- a/core/java/android/content/pm/IOtaDexopt.aidl +++ b/core/java/android/content/pm/IOtaDexopt.aidl @@ -50,6 +50,13 @@ interface IOtaDexopt { /** * Optimize the next package. Note: this command is synchronous, that is, only returns after * the package has been dexopted (or dexopting failed). + * + * Note: this will be removed after a transition period. Use nextDexoptCommand instead. */ void dexoptNextPackage(); + + /** + * Get the optimization parameters for the next package. + */ + String nextDexoptCommand(); } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 7b85a4f25ceee..72c549f7bec6b 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -61,6 +61,13 @@ public final class Installer extends SystemService { mInstaller = new InstallerConnection(); } + // Package-private installer that accepts a custom InstallerConnection. Used for + // OtaDexoptService. + Installer(Context context, InstallerConnection connection) { + super(context); + mInstaller = connection; + } + /** * Yell loudly if someone tries making future calls while holding a lock on * the given object. diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index df91f4a1f62a8..01b3dc28b50e3 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -32,10 +32,12 @@ import android.os.storage.StorageManager; import android.util.Log; import android.util.Slog; +import com.android.internal.os.InstallerConnection; import com.android.internal.os.InstallerConnection.InstallerException; import java.io.File; import java.io.FileDescriptor; +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -49,21 +51,28 @@ public class OtaDexoptService extends IOtaDexopt.Stub { private final static boolean DEBUG_DEXOPT = true; private final Context mContext; - private final PackageDexOptimizer mPackageDexOptimizer; private final PackageManagerService mPackageManagerService; // TODO: Evaluate the need for WeakReferences here. + + /** + * The list of packages to dexopt. + */ private List mDexoptPackages; + + /** + * The list of dexopt invocations for the current package (which will no longer be in + * mDexoptPackages). This can be more than one as a package may have multiple code paths, + * e.g., in the split-APK case. + */ + private List mCommandsForCurrentPackage; + private int completeSize; 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); - // Now it's time to check whether we need to move any A/B artifacts. moveAbArtifacts(packageManagerService.mInstaller); } @@ -93,6 +102,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { mPackageManagerService.mPackages.values(), mPackageManagerService); } completeSize = mDexoptPackages.size(); + mCommandsForCurrentPackage = null; } @Override @@ -101,6 +111,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { Log.i(TAG, "Cleaning up OTA Dexopt state."); } mDexoptPackages = null; + mCommandsForCurrentPackage = null; } @Override @@ -109,15 +120,109 @@ public class OtaDexoptService extends IOtaDexopt.Stub { throw new IllegalStateException("done() called before prepare()"); } - return mDexoptPackages.isEmpty(); + return mDexoptPackages.isEmpty() && (mCommandsForCurrentPackage == null); } @Override public synchronized float getProgress() throws RemoteException { + // We approximate by number of packages here. We could track all compiles, if we + // generated them ahead of time. Right now we're trying to conserve memory. if (completeSize == 0) { return 1f; } - return (completeSize - mDexoptPackages.size()) / ((float)completeSize); + int packagesLeft = mDexoptPackages.size() + (mCommandsForCurrentPackage != null ? 1 : 0); + return (completeSize - packagesLeft) / ((float)completeSize); + } + + /** + * Return the next dexopt command for the current package. Enforces the invariant + */ + private String getNextPackageDexopt() { + if (mCommandsForCurrentPackage != null) { + String next = mCommandsForCurrentPackage.remove(0); + if (mCommandsForCurrentPackage.isEmpty()) { + mCommandsForCurrentPackage = null; + } + return next; + } + return null; + } + + @Override + public synchronized String nextDexoptCommand() throws RemoteException { + if (mDexoptPackages == null) { + throw new IllegalStateException("dexoptNextPackage() called before prepare()"); + } + + // Get the next command. + for (;;) { + // Check whether there's one for the current package. + String next = getNextPackageDexopt(); + if (next != null) { + return next; + } + + // Move to the next package, if possible. + if (mDexoptPackages.isEmpty()) { + return "Nothing to do"; + } + + PackageParser.Package nextPackage = mDexoptPackages.remove(0); + + if (DEBUG_DEXOPT) { + Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt."); + } + + // Generate the next mPackageDexopts state. Ignore errors, this loop is strongly + // monotonically increasing, anyways. + generatePackageDexopts(nextPackage); + + // Invariant check: mPackageDexopts is null or not empty. + if (mCommandsForCurrentPackage != null && mCommandsForCurrentPackage.isEmpty()) { + cleanup(); + throw new IllegalStateException("mPackageDexopts empty for " + nextPackage); + } + } + } + + /** + * Generate all dexopt commands for the given package and place them into mPackageDexopts. + * Returns true on success, false in an error situation like low disk space. + */ + private synchronized boolean generatePackageDexopts(PackageParser.Package nextPackage) { + // 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(); + @SuppressWarnings("deprecation") + 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 false; + } + + // Use our custom connection that just collects the commands. + RecordingInstallerConnection collectingConnection = new RecordingInstallerConnection(); + Installer collectingInstaller = new Installer(mContext, collectingConnection); + + // Use the package manager install and install lock here for the OTA dex optimizer. + PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( + collectingInstaller, mPackageManagerService.mInstallLock, mContext); + optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, + null /* ISAs */, false /* checkProfiles */, + getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA)); + + mCommandsForCurrentPackage = collectingConnection.commands; + if (mCommandsForCurrentPackage.isEmpty()) { + mCommandsForCurrentPackage = null; + } + + return true; } @Override @@ -152,8 +257,10 @@ public class OtaDexoptService extends IOtaDexopt.Stub { return; } - mPackageDexOptimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, - null /* ISAs */, false /* checkProfiles */, + PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer( + mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext); + optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */, + false /* checkProfiles */, getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA)); } @@ -218,4 +325,40 @@ public class OtaDexoptService extends IOtaDexopt.Stub { } } + + private static class RecordingInstallerConnection extends InstallerConnection { + public List commands = new ArrayList(1); + + @Override + public void setWarnIfHeld(Object warnIfHeld) { + throw new IllegalStateException("Should not reach here"); + } + + @Override + public synchronized String transact(String cmd) { + commands.add(cmd); + return "0"; + } + + @Override + public boolean mergeProfiles(int uid, String pkgName) throws InstallerException { + throw new IllegalStateException("Should not reach here"); + } + + @Override + public boolean dumpProfiles(String gid, String packageName, String codePaths) + throws InstallerException { + throw new IllegalStateException("Should not reach here"); + } + + @Override + public void disconnect() { + throw new IllegalStateException("Should not reach here"); + } + + @Override + public void waitForConnection() { + throw new IllegalStateException("Should not reach here"); + } + } } diff --git a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java index e8fdfa50a12df..bbd404879a210 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java +++ b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java @@ -46,6 +46,8 @@ class OtaDexoptShellCommand extends ShellCommand { return runOtaDone(); case "step": return runOtaStep(); + case "next": + return runOtaNext(); case "progress": return runOtaProgress(); default: @@ -83,6 +85,11 @@ class OtaDexoptShellCommand extends ShellCommand { return 0; } + private int runOtaNext() throws RemoteException { + getOutPrintWriter().println(mInterface.nextDexoptCommand()); + return 0; + } + private int runOtaProgress() throws RemoteException { final float progress = mInterface.getProgress(); final PrintWriter pw = getOutPrintWriter(); @@ -103,6 +110,8 @@ class OtaDexoptShellCommand extends ShellCommand { pw.println(" Replies whether the OTA is complete or not."); pw.println(" step"); pw.println(" OTA dexopt the next package."); + pw.println(" next"); + pw.println(" Get parameters for OTA dexopt of the next package."); pw.println(" cleanup"); pw.println(" Clean up internal states. Ends an OTA session."); }