summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJiakai Zhang <jiakaiz@google.com>2024-04-10 16:18:37 +0100
committerJiakai Zhang <jiakaiz@google.com>2024-04-30 10:43:18 +0000
commitbd45d4629ef5a62baab677ef4e7708a89ff6ec12 (patch)
treef5712fa780491d5d1f913629cc408b02d1b65789
parentbb55c03f8899339755b4b5fcdbe434e58b8909ce (diff)
downloadart-bd45d4629ef5a62baab677ef4e7708a89ff6ec12.tar.gz
Implement Pre-reboot Dexopt (part 2).
MUST_SLEEP=can't use Sleeper, which is in Guava. Bug: 311377497 Test: adb shell pm art pre-reboot-dexopt Test: - 1. system/update_engine/scripts/update_device.py out/dist/aosp_cf_x86_64_phone-ota-*.zip 2. adb shell pm art pre-reboot-dexopt --slot _b Change-Id: I718de09f8a10d476e1919f3ef3e1d0bb2b4073e3
-rw-r--r--libartservice/service/java/com/android/server/art/ArtManagerLocal.java2
-rw-r--r--libartservice/service/java/com/android/server/art/ArtShellCommand.java38
-rw-r--r--libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java8
-rw-r--r--libartservice/service/java/com/android/server/art/ReasonMapping.java9
-rw-r--r--libartservice/service/java/com/android/server/art/Utils.java8
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java167
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java99
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java63
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java46
-rw-r--r--libartservice/service/proguard.flags3
10 files changed, 438 insertions, 5 deletions
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index a9a357776e..276e6eb7f5 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -169,7 +169,7 @@ public final class ArtManagerLocal {
public int handleShellCommand(@NonNull Binder target, @NonNull ParcelFileDescriptor in,
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
@NonNull String[] args) {
- return new ArtShellCommand(this, mInjector.getPackageManagerLocal())
+ return new ArtShellCommand(this, mInjector.getPackageManagerLocal(), mInjector.getContext())
.exec(target, in.getFileDescriptor(), out.getFileDescriptor(),
err.getFileDescriptor(), args);
}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index 592f9cd36f..9203143abe 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -31,6 +31,7 @@ import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptSt
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.os.Binder;
import android.os.Build;
import android.os.CancellationSignal;
@@ -45,12 +46,14 @@ import androidx.annotation.RequiresApi;
import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.modules.utils.build.SdkLevel;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DeleteResult;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.art.model.DexoptStatus;
import com.android.server.art.model.OperationProgress;
+import com.android.server.art.prereboot.PreRebootDriver;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
@@ -92,15 +95,17 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
private final ArtManagerLocal mArtManagerLocal;
private final PackageManagerLocal mPackageManagerLocal;
+ private final Context mContext;
@GuardedBy("sCancellationSignalMap")
@NonNull
private static final Map<String, CancellationSignal> sCancellationSignalMap = new HashMap<>();
public ArtShellCommand(@NonNull ArtManagerLocal artManagerLocal,
- @NonNull PackageManagerLocal packageManagerLocal) {
+ @NonNull PackageManagerLocal packageManagerLocal, @NonNull Context context) {
mArtManagerLocal = artManagerLocal;
mPackageManagerLocal = packageManagerLocal;
+ mContext = context;
}
@Override
@@ -181,6 +186,10 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
pw.println("Profiles cleared");
return 0;
}
+ case "pre-reboot-dexopt": {
+ // TODO(b/311377497): Remove this command once the development is done.
+ return handlePreRebootDexopt(pw);
+ }
default:
pw.printf("Error: Unknown 'art' sub-command '%s'\n", subcmd);
pw.println("See 'pm help' for help");
@@ -631,6 +640,33 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int handlePreRebootDexopt(@NonNull PrintWriter pw) {
+ if (!SdkLevel.isAtLeastV()) {
+ pw.println("Error: Unsupported command 'pre-reboot-dexopt'");
+ return 1;
+ }
+
+ String otaSlot = null;
+
+ String opt = getNextOption();
+ if ("--slot".equals(opt)) {
+ otaSlot = getNextArgRequired();
+ } else if (opt != null) {
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+
+ try (var signal = new WithCancellationSignal(pw, true /* verbose */)) {
+ if (new PreRebootDriver(mContext).run(otaSlot, signal.get())) {
+ pw.println("Job finished. See logs for details");
+ return 0;
+ } else {
+ pw.println("Job failed. See logs for details");
+ return 1;
+ }
+ }
+ }
+
@Override
public void onHelp() {
// No one should call this. The help text should be printed by the `onHelp` handler of `cmd
diff --git a/libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java b/libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java
index 1510bb4e84..b8a55af7e0 100644
--- a/libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java
+++ b/libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java
@@ -29,7 +29,13 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.ArtServiceJobInterface;
-/** @hide */
+/**
+ * The Pre-reboot Dexopt job.
+ *
+ * During Pre-reboot Dexopt, the old version of this code is run.
+ *
+ * @hide
+ */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class PreRebootDexoptJob implements ArtServiceJobInterface {
private static final String TAG = ArtManagerLocal.TAG;
diff --git a/libartservice/service/java/com/android/server/art/ReasonMapping.java b/libartservice/service/java/com/android/server/art/ReasonMapping.java
index 0048ebcddf..90ea0d4b62 100644
--- a/libartservice/service/java/com/android/server/art/ReasonMapping.java
+++ b/libartservice/service/java/com/android/server/art/ReasonMapping.java
@@ -62,6 +62,8 @@ public class ReasonMapping {
public static final String REASON_CMDLINE = "cmdline";
/** Downgrading the compiler filter when an app is not used for a long time. */
public static final String REASON_INACTIVE = "inactive";
+ /** @hide */
+ public static final String REASON_PRE_REBOOT_DEXOPT = "ab-ota";
// Reasons for Play Install Hints (go/install-hints).
public static final String REASON_INSTALL_FAST = "install-fast";
@@ -78,8 +80,9 @@ public class ReasonMapping {
// Keep this in sync with `ArtShellCommand.printHelp`.
/** @hide */
- public static final Set<String> BATCH_DEXOPT_REASONS = Set.of(REASON_FIRST_BOOT,
- REASON_BOOT_AFTER_OTA, REASON_BOOT_AFTER_MAINLINE_UPDATE, REASON_BG_DEXOPT);
+ public static final Set<String> BATCH_DEXOPT_REASONS =
+ Set.of(REASON_FIRST_BOOT, REASON_BOOT_AFTER_OTA, REASON_BOOT_AFTER_MAINLINE_UPDATE,
+ REASON_BG_DEXOPT, REASON_PRE_REBOOT_DEXOPT);
/**
* Reasons for {@link ArtManagerLocal#dexoptPackages}.
@@ -92,6 +95,7 @@ public class ReasonMapping {
REASON_BOOT_AFTER_OTA,
REASON_BOOT_AFTER_MAINLINE_UPDATE,
REASON_BG_DEXOPT,
+ REASON_PRE_REBOOT_DEXOPT,
})
// clang-format on
@Retention(RetentionPolicy.SOURCE)
@@ -175,6 +179,7 @@ public class ReasonMapping {
case REASON_CMDLINE:
return ArtFlags.PRIORITY_INTERACTIVE;
case REASON_BG_DEXOPT:
+ case REASON_PRE_REBOOT_DEXOPT:
case REASON_INACTIVE:
case REASON_INSTALL_BULK:
case REASON_INSTALL_BULK_SECONDARY:
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index 9358373eef..b05a597c7d 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -469,6 +469,14 @@ public final class Utils {
return uid == Process.SYSTEM_UID || uid == Process.ROOT_UID || uid == Process.SHELL_UID;
}
+ public static void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Slog.wtf(TAG, "Sleep interrupted", e);
+ }
+ }
+
@AutoValue
public abstract static class Abi {
static @NonNull Abi create(
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java
new file mode 100644
index 0000000000..2e43383161
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 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.art.prereboot;
+
+import static com.android.server.art.IDexoptChrootSetup.CHROOT_DIR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.ArtModuleServiceManager;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import android.util.Slog;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.ArtModuleServiceInitializer;
+import com.android.server.art.GlobalInjector;
+import com.android.server.art.IDexoptChrootSetup;
+import com.android.server.art.Utils;
+
+import dalvik.system.DelegateLastClassLoader;
+
+/**
+ * Drives Pre-reboot Dexopt, through reflection.
+ *
+ * During Pre-reboot Dexopt, the old version of this code is run.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class PreRebootDriver {
+ private static final String TAG = ArtManagerLocal.TAG;
+
+ @NonNull private final Injector mInjector;
+
+ public PreRebootDriver(@NonNull Context context) {
+ this(new Injector(context));
+ }
+
+ @VisibleForTesting
+ public PreRebootDriver(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
+ /**
+ * Runs Pre-reboot Dexopt and returns whether it is successful.
+ *
+ * @param otaSlot The slot that contains the OTA update, "_a" or "_b", or null for a Mainline
+ * update.
+ */
+ public boolean run(@Nullable String otaSlot, @NonNull CancellationSignal cancellationSignal) {
+ try {
+ setUp(otaSlot);
+ runFromChroot(cancellationSignal);
+ return true;
+ } catch (RemoteException e) {
+ Utils.logArtdException(e);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "Failed to set up chroot", e);
+ } catch (ReflectiveOperationException e) {
+ Log.e(TAG, "Failed to run pre-reboot dexopt", e);
+ } finally {
+ tearDown();
+ }
+ return false;
+ }
+
+ private void setUp(@Nullable String otaSlot) throws RemoteException {
+ mInjector.getDexoptChrootSetup().setUp(otaSlot);
+ }
+
+ private void tearDown() {
+ // In general, the teardown unmounts apexes and partitions, and open files can keep the
+ // mounts busy so that they cannot be unmounted. Therefore, two things can prevent the
+ // teardown from succeeding: a running Pre-reboot artd process and the new `service-art.jar`
+ // opened and mapped by system server. They are managed by the service manager and the
+ // runtime respectively. There aren't reliable APIs to kill the former or close the latter,
+ // so we have to do them by triggering GC and finalization, with sleep and retry mechanism.
+ for (int numRetries = 3; numRetries > 0;) {
+ try {
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ // Wait for the service manager to shut down artd. The shutdown is asynchronous.
+ Utils.sleep(5000);
+ mInjector.getDexoptChrootSetup().tearDown();
+ return;
+ } catch (RemoteException e) {
+ Utils.logArtdException(e);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "Failed to tear down chroot", e);
+ } catch (IllegalStateException e) {
+ // Not expected, but we still want retries in such an extreme case.
+ Slog.wtf(TAG, "Unexpected exception", e);
+ }
+
+ if (--numRetries > 0) {
+ Log.i(TAG, "Retrying....");
+ Utils.sleep(30000);
+ }
+ }
+ }
+
+ private void runFromChroot(@NonNull CancellationSignal cancellationSignal)
+ throws ReflectiveOperationException {
+ String chrootArtDir = CHROOT_DIR + "/apex/com.android.art";
+ String dexPath = chrootArtDir + "/javalib/service-art.jar";
+ var classLoader =
+ new DelegateLastClassLoader(dexPath, this.getClass().getClassLoader() /* parent */);
+ Class<?> preRebootManagerClass =
+ classLoader.loadClass("com.android.server.art.prereboot.PreRebootManager");
+ // Check if the dex file is loaded successfully. Note that the constructor of
+ // `DelegateLastClassLoader` does not throw when the load fails.
+ if (preRebootManagerClass == PreRebootManager.class) {
+ throw new IllegalStateException(String.format("Failed to load %s", dexPath));
+ }
+ Object preRebootManager = preRebootManagerClass.getConstructor().newInstance();
+ preRebootManagerClass
+ .getMethod("run", ArtModuleServiceManager.class, Context.class,
+ CancellationSignal.class)
+ .invoke(preRebootManager, ArtModuleServiceInitializer.getArtModuleServiceManager(),
+ mInjector.getContext(), cancellationSignal);
+ }
+
+ /**
+ * Injector pattern for testing purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ @NonNull private final Context mContext;
+
+ Injector(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @NonNull
+ public Context getContext() {
+ return mContext;
+ }
+
+ @NonNull
+ public IDexoptChrootSetup getDexoptChrootSetup() {
+ return GlobalInjector.getInstance().getDexoptChrootSetup();
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java
new file mode 100644
index 0000000000..6d808ae12c
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 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.art.prereboot;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.ArtModuleServiceManager;
+import android.os.Build;
+import android.os.RemoteException;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.server.art.ArtdRefCache;
+import com.android.server.art.DexUseManagerLocal;
+import com.android.server.art.GlobalInjector;
+import com.android.server.art.IArtd;
+import com.android.server.art.IDexoptChrootSetup;
+
+import java.util.Objects;
+
+/**
+ * The implementation of the Global injector that is used when the code is running Pre-reboot
+ * Dexopt.
+ *
+ * During Pre-reboot Dexopt, the new version of this code is run.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class PreRebootGlobalInjector extends GlobalInjector {
+ @NonNull private ArtModuleServiceManager mArtModuleServiceManager;
+ @Nullable private DexUseManagerLocal mDexUseManager;
+
+ private PreRebootGlobalInjector(@NonNull ArtModuleServiceManager artModuleServiceManager) {
+ mArtModuleServiceManager = artModuleServiceManager;
+ }
+
+ public static void init(
+ @NonNull ArtModuleServiceManager artModuleServiceManager, @NonNull Context context) {
+ var instance = new PreRebootGlobalInjector(artModuleServiceManager);
+ GlobalInjector.setInstance(instance);
+ try (var pin = ArtdRefCache.getInstance().new Pin()) {
+ // Fail early if artd cannot be initialized.
+ ArtdRefCache.getInstance().getArtd();
+ instance.mDexUseManager = DexUseManagerLocal.createInstance(context);
+ }
+ }
+
+ @Override
+ public boolean isPreReboot() {
+ return true;
+ }
+
+ @Override
+ public void checkArtModuleServiceManager() {}
+
+ @Override
+ @NonNull
+ public IArtd getArtd() {
+ IArtd artd = IArtd.Stub.asInterface(
+ mArtModuleServiceManager.getArtdPreRebootServiceRegisterer().waitForService());
+ if (artd == null) {
+ throw new IllegalStateException("Unable to connect to artd for pre-reboot dexopt");
+ }
+ try {
+ artd.preRebootInit();
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Unable to initialize artd for pre-reboot dexopt", e);
+ }
+ return artd;
+ }
+
+ @Override
+ @NonNull
+ public IDexoptChrootSetup getDexoptChrootSetup() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ @NonNull
+ public DexUseManagerLocal getDexUseManager() {
+ return Objects.requireNonNull(mDexUseManager);
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java
new file mode 100644
index 0000000000..d6947ba156
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 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.art.prereboot;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.ArtModuleServiceManager;
+import android.os.Build;
+import android.os.CancellationSignal;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.ArtdRefCache;
+import com.android.server.art.ReasonMapping;
+import com.android.server.pm.PackageManagerLocal;
+
+import java.util.Objects;
+
+/**
+ * Implementation of {@link PreRebootManagerInterface}.
+ *
+ * DO NOT add a constructor with parameters! There can't be stability guarantees on constructors as
+ * they can't be checked against the interface.
+ *
+ * During Pre-reboot Dexopt, the new version of this code is run.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public class PreRebootManager implements PreRebootManagerInterface {
+ public void run(@NonNull ArtModuleServiceManager artModuleServiceManager,
+ @NonNull Context context, @NonNull CancellationSignal cancellationSignal) {
+ try {
+ PreRebootGlobalInjector.init(artModuleServiceManager, context);
+ ArtManagerLocal artManagerLocal = new ArtManagerLocal(context);
+ PackageManagerLocal packageManagerLocal = Objects.requireNonNull(
+ LocalManagerRegistry.getManager(PackageManagerLocal.class));
+ try (var snapshot = packageManagerLocal.withFilteredSnapshot()) {
+ artManagerLocal.dexoptPackages(snapshot, ReasonMapping.REASON_PRE_REBOOT_DEXOPT,
+ cancellationSignal, null /* processCallbackExecutor */,
+ null /* processCallback */);
+ }
+ } finally {
+ ArtdRefCache.getInstance().reset();
+ }
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java
new file mode 100644
index 0000000000..8422debdbc
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.art.prereboot;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.ArtModuleServiceManager;
+import android.os.Build;
+import android.os.CancellationSignal;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * The interface for the entry point of Pre-reboot Dexopt, called through reflection from an old
+ * version of the ART module. This interface must be kept stable from one version of the ART module
+ * to another. In principle, a method here should be kept as long as devices that receive Mainline
+ * updates call it from their old factory installed modules, unless there is a good reason to drop
+ * the Pre-reboot Dexopt support earlier for certain versions of the ART module.
+ *
+ * Dropping the support for certain versions will only make devices lose the opportunity to optimize
+ * apps before the reboot, but it won't cause severe results such as crashes because even the oldest
+ * version that uses this interface can elegantly handle reflection exceptions.
+ *
+ * During Pre-reboot Dexopt, the new version of this code is run.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+public interface PreRebootManagerInterface {
+ void run(@NonNull ArtModuleServiceManager artModuleServiceManager, @NonNull Context context,
+ @NonNull CancellationSignal cancellationSignal);
+}
diff --git a/libartservice/service/proguard.flags b/libartservice/service/proguard.flags
index 8ef413f3a3..3203cb6b56 100644
--- a/libartservice/service/proguard.flags
+++ b/libartservice/service/proguard.flags
@@ -8,3 +8,6 @@
# A job service is referenced by the framework through reflection.
-keep class * extends android.app.job.JobService { *; }
+
+# PreRebootManager is called through reflection.
+-keep class com.android.server.art.prereboot.PreRebootManager { *; }