Frameworks/base: Add new flow to OtaDexoptService

Add functionality to capture/intercept installd communication, and
use this to return the full communication for dexopt. These parameters
can be used to drive otapreopt_chroot directly.

Keep the old direct invocation alive until devices have transitioned
to a service that exposes this API.

In preparation for renaming of A/B OTA artifacts to include target
slot names.

Bug: 25612095
Bug: 28069686
Change-Id: I14728ee1266f3882cada8f08dd21891ed5f7a0cb
(cherry picked from commit cc241a580c)
This commit is contained in:
Andreas Gampe
2016-06-23 20:27:12 -07:00
parent ff8ab4c9fa
commit d15300cf38
4 changed files with 175 additions and 9 deletions

View File

@@ -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();
}

View File

@@ -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.

View File

@@ -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<PackageParser.Package> 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<String> 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<String> commands = new ArrayList<String>(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");
}
}
}

View File

@@ -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.");
}