Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d15300cf authored by Andreas Gampe's avatar Andreas Gampe
Browse files

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 cc241a58)
parent ff8ab4c9
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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();
}
+7 −0
Original line number Diff line number Diff line
@@ -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.
+152 −9
Original line number Diff line number Diff line
@@ -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");
        }
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -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.");
    }