Merge "Added brownout_mitigation_modem, pixel_sensors to native namespace list" into main
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index edb3a64..bebb912 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -125,15 +125,15 @@
     @UnsupportedAppUsage
     @CriticalNative
     @android.ravenwood.annotation.RavenwoodReplace
-    private static native boolean nativeIsTagEnabled(long tag);
+    private static native long nativeGetEnabledTags();
     @android.ravenwood.annotation.RavenwoodReplace
     private static native void nativeSetAppTracingAllowed(boolean allowed);
     @android.ravenwood.annotation.RavenwoodReplace
     private static native void nativeSetTracingEnabled(boolean allowed);
 
-    private static boolean nativeIsTagEnabled$ravenwood(long traceTag) {
+    private static long nativeGetEnabledTags$ravenwood() {
         // Tracing currently completely disabled under Ravenwood
-        return false;
+        return 0;
     }
 
     private static void nativeSetAppTracingAllowed$ravenwood(boolean allowed) {
@@ -181,7 +181,8 @@
     @UnsupportedAppUsage
     @SystemApi(client = MODULE_LIBRARIES)
     public static boolean isTagEnabled(long traceTag) {
-        return nativeIsTagEnabled(traceTag);
+        long tags = nativeGetEnabledTags();
+        return (tags & traceTag) != 0;
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 3e065bf..01b4569 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -171,7 +171,9 @@
             return;
         }
 
-        prepareIcon(icon);
+        if (icon != null) {
+            prepareIcon(icon);
+        }
 
         mIconToGlue = icon;
         mGluePending = true;
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index 422bc1e..b579daf 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -124,8 +124,8 @@
     });
 }
 
-static jboolean android_os_Trace_nativeIsTagEnabled(JNIEnv* env, jlong tag) {
-    return tracing_perfetto::isTagEnabled(tag);
+static jlong android_os_Trace_nativeGetEnabledTags(JNIEnv* env) {
+    return tracing_perfetto::getEnabledCategories();
 }
 
 static void android_os_Trace_nativeRegisterWithPerfetto(JNIEnv* env) {
@@ -157,7 +157,7 @@
         {"nativeRegisterWithPerfetto", "()V", (void*)android_os_Trace_nativeRegisterWithPerfetto},
 
         // ----------- @CriticalNative  ----------------
-        {"nativeIsTagEnabled", "(J)Z", (void*)android_os_Trace_nativeIsTagEnabled},
+        {"nativeGetEnabledTags", "()J", (void*)android_os_Trace_nativeGetEnabledTags},
 };
 
 int register_android_os_Trace(JNIEnv* env) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 1c54754..3707207 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -332,6 +332,8 @@
 
         ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
 
+        int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
+
         // Pull out the pairs as we iterate back in the list
         ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>();
         for (int i = 0; i < rawList.size(); i++) {
@@ -344,6 +346,9 @@
             if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
                     && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
                 // Freeform tasks will be added as a separate entry
+                if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
+                    mostRecentFreeformTaskIndex = recentTasks.size();
+                }
                 freeformTasks.add(taskInfo);
                 continue;
             }
@@ -362,7 +367,7 @@
 
         // Add a special entry for freeform tasks
         if (!freeformTasks.isEmpty()) {
-            recentTasks.add(0, GroupedRecentTaskInfo.forFreeformTasks(
+            recentTasks.add(mostRecentFreeformTaskIndex, GroupedRecentTaskInfo.forFreeformTasks(
                     freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0])));
         }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 41a4e8d..d38e97f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -302,6 +302,54 @@
     }
 
     @Test
+    public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() {
+        StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+                DesktopModeStatus.class).startMocking();
+        when(DesktopModeStatus.isEnabled()).thenReturn(true);
+
+        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+        ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+        setRawList(t1, t2, t3, t4, t5);
+
+        SplitBounds pair1Bounds =
+                new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_50_50);
+        mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
+
+        when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+
+        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+        // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
+        assertEquals(3, recentTasks.size());
+        GroupedRecentTaskInfo splitGroup = recentTasks.get(0);
+        GroupedRecentTaskInfo freeformGroup = recentTasks.get(1);
+        GroupedRecentTaskInfo singleGroup = recentTasks.get(2);
+
+        // Check that groups have expected types
+        assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType());
+        assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+        assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType());
+
+        // Check freeform group entries
+        assertEquals(t3, freeformGroup.getTaskInfoList().get(0));
+        assertEquals(t5, freeformGroup.getTaskInfoList().get(1));
+
+        // Check split group entries
+        assertEquals(t1, splitGroup.getTaskInfoList().get(0));
+        assertEquals(t2, splitGroup.getTaskInfoList().get(1));
+
+        // Check single entry
+        assertEquals(t4, singleGroup.getTaskInfo1());
+
+        mockitoSession.finishMocking();
+    }
+
+    @Test
     public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
         StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
                 DesktopModeStatus.class).startMocking();
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d9c371a..e346e72 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -277,6 +277,9 @@
     <!-- to change spatial audio -->
     <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
 
+    <!-- to adjust volume in volume panel -->
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+
     <!-- to access ResolverRankerServices -->
     <uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
index abe1e3d..1c763e8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
@@ -181,6 +181,11 @@
         turbulenceNoiseShader.setColor(newColor)
     }
 
+    /** Updates the noise color that's screen blended on top. */
+    fun updateScreenColor(newColor: Int) {
+        turbulenceNoiseShader.setScreenColor(newColor)
+    }
+
     /**
      * Retrieves the noise offset x, y, z values. This is useful for replaying the animation
      * smoothly from the last animation, by passing in the last values to the next animation.
@@ -322,7 +327,10 @@
     private fun draw() {
         paintCallback?.onDraw(paint!!)
         renderEffectCallback?.onDraw(
-            RenderEffect.createRuntimeShaderEffect(turbulenceNoiseShader, "in_src")
+            RenderEffect.createRuntimeShaderEffect(
+                turbulenceNoiseShader,
+                TurbulenceNoiseShader.BACKGROUND_UNIFORM
+            )
         )
     }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 59354c8..ba8f1ac 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -52,7 +52,7 @@
     /** Color of the effect. */
     val color: Int = DEFAULT_COLOR,
     /** Background color of the effect. */
-    val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
+    val screenColor: Int = DEFAULT_SCREEN_COLOR,
     val width: Float = 0f,
     val height: Float = 0f,
     val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS,
@@ -72,7 +72,7 @@
      */
     val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS,
     /** Whether to flip the luma mask. */
-    val shouldInverseNoiseLuminosity: Boolean = false
+    val shouldInverseNoiseLuminosity: Boolean = false,
 ) {
     companion object {
         const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -83,7 +83,7 @@
         const val DEFAULT_COLOR = Color.WHITE
         const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f
         const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f
-        const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
+        const val DEFAULT_SCREEN_COLOR = Color.BLACK
         private val random = Random()
     }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 8dd90a8..025c8b9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.surfaceeffects.turbulencenoise
 
 import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaders.SolidColorShader
 import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
 import java.lang.Float.max
 
@@ -28,9 +29,11 @@
     RuntimeShader(getShader(baseType)) {
     // language=AGSL
     companion object {
+        /** Uniform name for the background buffer (e.g. image, solid color, etc.). */
+        const val BACKGROUND_UNIFORM = "in_src"
         private const val UNIFORMS =
             """
-            uniform shader in_src; // Needed to support RenderEffect.
+            uniform shader ${BACKGROUND_UNIFORM};
             uniform float in_gridNum;
             uniform vec3 in_noiseMove;
             uniform vec2 in_size;
@@ -41,7 +44,7 @@
             uniform half in_lumaMatteBlendFactor;
             uniform half in_lumaMatteOverallBrightness;
             layout(color) uniform vec4 in_color;
-            layout(color) uniform vec4 in_backgroundColor;
+            layout(color) uniform vec4 in_screenColor;
         """
 
         private const val SIMPLEX_SHADER =
@@ -50,22 +53,20 @@
                 vec2 uv = p / in_size.xy;
                 uv.x *= in_aspectRatio;
 
+                // Compute turbulence effect with the uv distorted with simplex noise.
                 vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
-                // Bring it to [0, 1] range.
-                float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5;
-                luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
-                        * in_opacity;
-                vec3 mask = maskLuminosity(in_color.rgb, luma);
-                vec3 color = in_backgroundColor.rgb + mask * 0.6;
+                vec3 color = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma);
+
+                // Blend the result with the background color.
+                color = in_src.eval(p).rgb + color * 0.6;
 
                 // Add dither with triangle distribution to avoid color banding. Dither in the
                 // shader here as we are in gamma space.
                 float dither = triangleNoise(p * in_pixelDensity) / 255.;
+                color += dither.rrr;
 
-                // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
-                // multiply rgb with a to get the correct result.
-                color = (color + dither.rrr) * in_opacity;
-                return vec4(color, in_opacity);
+                // Return the pre-multiplied alpha result, i.e. [R*A, G*A, B*A, A].
+                return vec4(color * in_opacity, in_opacity);
             }
         """
 
@@ -76,32 +77,105 @@
                 uv.x *= in_aspectRatio;
 
                 vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
-                // Bring it to [0, 1] range.
-                float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5;
-                luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
-                        * in_opacity;
-                vec3 mask = maskLuminosity(in_color.rgb, luma);
-                vec3 color = in_backgroundColor.rgb + mask * 0.6;
+                vec3 color = getColorTurbulenceMask(simplex3d_fractal(noiseP) * in_inverseLuma);
+
+                // Blend the result with the background color.
+                color = in_src.eval(p).rgb + color * 0.6;
 
                 // Skip dithering.
                 return vec4(color * in_opacity, in_opacity);
             }
         """
+
+        /**
+         * This effect has two layers: color turbulence effect with sparkles on top.
+         * 1. Gets the luma matte using Simplex noise.
+         * 2. Generate a colored turbulence layer with the luma matte.
+         * 3. Generate a colored sparkle layer with the same luma matter.
+         * 4. Apply a screen color to the background image.
+         * 5. Composite the previous result with the color turbulence.
+         * 6. Composite the latest result with the sparkles.
+         */
+        private const val SIMPLEX_SPARKLE_SHADER =
+            """
+            vec4 main(vec2 p) {
+                vec2 uv = p / in_size.xy;
+                uv.x *= in_aspectRatio;
+
+                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+                // Luma is used for both color and sparkle masks.
+                float luma = simplex3d(noiseP) * in_inverseLuma;
+
+                // Get color layer (color mask with in_color applied)
+                vec3 colorLayer = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma);
+                float dither = triangleNoise(p * in_pixelDensity) / 255.;
+                colorLayer += dither.rrr;
+
+                // Get sparkle layer (sparkle mask with particles & in_color applied)
+                vec3 sparkleLayer = getSparkleTurbulenceMask(luma, p);
+
+                // Composite with the background.
+                half4 bgColor = in_src.eval(p);
+                half sparkleOpacity = smoothstep(0, 0.75, in_opacity);
+
+                half3 effect = screen(bgColor.rgb, in_screenColor.rgb);
+                effect = screen(effect, colorLayer * 0.22);
+                effect += sparkleLayer * sparkleOpacity;
+
+                return mix(bgColor, vec4(effect, 1.), in_opacity);
+            }
+        """
+
+        private const val COMMON_FUNCTIONS =
+            /**
+             * Below two functions generate turbulence layers (color or sparkles applied) with the
+             * given luma matte. They both return a mask with in_color applied.
+             */
+            """
+            vec3 getColorTurbulenceMask(float luma) {
+                // Bring it to [0, 1] range.
+                luma = luma * 0.5 + 0.5;
+
+                half colorLuma =
+                    saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+                    * in_opacity;
+                vec3 colorLayer = maskLuminosity(in_color.rgb, colorLuma);
+
+                return colorLayer;
+            }
+
+            vec3 getSparkleTurbulenceMask(float luma, vec2 p) {
+                half lumaIntensity = 1.75;
+                half lumaBrightness = -1.3;
+                half sparkleLuma = max(luma * lumaIntensity + lumaBrightness, 0.);
+
+                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_noiseMove.z);
+                vec3 sparkleLayer = maskLuminosity(in_color.rgb * sparkle, sparkleLuma);
+
+                return sparkleLayer;
+            }
+        """
         private const val SIMPLEX_NOISE_SHADER =
-            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SHADER
         private const val FRACTAL_NOISE_SHADER =
-            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER
-        // TODO (b/282007590): Add NOISE_WITH_SPARKLE
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + FRACTAL_SHADER
+        private const val SPARKLE_NOISE_SHADER =
+            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SPARKLE_SHADER
 
         enum class Type {
+            /** Effect with a simple color noise turbulence. */
             SIMPLEX_NOISE,
+            /** Effect with a simple color noise turbulence, with fractal. */
             SIMPLEX_NOISE_FRACTAL,
+            /** Effect with color & sparkle turbulence with screen color layer. */
+            SIMPLEX_NOISE_SPARKLE
         }
 
         fun getShader(type: Type): String {
             return when (type) {
                 Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER
                 Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER
+                Type.SIMPLEX_NOISE_SPARKLE -> SPARKLE_NOISE_SHADER
             }
         }
     }
@@ -111,7 +185,7 @@
         setGridCount(config.gridCount)
         setPixelDensity(config.pixelDensity)
         setColor(config.color)
-        setBackgroundColor(config.backgroundColor)
+        setScreenColor(config.screenColor)
         setSize(config.width, config.height)
         setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
         setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity)
@@ -137,9 +211,20 @@
         setColorUniform("in_color", color)
     }
 
-    /** Sets the background color of the effect. Alpha is ignored. */
+    /**
+     * Sets the color that is used for blending on top of the background color/image. Only relevant
+     * to [Type.SIMPLEX_NOISE_SPARKLE].
+     */
+    fun setScreenColor(color: Int) {
+        setColorUniform("in_screenColor", color)
+    }
+
+    /**
+     * Sets the background color of the effect. Alpha is ignored. If you are using [RenderEffect],
+     * no need to call this function since the background image of the View will be used.
+     */
     fun setBackgroundColor(color: Int) {
-        setColorUniform("in_backgroundColor", color)
+        setInputShader(BACKGROUND_UNIFORM, SolidColorShader(color))
     }
 
     /**
@@ -163,7 +248,7 @@
      *
      * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting
      *   this a lower number removes variations. I.e. the turbulence noise will look more blended.
-     *   Expected input range is [0, 1]. more dimmed.
+     *   Expected input range is [0, 1].
      * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise.
      *   Expected input range is [0, 1].
      *
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
index a8999ff..6c8949e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
@@ -16,6 +16,7 @@
 
 import android.content.Context;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.widget.LinearLayout;
 
 import com.android.systemui.plugins.annotations.DependsOn;
@@ -74,4 +75,9 @@
 
     /** Sets the index of this tile in its layout */
     public abstract void setPosition(int position);
+
+    /** Get the duration of a visuo-haptic long-press effect */
+    public int getLongPressEffectDuration() {
+        return ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index ec72a14..f1620d9 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -214,6 +214,24 @@
         _actionType.value = null
     }
 
+    /**
+     * Reset the effect with a new effect duration.
+     *
+     * The effect will go back to an [IDLE] state where it can begin its logic with a new duration.
+     *
+     * @param[duration] New duration for the long-press effect
+     */
+    fun resetWithDuration(duration: Int) {
+        // The effect can't reset if it is running
+        if (effectAnimator.isRunning) return
+
+        effectAnimator.duration = duration.toLong()
+        _effectProgress.value = 0f
+        _actionType.value = null
+        waitJob?.cancel()
+        state = State.IDLE
+    }
+
     enum class State {
         IDLE, /* The effect is idle waiting for touch input */
         TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index fc95ec9..d0246a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -142,12 +143,14 @@
                         }
                     }
 
-                    if (keyguardBottomAreaRefactor()) {
+                    if (keyguardBottomAreaRefactor() || DeviceEntryUdfpsRefactor.isEnabled) {
                         launch {
                             viewModel.alpha(viewState).collect { alpha ->
                                 view.alpha = alpha
-                                childViews[statusViewId]?.alpha = alpha
-                                childViews[burnInLayerId]?.alpha = alpha
+                                if (keyguardBottomAreaRefactor()) {
+                                    childViews[statusViewId]?.alpha = alpha
+                                    childViews[burnInLayerId]?.alpha = alpha
+                                }
                             }
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 5f7991e..1c11178 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -24,12 +24,13 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
+import com.android.app.tracing.coroutines.launch
 import com.android.systemui.util.Assert
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.launch
 
 /**
  * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
@@ -66,7 +67,8 @@
     // dispatcher to use. We don't want it to run on the Dispatchers.Default thread pool as
     // default behavior. Instead, we want it to run on the view's UI thread since the user will
     // presumably want to call view methods that require being called from said UI thread.
-    val lifecycleCoroutineContext = Dispatchers.Main + coroutineContext
+    val lifecycleCoroutineContext =
+        Dispatchers.Main + createCoroutineTracingContext() + coroutineContext
     var lifecycleOwner: ViewLifecycleOwner? = null
     val onAttachListener =
         object : View.OnAttachStateChangeListener {
@@ -97,14 +99,12 @@
             )
     }
 
-    return object : DisposableHandle {
-        override fun dispose() {
-            Assert.isMainThread()
+    return DisposableHandle {
+        Assert.isMainThread()
 
-            lifecycleOwner?.onDestroy()
-            lifecycleOwner = null
-            view.removeOnAttachStateChangeListener(onAttachListener)
-        }
+        lifecycleOwner?.onDestroy()
+        lifecycleOwner = null
+        view.removeOnAttachStateChangeListener(onAttachListener)
     }
 }
 
@@ -115,7 +115,12 @@
 ): ViewLifecycleOwner {
     return ViewLifecycleOwner(view).apply {
         onCreate()
-        lifecycleScope.launch(coroutineContext) { block(view) }
+        lifecycleScope.launch(
+            "ViewLifecycleOwner(${view::class.java.simpleName})",
+            coroutineContext
+        ) {
+            block(view)
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 26c63f3..899b9ed 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -1306,7 +1306,7 @@
                 TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
                 // Color will be correctly updated in ColorSchemeTransition.
                 /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
-                /* backgroundColor= */ Color.BLACK,
+                /* screenColor= */ Color.BLACK,
                 width,
                 height,
                 TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 63963de..e1c543f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -37,7 +37,6 @@
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
-import android.view.ViewConfiguration
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
@@ -185,6 +184,8 @@
     private var initialLongPressProperties: QSLongPressProperties? = null
     private var finalLongPressProperties: QSLongPressProperties? = null
     private val colorEvaluator = ArgbEvaluator.getInstance()
+    val hasLongPressEffect: Boolean
+        get() = longPressEffect != null
 
     init {
         val typedValue = TypedValue()
@@ -611,10 +612,9 @@
 
         // Long-press effects
         if (quickSettingsVisualHapticsLongpress()){
-            if (state.handlesLongClick) {
-                // initialize the long-press effect and set it as the touch listener
+            if (state.handlesLongClick && maybeCreateAndInitializeLongPressEffect()) {
+                // set the valid long-press effect as the touch listener
                 showRippleEffect = false
-                initializeLongPressEffect()
                 setOnTouchListener(longPressEffect)
                 QSLongPressEffectViewBinder.bind(this, longPressEffect)
             } else {
@@ -751,7 +751,7 @@
     override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
 
     fun updateLongPressEffectProperties(effectProgress: Float) {
-        if (!isLongClickable) return
+        if (!isLongClickable || longPressEffect == null) return
         setAllColors(
             colorEvaluator.evaluate(
                 effectProgress,
@@ -836,13 +836,25 @@
         icon.setTint(icon.mIcon as ImageView, lastIconTint)
     }
 
-    private fun initializeLongPressEffect() {
+    private fun maybeCreateAndInitializeLongPressEffect(): Boolean {
+        // Don't setup the effect if the long-press duration is invalid
+        val effectDuration = longPressEffectDuration
+        if (effectDuration <= 0) {
+            longPressEffect = null
+            return false
+        }
+
         initializeLongPressProperties()
-        longPressEffect =
-            QSLongPressEffect(
-                vibratorHelper,
-                ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(),
-            )
+        if (longPressEffect == null) {
+            longPressEffect =
+                QSLongPressEffect(
+                    vibratorHelper,
+                    effectDuration,
+                )
+        } else {
+            longPressEffect?.resetWithDuration(effectDuration)
+        }
+        return true
     }
 
     private fun initializeLongPressProperties() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index ac1d280..e977014 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -91,7 +91,8 @@
             )
         controller.init()
 
-        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+        val bgDispatcher = bgHandler.asCoroutineDispatcher("@UnfoldBg Handler")
+        applicationScope.launch(bgDispatcher) {
             powerInteractor.screenPowerState.collect {
                 if (it == ScreenPowerState.SCREEN_ON) {
                     readyCallback = null
@@ -99,7 +100,7 @@
             }
         }
 
-        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+        applicationScope.launch(bgDispatcher) {
             deviceStateRepository.state
                 .map { it == DeviceStateRepository.DeviceState.FOLDED }
                 .distinctUntilChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 66fdf53..933ddb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -16,25 +16,22 @@
 
 package com.android.systemui.haptics.slider
 
-import android.os.VibrationAttributes
 import android.os.VibrationEffect
 import android.view.VelocityTracker
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.fakeSystemClock
 import kotlin.math.max
 import kotlin.test.assertEquals
+import kotlin.test.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -42,10 +39,10 @@
 class SliderHapticFeedbackProviderTest : SysuiTestCase() {
 
     @Mock private lateinit var velocityTracker: VelocityTracker
-    @Mock private lateinit var vibratorHelper: VibratorHelper
+
+    private val kosmos = testKosmos()
 
     private val config = SliderHapticFeedbackConfig()
-    private val clock = FakeSystemClock()
 
     private val lowTickDuration = 12 // Mocked duration of a low tick
     private val dragTextureThresholdMillis =
@@ -55,250 +52,278 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        whenever(vibratorHelper.getPrimitiveDurations(any()))
-            .thenReturn(intArrayOf(lowTickDuration))
         whenever(velocityTracker.isAxisSupported(config.velocityAxis)).thenReturn(true)
         whenever(velocityTracker.getAxisVelocity(config.velocityAxis))
             .thenReturn(config.maxVelocityToScale)
+
+        kosmos.vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_LOW_TICK] =
+            lowTickDuration
         sliderHapticFeedbackProvider =
-            SliderHapticFeedbackProvider(vibratorHelper, velocityTracker, config, clock)
-    }
-
-    @Test
-    fun playHapticAtLowerBookend_playsClick() {
-        val vibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
-                )
-                .compose()
-
-        sliderHapticFeedbackProvider.onLowerBookend()
-
-        verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
-    }
-
-    @Test
-    fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() {
-        val vibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
-                )
-                .compose()
-
-        sliderHapticFeedbackProvider.onLowerBookend()
-        sliderHapticFeedbackProvider.onLowerBookend()
-
-        verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
-    }
-
-    @Test
-    fun playHapticAtUpperBookend_playsClick() {
-        val vibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
-                )
-                .compose()
-
-        sliderHapticFeedbackProvider.onUpperBookend()
-
-        verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
-    }
-
-    @Test
-    fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() {
-        val vibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
-                )
-                .compose()
-
-        sliderHapticFeedbackProvider.onUpperBookend()
-        sliderHapticFeedbackProvider.onUpperBookend()
-
-        verify(vibratorHelper, times(1))
-            .vibrate(eq(vibration), any(VibrationAttributes::class.java))
-    }
-
-    @Test
-    fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() {
-        // GIVEN max velocity and slider progress
-        val progress = 1f
-        val expectedScale =
-            sliderHapticFeedbackProvider.scaleOnDragTexture(
-                config.maxVelocityToScale,
-                progress,
+            SliderHapticFeedbackProvider(
+                kosmos.vibratorHelper,
+                velocityTracker,
+                config,
+                kosmos.fakeSystemClock,
             )
-        val ticks = VibrationEffect.startComposition()
-        repeat(config.numberOfLowTicks) {
-            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+    }
+
+    @Test
+    fun playHapticAtLowerBookend_playsClick() =
+        with(kosmos) {
+            val vibration =
+                VibrationEffect.startComposition()
+                    .addPrimitive(
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+                            config.maxVelocityToScale
+                        ),
+                    )
+                    .compose()
+
+            sliderHapticFeedbackProvider.onLowerBookend()
+
+            assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
         }
 
-        // GIVEN system running for 1s
-        clock.advanceTime(1000)
-
-        // WHEN two calls to play occur immediately
-        sliderHapticFeedbackProvider.onProgress(progress)
-        sliderHapticFeedbackProvider.onProgress(progress)
-
-        // THEN the correct composition only plays once
-        verify(vibratorHelper, times(1))
-            .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java))
-    }
-
     @Test
-    fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() {
-        // GIVEN max velocity and a slider progress at half progress
-        val firstProgress = 0.5f
-        val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+    fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() =
+        with(kosmos) {
+            val vibration =
+                VibrationEffect.startComposition()
+                    .addPrimitive(
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
+                    )
+                    .compose()
 
-        // Given a second slider progress event smaller than the progress threshold
-        val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
+            sliderHapticFeedbackProvider.onLowerBookend()
+            sliderHapticFeedbackProvider.onLowerBookend()
 
-        // GIVEN system running for 1s
-        clock.advanceTime(1000)
-
-        // WHEN two calls to play occur with the required threshold separation (time and progress)
-        sliderHapticFeedbackProvider.onProgress(firstProgress)
-        clock.advanceTime(dragTextureThresholdMillis.toLong())
-        sliderHapticFeedbackProvider.onProgress(secondProgress)
-
-        // THEN Only the first compositions plays
-        verify(vibratorHelper, times(1))
-            .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
-        verify(vibratorHelper, times(1))
-            .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java))
-    }
-
-    @Test
-    fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
-        // GIVEN max velocity and a slider progress at half progress
-        val firstProgress = 0.5f
-        val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
-
-        // Given a second slider progress event beyond progress threshold
-        val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
-        val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)
-
-        // GIVEN system running for 1s
-        clock.advanceTime(1000)
-
-        // WHEN two calls to play occur with the required threshold separation (time and progress)
-        sliderHapticFeedbackProvider.onProgress(firstProgress)
-        clock.advanceTime(dragTextureThresholdMillis.toLong())
-        sliderHapticFeedbackProvider.onProgress(secondProgress)
-
-        // THEN the correct compositions play
-        verify(vibratorHelper, times(1))
-            .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
-        verify(vibratorHelper, times(1))
-            .vibrate(eq(secondTicks), any(VibrationAttributes::class.java))
-    }
-
-    @Test
-    fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() {
-        // GIVEN max velocity and slider progress
-        val progress = 1f
-        val expectedScale =
-            sliderHapticFeedbackProvider.scaleOnDragTexture(
-                config.maxVelocityToScale,
-                progress,
-            )
-        val ticks = VibrationEffect.startComposition()
-        repeat(config.numberOfLowTicks) {
-            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+            assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
         }
-        val bookendVibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
-                )
-                .compose()
-
-        // GIVEN a vibration at the lower bookend followed by a request to vibrate at progress
-        sliderHapticFeedbackProvider.onLowerBookend()
-        sliderHapticFeedbackProvider.onProgress(progress)
-
-        // WHEN a vibration is to trigger again at the lower bookend
-        sliderHapticFeedbackProvider.onLowerBookend()
-
-        // THEN there are two bookend vibrations
-        verify(vibratorHelper, times(2))
-            .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
-    }
 
     @Test
-    fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() {
-        // GIVEN max velocity and slider progress
-        val progress = 1f
-        val expectedScale =
-            sliderHapticFeedbackProvider.scaleOnDragTexture(
-                config.maxVelocityToScale,
-                progress,
-            )
-        val ticks = VibrationEffect.startComposition()
-        repeat(config.numberOfLowTicks) {
-            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+    fun playHapticAtUpperBookend_playsClick() =
+        with(kosmos) {
+            val vibration =
+                VibrationEffect.startComposition()
+                    .addPrimitive(
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+                            config.maxVelocityToScale
+                        ),
+                    )
+                    .compose()
+
+            sliderHapticFeedbackProvider.onUpperBookend()
+
+            assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
         }
-        val bookendVibration =
-            VibrationEffect.startComposition()
-                .addPrimitive(
-                    VibrationEffect.Composition.PRIMITIVE_CLICK,
-                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
-                )
-                .compose()
-
-        // GIVEN a vibration at the upper bookend followed by a request to vibrate at progress
-        sliderHapticFeedbackProvider.onUpperBookend()
-        sliderHapticFeedbackProvider.onProgress(progress)
-
-        // WHEN a vibration is to trigger again at the upper bookend
-        sliderHapticFeedbackProvider.onUpperBookend()
-
-        // THEN there are two bookend vibrations
-        verify(vibratorHelper, times(2))
-            .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
-    }
-
-    fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() {
-        // GIVEN max velocity and a slider progress at half progress
-        val progress = 0.5f
-
-        // GIVEN system running for 1s
-        clock.advanceTime(1000)
-
-        // WHEN a drag texture plays
-        sliderHapticFeedbackProvider.onProgress(progress)
-
-        // THEN the dragTextureLastProgress remembers the latest progress
-        assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress)
-    }
 
     @Test
-    fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() {
-        // GIVEN max velocity and a slider progress at half progress
-        val progress = 0.5f
+    fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() =
+        with(kosmos) {
+            val vibration =
+                VibrationEffect.startComposition()
+                    .addPrimitive(
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+                            config.maxVelocityToScale
+                        ),
+                    )
+                    .compose()
 
-        // GIVEN system running for 1s
-        clock.advanceTime(1000)
+            sliderHapticFeedbackProvider.onUpperBookend()
+            sliderHapticFeedbackProvider.onUpperBookend()
 
-        // WHEN a drag texture plays
-        sliderHapticFeedbackProvider.onProgress(progress)
+            assertEquals(/* expected=*/ 1, vibratorHelper.timesVibratedWithEffect(vibration))
+        }
 
-        // WHEN the handle is released
-        sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
+    @Test
+    fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() =
+        with(kosmos) {
+            // GIVEN max velocity and slider progress
+            val progress = 1f
+            val expectedScale =
+                sliderHapticFeedbackProvider.scaleOnDragTexture(
+                    config.maxVelocityToScale,
+                    progress,
+                )
+            val ticks = VibrationEffect.startComposition()
+            repeat(config.numberOfLowTicks) {
+                ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+            }
 
-        // THEN the dragTextureLastProgress tracker is reset
-        assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
-    }
+            // GIVEN system running for 1s
+            fakeSystemClock.advanceTime(1000)
+
+            // WHEN two calls to play occur immediately
+            sliderHapticFeedbackProvider.onProgress(progress)
+            sliderHapticFeedbackProvider.onProgress(progress)
+
+            // THEN the correct composition only plays once
+            assertEquals(/* expected=*/ 1, vibratorHelper.timesVibratedWithEffect(ticks.compose()))
+        }
+
+    @Test
+    fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() =
+        with(kosmos) {
+            // GIVEN max velocity and a slider progress at half progress
+            val firstProgress = 0.5f
+            val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+            // Given a second slider progress event smaller than the progress threshold
+            val secondProgress =
+                firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
+
+            // GIVEN system running for 1s
+            fakeSystemClock.advanceTime(1000)
+
+            // WHEN two calls to play occur with the required threshold separation (time and
+            // progress)
+            sliderHapticFeedbackProvider.onProgress(firstProgress)
+            fakeSystemClock.advanceTime(dragTextureThresholdMillis.toLong())
+            sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+            // THEN Only the first compositions plays
+            assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(firstTicks))
+            assertEquals(/* expected= */ 1, vibratorHelper.totalVibrations)
+        }
+
+    @Test
+    fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() =
+        with(kosmos) {
+            // GIVEN max velocity and a slider progress at half progress
+            val firstProgress = 0.5f
+            val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+            // Given a second slider progress event beyond progress threshold
+            val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
+            val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)
+
+            // GIVEN system running for 1s
+            fakeSystemClock.advanceTime(1000)
+
+            // WHEN two calls to play occur with the required threshold separation (time and
+            // progress)
+            sliderHapticFeedbackProvider.onProgress(firstProgress)
+            fakeSystemClock.advanceTime(dragTextureThresholdMillis.toLong())
+            sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+            // THEN the correct compositions play
+            assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(firstTicks))
+            assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(secondTicks))
+        }
+
+    @Test
+    fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() =
+        with(kosmos) {
+            // GIVEN max velocity and slider progress
+            val progress = 1f
+            val expectedScale =
+                sliderHapticFeedbackProvider.scaleOnDragTexture(
+                    config.maxVelocityToScale,
+                    progress,
+                )
+            val ticks = VibrationEffect.startComposition()
+            repeat(config.numberOfLowTicks) {
+                ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+            }
+            val bookendVibration =
+                VibrationEffect.startComposition()
+                    .addPrimitive(
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+                            config.maxVelocityToScale
+                        ),
+                    )
+                    .compose()
+
+            // GIVEN a vibration at the lower bookend followed by a request to vibrate at progress
+            sliderHapticFeedbackProvider.onLowerBookend()
+            sliderHapticFeedbackProvider.onProgress(progress)
+
+            // WHEN a vibration is to trigger again at the lower bookend
+            sliderHapticFeedbackProvider.onLowerBookend()
+
+            // THEN there are two bookend vibrations
+            assertEquals(
+                /* expected= */ 2,
+                vibratorHelper.timesVibratedWithEffect(bookendVibration)
+            )
+        }
+
+    @Test
+    fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() =
+        with(kosmos) {
+            // GIVEN max velocity and slider progress
+            val progress = 1f
+            val expectedScale =
+                sliderHapticFeedbackProvider.scaleOnDragTexture(
+                    config.maxVelocityToScale,
+                    progress,
+                )
+            val ticks = VibrationEffect.startComposition()
+            repeat(config.numberOfLowTicks) {
+                ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+            }
+            val bookendVibration =
+                VibrationEffect.startComposition()
+                    .addPrimitive(
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+                            config.maxVelocityToScale
+                        ),
+                    )
+                    .compose()
+
+            // GIVEN a vibration at the upper bookend followed by a request to vibrate at progress
+            sliderHapticFeedbackProvider.onUpperBookend()
+            sliderHapticFeedbackProvider.onProgress(progress)
+
+            // WHEN a vibration is to trigger again at the upper bookend
+            sliderHapticFeedbackProvider.onUpperBookend()
+
+            // THEN there are two bookend vibrations
+            assertEquals(
+                /* expected= */ 2,
+                vibratorHelper.timesVibratedWithEffect(bookendVibration)
+            )
+        }
+
+    fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() =
+        with(kosmos) {
+            // GIVEN max velocity and a slider progress at half progress
+            val progress = 0.5f
+
+            // GIVEN system running for 1s
+            fakeSystemClock.advanceTime(1000)
+
+            // WHEN a drag texture plays
+            sliderHapticFeedbackProvider.onProgress(progress)
+
+            // THEN the dragTextureLastProgress remembers the latest progress
+            assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress)
+        }
+
+    @Test
+    fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() =
+        with(kosmos) {
+            // GIVEN max velocity and a slider progress at half progress
+            val progress = 0.5f
+
+            // GIVEN system running for 1s
+            fakeSystemClock.advanceTime(1000)
+
+            // WHEN a drag texture plays
+            sliderHapticFeedbackProvider.onProgress(progress)
+
+            // WHEN the handle is released
+            sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
+
+            // THEN the dragTextureLastProgress tracker is reset
+            assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
+        }
 
     private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
         val ticks = VibrationEffect.startComposition()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index e0fff9c..04e214a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.graphics.drawable.Drawable
+import android.platform.test.annotations.EnableFlags
 import android.service.quicksettings.Tile
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -27,6 +28,7 @@
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.TextView
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.qs.QSTile
@@ -380,6 +382,34 @@
         assertThat(tileView.stateDescription?.contains(unavailableString)).isTrue()
     }
 
+    @Test
+    @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
+    fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotCreateEffect() {
+        val state = QSTile.State() // A state that handles longPress
+
+        // GIVEN an invalid long-press effect duration
+        tileView.constantLongPressEffectDuration = -1
+
+        // WHEN the state changes
+        tileView.changeState(state)
+
+        // THEN the long-press effect is not created
+        assertThat(tileView.hasLongPressEffect).isFalse()
+    }
+
+    @Test
+    @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
+    fun onStateChange_longPressEffectActive_withValidDuration_createsEffect() {
+        // GIVEN a test state that handles long-press and a valid long-press effect duration
+        val state = QSTile.State()
+
+        // WHEN the state changes
+        tileView.changeState(state)
+
+        // THEN the long-press effect created
+        assertThat(tileView.hasLongPressEffect).isTrue()
+    }
+
     class FakeTileView(
         context: Context,
         collapsed: Boolean
@@ -387,6 +417,9 @@
             ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
             collapsed
     ) {
+        var constantLongPressEffectDuration = 500
+
+        override fun getLongPressEffectDuration(): Int = constantLongPressEffectDuration
         fun changeState(state: QSTile.State) {
             handleStateChanged(state)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
index 7c36a85..7a83cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
@@ -19,7 +19,9 @@
 import android.graphics.Paint
 import android.graphics.RenderEffect
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
+import com.android.systemui.animation.AnimatorTestRule
 import com.android.systemui.model.SysUiStateTest
 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState
 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN
@@ -31,18 +33,17 @@
 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 class LoadingEffectTest : SysUiStateTest() {
 
-    private val fakeSystemClock = FakeSystemClock()
-    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     @Test
     fun play_paintCallback_triggersDrawCallback() {
@@ -61,14 +62,12 @@
                 animationStateChangedCallback = null
             )
 
-        fakeExecutor.execute {
-            assertThat(paintFromCallback).isNull()
+        assertThat(paintFromCallback).isNull()
 
-            loadingEffect.play()
-            fakeSystemClock.advanceTime(500L)
+        loadingEffect.play()
+        animatorTestRule.advanceTimeBy(500L)
 
-            assertThat(paintFromCallback).isNotNull()
-        }
+        assertThat(paintFromCallback).isNotNull()
     }
 
     @Test
@@ -88,25 +87,22 @@
                 animationStateChangedCallback = null
             )
 
-        fakeExecutor.execute {
-            assertThat(renderEffectFromCallback).isNull()
+        assertThat(renderEffectFromCallback).isNull()
 
-            loadingEffect.play()
-            fakeSystemClock.advanceTime(500L)
+        loadingEffect.play()
+        animatorTestRule.advanceTimeBy(500L)
 
-            assertThat(renderEffectFromCallback).isNotNull()
-        }
+        assertThat(renderEffectFromCallback).isNotNull()
     }
 
     @Test
     fun play_animationStateChangesInOrder() {
         val config = TurbulenceNoiseAnimationConfig()
-        val expectedStates = arrayOf(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING)
-        val actualStates = mutableListOf(NOT_PLAYING)
+        val states = mutableListOf(NOT_PLAYING)
         val stateChangedCallback =
             object : AnimationStateChangedCallback {
                 override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
-                    actualStates.add(newState)
+                    states.add(newState)
                 }
             }
         val drawCallback =
@@ -121,16 +117,15 @@
                 stateChangedCallback
             )
 
-        val timeToAdvance =
-            config.easeInDuration + config.maxDuration + config.easeOutDuration + 100
+        loadingEffect.play()
 
-        fakeExecutor.execute {
-            loadingEffect.play()
+        // Execute all the animators by advancing each duration with some buffer.
+        animatorTestRule.advanceTimeBy(config.easeInDuration.toLong())
+        animatorTestRule.advanceTimeBy(config.maxDuration.toLong())
+        animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong())
+        animatorTestRule.advanceTimeBy(500)
 
-            fakeSystemClock.advanceTime(timeToAdvance.toLong())
-
-            assertThat(actualStates).isEqualTo(expectedStates)
-        }
+        assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING)
     }
 
     @Test
@@ -157,17 +152,15 @@
                 stateChangedCallback
             )
 
-        fakeExecutor.execute {
-            assertThat(numPlay).isEqualTo(0)
+        assertThat(numPlay).isEqualTo(0)
 
-            loadingEffect.play()
-            loadingEffect.play()
-            loadingEffect.play()
-            loadingEffect.play()
-            loadingEffect.play()
+        loadingEffect.play()
+        loadingEffect.play()
+        loadingEffect.play()
+        loadingEffect.play()
+        loadingEffect.play()
 
-            assertThat(numPlay).isEqualTo(1)
-        }
+        assertThat(numPlay).isEqualTo(1)
     }
 
     @Test
@@ -181,7 +174,7 @@
         val stateChangedCallback =
             object : AnimationStateChangedCallback {
                 override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
-                    if (oldState == MAIN && newState == NOT_PLAYING) {
+                    if (oldState == EASE_OUT && newState == NOT_PLAYING) {
                         isFinished = true
                     }
                 }
@@ -194,18 +187,17 @@
                 stateChangedCallback
             )
 
-        fakeExecutor.execute {
-            assertThat(isFinished).isFalse()
+        assertThat(isFinished).isFalse()
 
-            loadingEffect.play()
-            fakeSystemClock.advanceTime(config.easeInDuration.toLong() + 500L)
+        loadingEffect.play()
+        animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L)
 
-            assertThat(isFinished).isFalse()
+        assertThat(isFinished).isFalse()
 
-            loadingEffect.finish()
+        loadingEffect.finish()
+        animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L)
 
-            assertThat(isFinished).isTrue()
-        }
+        assertThat(isFinished).isTrue()
     }
 
     @Test
@@ -232,13 +224,11 @@
                 stateChangedCallback
             )
 
-        fakeExecutor.execute {
-            assertThat(isFinished).isFalse()
+        assertThat(isFinished).isFalse()
 
-            loadingEffect.finish()
+        loadingEffect.finish()
 
-            assertThat(isFinished).isFalse()
-        }
+        assertThat(isFinished).isFalse()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
index 549280a..e62ca64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE_FRACTAL
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE_SPARKLE
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -38,4 +39,9 @@
     fun compilesFractalNoise() {
         turbulenceNoiseShader = TurbulenceNoiseShader(baseType = SIMPLEX_NOISE_FRACTAL)
     }
+
+    @Test
+    fun compilesSparkleNoise() {
+        turbulenceNoiseShader = TurbulenceNoiseShader(baseType = SIMPLEX_NOISE_SPARKLE)
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt
new file mode 100644
index 0000000..875f6ed
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.systemui.haptics
+
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import android.os.Vibrator
+
+/** A simple empty vibrator required for the [FakeVibratorHelper] */
+class EmptyVibrator : Vibrator() {
+    override fun cancel() {}
+
+    override fun cancel(usageFilter: Int) {}
+
+    override fun hasAmplitudeControl(): Boolean = true
+
+    override fun hasVibrator(): Boolean = true
+
+    override fun vibrate(
+        uid: Int,
+        opPkg: String,
+        vibe: VibrationEffect,
+        reason: String,
+        attributes: VibrationAttributes,
+    ) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt
new file mode 100644
index 0000000..4c0b132
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.systemui.haptics
+
+import android.annotation.SuppressLint
+import android.media.AudioAttributes
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+/** A fake [VibratorHelper] that only keeps track of the latest vibration effects delivered */
+@SuppressLint("VisibleForTests")
+class FakeVibratorHelper : VibratorHelper(EmptyVibrator(), FakeExecutor(FakeSystemClock())) {
+
+    /** A customizable map of primitive ids and their durations in ms */
+    val primitiveDurations: HashMap<Int, Int> = ALL_PRIMITIVE_DURATIONS
+
+    private val vibrationEffectHistory = ArrayList<VibrationEffect>()
+
+    val totalVibrations: Int
+        get() = vibrationEffectHistory.size
+
+    override fun vibrate(effect: VibrationEffect) {
+        vibrationEffectHistory.add(effect)
+    }
+
+    override fun vibrate(effect: VibrationEffect, attributes: VibrationAttributes) = vibrate(effect)
+
+    override fun vibrate(effect: VibrationEffect, attributes: AudioAttributes) = vibrate(effect)
+
+    override fun vibrate(
+        uid: Int,
+        opPkg: String?,
+        vibe: VibrationEffect,
+        reason: String?,
+        attributes: VibrationAttributes,
+    ) = vibrate(vibe)
+
+    override fun getPrimitiveDurations(vararg primitiveIds: Int): IntArray =
+        primitiveIds.map { primitiveDurations[it] ?: 0 }.toIntArray()
+
+    fun hasVibratedWithEffects(vararg effects: VibrationEffect): Boolean =
+        vibrationEffectHistory.containsAll(effects.toList())
+
+    fun timesVibratedWithEffect(effect: VibrationEffect): Int =
+        vibrationEffectHistory.count { it == effect }
+
+    companion object {
+        val ALL_PRIMITIVE_DURATIONS =
+            hashMapOf(
+                VibrationEffect.Composition.PRIMITIVE_NOOP to 0,
+                VibrationEffect.Composition.PRIMITIVE_CLICK to 12,
+                VibrationEffect.Composition.PRIMITIVE_THUD to 300,
+                VibrationEffect.Composition.PRIMITIVE_SPIN to 133,
+                VibrationEffect.Composition.PRIMITIVE_QUICK_RISE to 150,
+                VibrationEffect.Composition.PRIMITIVE_SLOW_RISE to 500,
+                VibrationEffect.Composition.PRIMITIVE_QUICK_FALL to 100,
+                VibrationEffect.Composition.PRIMITIVE_TICK to 5,
+                VibrationEffect.Composition.PRIMITIVE_LOW_TICK to 12,
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
new file mode 100644
index 0000000..434953f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.systemui.haptics
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.vibratorHelper by Kosmos.Fixture { FakeVibratorHelper() }
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 08093c0..e64a87f 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -45,7 +45,6 @@
 
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.KnownPackages;
 import com.android.server.pm.PackageArchiver;
 import com.android.server.pm.PackageList;
@@ -1396,21 +1395,6 @@
             @UserIdInt int userId, @Nullable String recentCallingPackage,
             @NonNull String debugInfo);
 
-    /** @deprecated For legacy shell command only. */
-    @Deprecated
-    public abstract void legacyDumpProfiles(@NonNull String packageName,
-            boolean dumpClassesAndMethods) throws LegacyDexoptDisabledException;
-
-    /** @deprecated For legacy shell command only. */
-    @Deprecated
-    public abstract void legacyForceDexOpt(@NonNull String packageName)
-            throws LegacyDexoptDisabledException;
-
-    /** @deprecated For legacy shell command only. */
-    @Deprecated
-    public abstract void legacyReconcileSecondaryDexFiles(String packageName)
-            throws LegacyDexoptDisabledException;
-
     /**
      * Gets {@link PackageManager.DistractionRestriction restrictions} of the given
      * packages of the given user.
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 70d447f..a917909 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -276,7 +276,15 @@
     private final SparseArray<UserState> mStartedUsers = new SparseArray<>();
 
     /**
-     * LRU list of history of current users.  Most recently current is at the end.
+     * LRU list of history of running users, in order of when we last needed to start them.
+     *
+     * Switching to a user will move it towards the end. Attempting to start a user/profile (even
+     * if it was already running) will move it towards the end.
+     *
+     * <p>Guarantees (by the end of startUser):
+     * <li>The current user will always be at the end, even if background users were started
+     * subsequently.
+     * <li>Parents always come later than (but not necessarily adjacent to) their profiles.
      */
     @GuardedBy("mLock")
     private final ArrayList<Integer> mUserLru = new ArrayList<>();
@@ -299,6 +307,9 @@
     /**
      * Mapping from each known user ID to the profile group ID it is associated with.
      * <p>Users not present in this array have a profile group of NO_PROFILE_GROUP_ID.
+     *
+     * <p>For better or worse, this class sometimes assumes that the profileGroupId of a parent user
+     * is always identical with its userId. If that ever becomes false, this class needs updating.
      */
     @GuardedBy("mLock")
     private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();
@@ -499,6 +510,23 @@
         });
     }
 
+    /** Adds a user to mUserLru, moving it to the end of the list if it was already present. */
+    private void addUserToUserLru(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final Integer userIdObj = userId;
+            mUserLru.remove(userIdObj);
+            mUserLru.add(userIdObj);
+
+            // Now also move the user's parent to the end (if applicable).
+            Integer parentIdObj = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+            if (parentIdObj != UserInfo.NO_PROFILE_GROUP_ID && !parentIdObj.equals(userIdObj)
+                    && mUserLru.remove(parentIdObj)) {
+                mUserLru.add(parentIdObj);
+            }
+        }
+    }
+
+    /** Returns a list of running users, in order of when they were started (oldest first). */
     @GuardedBy("mLock")
     @VisibleForTesting
     List<Integer> getRunningUsersLU() {
@@ -536,9 +564,9 @@
 
     @GuardedBy("mLock")
     private void stopExcessRunningUsersLU(int maxRunningUsers, ArraySet<Integer> exemptedUsers) {
-        List<Integer> currentlyRunning = getRunningUsersLU();
-        Iterator<Integer> iterator = currentlyRunning.iterator();
-        while (currentlyRunning.size() > maxRunningUsers && iterator.hasNext()) {
+        List<Integer> currentlyRunningLru = getRunningUsersLU();
+        Iterator<Integer> iterator = currentlyRunningLru.iterator();
+        while (currentlyRunningLru.size() > maxRunningUsers && iterator.hasNext()) {
             Integer userId = iterator.next();
             if (userId == UserHandle.USER_SYSTEM
                     || userId == mCurrentUserId
@@ -551,6 +579,10 @@
             if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
                     /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
                     == USER_OP_SUCCESS) {
+                // Technically, stopUsersLU can remove more than one user when stopping a parent.
+                // But mUserLru is designed so that profiles always precede their parent, so this
+                // normally won't happen here, and therefore won't cause underestimating the number
+                // removed.
                 iterator.remove();
             }
         }
@@ -947,7 +979,7 @@
     }
 
     /**
-     * Stops the user along with its related users. The method calls
+     * Stops the user along with its profiles. The method calls
      * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
      */
     @GuardedBy("mLock")
@@ -992,7 +1024,12 @@
     }
 
     /**
-     * Stops a single User. This can also trigger locking user data out depending on device's
+     * Stops a single User.
+     *
+     * This should only ever be called by {@link #stopUsersLU},
+     * which is responsible to making sure any associated users are appropriately stopped too.
+     *
+     * This can also trigger locking user data out depending on device's
      * config ({@code mDelayUserDataLocking}) and arguments.
      *
      * In the default configuration for most device and users, users will be locked when stopping.
@@ -1425,7 +1462,8 @@
 
     /**
      * Determines the list of users that should be stopped together with the specified
-     * {@code userId}. The returned list includes {@code userId}.
+     * {@code userId}, i.e. the user and its profiles (if the given user is a parent).
+     * The returned list includes {@code userId}.
      */
     @GuardedBy("mLock")
     private @NonNull int[] getUsersToStopLU(@UserIdInt int userId) {
@@ -1433,20 +1471,23 @@
         IntArray userIds = new IntArray();
         userIds.add(userId);
         int userGroupId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
-        for (int i = 0; i < startedUsersSize; i++) {
-            UserState uss = mStartedUsers.valueAt(i);
-            int startedUserId = uss.mHandle.getIdentifier();
-            // Skip unrelated users (profileGroupId mismatch)
-            int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
-                    UserInfo.NO_PROFILE_GROUP_ID);
-            boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
-                    && (userGroupId == startedUserGroupId);
-            // userId has already been added
-            boolean sameUserId = startedUserId == userId;
-            if (!sameGroup || sameUserId) {
-                continue;
+        if (userGroupId == userId) {
+            // The user is the parent of the profile group. Stop its profiles too.
+            for (int i = 0; i < startedUsersSize; i++) {
+                UserState uss = mStartedUsers.valueAt(i);
+                int startedUserId = uss.mHandle.getIdentifier();
+                // Skip unrelated users (profileGroupId mismatch)
+                int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
+                        UserInfo.NO_PROFILE_GROUP_ID);
+                boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
+                        && (userGroupId == startedUserGroupId);
+                // userId has already been added
+                boolean sameUserId = startedUserId == userId;
+                if (!sameGroup || sameUserId) {
+                    continue;
+                }
+                userIds.add(startedUserId);
             }
-            userIds.add(startedUserId);
         }
         return userIds.toArray();
     }
@@ -1519,6 +1560,7 @@
         });
     }
 
+    /** Starts all applicable profiles of the current user. */
     private void startProfiles() {
         int currentUserId = getCurrentUserId();
         if (DEBUG_MU) Slogf.i(TAG, "startProfilesLocked");
@@ -1711,6 +1753,7 @@
             t.traceBegin("getStartedUserState");
             final int oldUserId = getCurrentUserId();
             if (oldUserId == userId) {
+                // The user we're requested to start is already the current user.
                 final UserState state = getStartedUserState(userId);
                 if (state == null) {
                     Slogf.wtf(TAG, "Current user has no UserState");
@@ -1793,10 +1836,12 @@
                     t.traceEnd(); // updateStartedUserArrayStarting
                     return true;
                 }
-                final Integer userIdInt = userId;
-                mUserLru.remove(userIdInt);
-                mUserLru.add(userIdInt);
             }
+
+            // No matter what, the fact that we're requested to start the user (even if it is
+            // already running) puts it towards the end of the mUserLru list.
+            addUserToUserLru(userId);
+
             if (unlockListener != null) {
                 uss.mUnlockProgress.addListener(unlockListener);
             }
@@ -1835,12 +1880,10 @@
                 }
 
             } else {
-                final Integer currentUserIdInt = mCurrentUserId;
                 updateProfileRelatedCaches();
-                synchronized (mLock) {
-                    mUserLru.remove(currentUserIdInt);
-                    mUserLru.add(currentUserIdInt);
-                }
+                // We are starting a non-foreground user. They have already been added to the end
+                // of mUserLru, so we need to ensure that the foreground user isn't displaced.
+                addUserToUserLru(mCurrentUserId);
             }
             t.traceEnd();
 
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 18ba2cf..9ba88aa 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -45,7 +45,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemServerInitThreadPool;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -256,41 +255,6 @@
                 }
             }
 
-            if (!DexOptHelper.useArtService()) { // ART Service handles this on demand instead.
-                // Prepare the application profiles only for upgrades and
-                // first boot (so that we don't repeat the same operation at
-                // each boot).
-                //
-                // We only have to cover the upgrade and first boot here
-                // because for app installs we prepare the profiles before
-                // invoking dexopt (in installPackageLI).
-                //
-                // We also have to cover non system users because we do not
-                // call the usual install package methods for them.
-                //
-                // NOTE: in order to speed up first boot time we only create
-                // the current profile and do not update the content of the
-                // reference profile. A system image should already be
-                // configured with the right profile keys and the profiles
-                // for the speed-profile prebuilds should already be copied.
-                // That's done in #performDexOptUpgrade.
-                //
-                // TODO(calin, mathieuc): We should use .dm files for
-                // prebuilds profiles instead of manually copying them in
-                // #performDexOptUpgrade. When we do that we should have a
-                // more granular check here and only update the existing
-                // profiles.
-                if (pkg != null && (mPm.isDeviceUpgrading() || mPm.isFirstBoot()
-                        || (userId != UserHandle.USER_SYSTEM))) {
-                    try {
-                        mArtManagerService.prepareAppProfiles(pkg, userId,
-                                /* updateReferenceProfileContent= */ false);
-                    } catch (LegacyDexoptDisabledException e2) {
-                        throw new RuntimeException(e2);
-                    }
-                }
-            }
-
             final long ceDataInode = createAppDataResult.ceDataInode;
             final long deDataInode = createAppDataResult.deDataInode;
 
@@ -615,15 +579,7 @@
             Slog.wtf(TAG, "Package was null!", new Throwable());
             return;
         }
-        if (DexOptHelper.useArtService()) {
-            destroyAppProfilesWithArtService(pkg.getPackageName());
-        } else {
-            try {
-                mArtManagerService.clearAppProfiles(pkg);
-            } catch (LegacyDexoptDisabledException e) {
-                throw new RuntimeException(e);
-            }
-        }
+        destroyAppProfilesLIF(pkg.getPackageName());
     }
 
     public void destroyAppDataLIF(AndroidPackage pkg, int userId, int flags) {
@@ -657,20 +613,6 @@
      * Destroy ART app profiles for the package.
      */
     void destroyAppProfilesLIF(String packageName) {
-        if (DexOptHelper.useArtService()) {
-            destroyAppProfilesWithArtService(packageName);
-        } else {
-            try {
-                mInstaller.destroyAppProfiles(packageName);
-            } catch (LegacyDexoptDisabledException e) {
-                throw new RuntimeException(e);
-            } catch (Installer.InstallerException e) {
-                Slog.w(TAG, String.valueOf(e));
-            }
-        }
-    }
-
-    private void destroyAppProfilesWithArtService(String packageName) {
         if (!DexOptHelper.artManagerLocalIsInitialized()) {
             // This function may get called while PackageManagerService is constructed (via e.g.
             // InitAppsHelper.initSystemApps), and ART Service hasn't yet been started then (it
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java b/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
deleted file mode 100644
index d945274..0000000
--- a/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 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.job.JobParameters;
-import android.app.job.JobService;
-
-/**
- * JobService to run background dex optimization. This is a thin wrapper and most logic exits in
- * {@link BackgroundDexOptService}.
- */
-public final class BackgroundDexOptJobService extends JobService {
-
-    @Override
-    public boolean onStartJob(JobParameters params) {
-        return BackgroundDexOptService.getService().onStartJob(this, params);
-    }
-
-    @Override
-    public boolean onStopJob(JobParameters params) {
-        return BackgroundDexOptService.getService().onStopJob(this, params);
-    }
-}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
deleted file mode 100644
index 36677df..0000000
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ /dev/null
@@ -1,1152 +0,0 @@
-/*
- * Copyright (C) 2014 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 static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
-import static com.android.server.pm.dex.ArtStatsLogUtils.BackgroundDexoptJobStatsLogger;
-
-import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.os.BatteryManagerInternal;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.storage.StorageManager;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.FunctionalUtils.ThrowingCheckedSupplier;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.PinnerService;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
-import com.android.server.pm.PackageDexOptimizer.DexOptResult;
-import com.android.server.pm.dex.DexManager;
-import com.android.server.pm.dex.DexoptOptions;
-import com.android.server.utils.TimingsTraceAndSlog;
-
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Controls background dex optimization run as idle job or command line.
- */
-public final class BackgroundDexOptService {
-    private static final String TAG = "BackgroundDexOptService";
-
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    @VisibleForTesting static final int JOB_IDLE_OPTIMIZE = 800;
-    @VisibleForTesting static final int JOB_POST_BOOT_UPDATE = 801;
-
-    private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1);
-
-    private static final long CANCELLATION_WAIT_CHECK_INTERVAL_MS = 200;
-
-    private static final ComponentName sDexoptServiceName =
-            new ComponentName("android", BackgroundDexOptJobService.class.getName());
-
-    // Possible return codes of individual optimization steps.
-    /** Initial value. */
-    public static final int STATUS_UNSPECIFIED = -1;
-    /** Ok status: Optimizations finished, All packages were processed, can continue */
-    public static final int STATUS_OK = 0;
-    /** Optimizations should be aborted. Job scheduler requested it. */
-    public static final int STATUS_ABORT_BY_CANCELLATION = 1;
-    /** Optimizations should be aborted. No space left on device. */
-    public static final int STATUS_ABORT_NO_SPACE_LEFT = 2;
-    /** Optimizations should be aborted. Thermal throttling level too high. */
-    public static final int STATUS_ABORT_THERMAL = 3;
-    /** Battery level too low */
-    public static final int STATUS_ABORT_BATTERY = 4;
-    /**
-     * {@link PackageDexOptimizer#DEX_OPT_FAILED} case. This state means some packages have failed
-     * compilation during the job. Note that the failure will not be permanent as the next dexopt
-     * job will exclude those failed packages.
-     */
-    public static final int STATUS_DEX_OPT_FAILED = 5;
-    /** Encountered fatal error, such as a runtime exception. */
-    public static final int STATUS_FATAL_ERROR = 6;
-
-    @IntDef(prefix = {"STATUS_"},
-            value =
-                    {
-                            STATUS_UNSPECIFIED,
-                            STATUS_OK,
-                            STATUS_ABORT_BY_CANCELLATION,
-                            STATUS_ABORT_NO_SPACE_LEFT,
-                            STATUS_ABORT_THERMAL,
-                            STATUS_ABORT_BATTERY,
-                            STATUS_DEX_OPT_FAILED,
-                            STATUS_FATAL_ERROR,
-                    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Status {}
-
-    // Used for calculating space threshold for downgrading unused apps.
-    private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
-
-    // Thermal cutoff value used if one isn't defined by a system property.
-    private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE;
-
-    private final Injector mInjector;
-
-    private final DexOptHelper mDexOptHelper;
-
-    private final BackgroundDexoptJobStatsLogger mStatsLogger =
-            new BackgroundDexoptJobStatsLogger();
-
-    private final Object mLock = new Object();
-
-    // Thread currently running dexopt. This will be null if dexopt is not running.
-    // The thread running dexopt make sure to set this into null when the pending dexopt is
-    // completed.
-    @GuardedBy("mLock") @Nullable private Thread mDexOptThread;
-
-    // Thread currently cancelling dexopt. This thread is in blocked wait state until
-    // cancellation is done. Only this thread can change states for control. The other threads, if
-    // need to wait for cancellation, should just wait without doing any control.
-    @GuardedBy("mLock") @Nullable private Thread mDexOptCancellingThread;
-
-    // Tells whether post boot update is completed or not.
-    @GuardedBy("mLock") private boolean mFinishedPostBootUpdate;
-
-    // True if JobScheduler invocations of dexopt have been disabled.
-    @GuardedBy("mLock") private boolean mDisableJobSchedulerJobs;
-
-    @GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_UNSPECIFIED;
-
-    @GuardedBy("mLock") private long mLastExecutionStartUptimeMs;
-    @GuardedBy("mLock") private long mLastExecutionDurationMs;
-
-    // Keeps packages cancelled from PDO for last session. This is for debugging.
-    @GuardedBy("mLock")
-    private final ArraySet<String> mLastCancelledPackages = new ArraySet<String>();
-
-    /**
-     * Set of failed packages remembered across job runs.
-     */
-    @GuardedBy("mLock")
-    private final ArraySet<String> mFailedPackageNamesPrimary = new ArraySet<String>();
-    @GuardedBy("mLock")
-    private final ArraySet<String> mFailedPackageNamesSecondary = new ArraySet<String>();
-
-    private final long mDowngradeUnusedAppsThresholdInMillis;
-
-    private final List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>();
-
-    private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
-
-    /** Listener for monitoring package change due to dexopt. */
-    public interface PackagesUpdatedListener {
-        /** Called when the packages are updated through dexopt */
-        void onPackagesUpdated(ArraySet<String> updatedPackages);
-    }
-
-    public BackgroundDexOptService(Context context, DexManager dexManager, PackageManagerService pm)
-            throws LegacyDexoptDisabledException {
-        this(new Injector(context, dexManager, pm));
-    }
-
-    @VisibleForTesting
-    public BackgroundDexOptService(Injector injector) throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-        mInjector = injector;
-        mDexOptHelper = mInjector.getDexOptHelper();
-        LocalServices.addService(BackgroundDexOptService.class, this);
-        mDowngradeUnusedAppsThresholdInMillis = mInjector.getDowngradeUnusedAppsThresholdInMillis();
-    }
-
-    /** Start scheduling job after boot completion */
-    public void systemReady() throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-        if (mInjector.isBackgroundDexOptDisabled()) {
-            return;
-        }
-
-        mInjector.getContext().registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                mInjector.getContext().unregisterReceiver(this);
-                // queue both job. JOB_IDLE_OPTIMIZE will not start until JOB_POST_BOOT_UPDATE is
-                // completed.
-                scheduleAJob(JOB_POST_BOOT_UPDATE);
-                scheduleAJob(JOB_IDLE_OPTIMIZE);
-                if (DEBUG) {
-                    Slog.d(TAG, "BootBgDexopt scheduled");
-                }
-            }
-        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
-    }
-
-    /** Dump the current state */
-    public void dump(IndentingPrintWriter writer) {
-        boolean disabled = mInjector.isBackgroundDexOptDisabled();
-        writer.print("enabled:");
-        writer.println(!disabled);
-        if (disabled) {
-            return;
-        }
-        synchronized (mLock) {
-            writer.print("mDexOptThread:");
-            writer.println(mDexOptThread);
-            writer.print("mDexOptCancellingThread:");
-            writer.println(mDexOptCancellingThread);
-            writer.print("mFinishedPostBootUpdate:");
-            writer.println(mFinishedPostBootUpdate);
-            writer.print("mDisableJobSchedulerJobs:");
-            writer.println(mDisableJobSchedulerJobs);
-            writer.print("mLastExecutionStatus:");
-            writer.println(mLastExecutionStatus);
-            writer.print("mLastExecutionStartUptimeMs:");
-            writer.println(mLastExecutionStartUptimeMs);
-            writer.print("mLastExecutionDurationMs:");
-            writer.println(mLastExecutionDurationMs);
-            writer.print("now:");
-            writer.println(SystemClock.elapsedRealtime());
-            writer.print("mLastCancelledPackages:");
-            writer.println(String.join(",", mLastCancelledPackages));
-            writer.print("mFailedPackageNamesPrimary:");
-            writer.println(String.join(",", mFailedPackageNamesPrimary));
-            writer.print("mFailedPackageNamesSecondary:");
-            writer.println(String.join(",", mFailedPackageNamesSecondary));
-        }
-    }
-
-    /** Gets the instance of the service */
-    public static BackgroundDexOptService getService() {
-        return LocalServices.getService(BackgroundDexOptService.class);
-    }
-
-    /**
-     * Executes the background dexopt job immediately for selected packages or all packages.
-     *
-     * <p>This is only for shell command and only root or shell user can use this.
-     *
-     * @param packageNames dex optimize the passed packages in the given order, or all packages in
-     *         the default order if null
-     *
-     * @return true if dex optimization is complete. false if the task is cancelled or if there was
-     *         an error.
-     */
-    public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames)
-            throws LegacyDexoptDisabledException {
-        enforceRootOrShell();
-        long identity = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                // Do not cancel and wait for completion if there is pending task.
-                waitForDexOptThreadToFinishLocked();
-                resetStatesForNewDexOptRunLocked(Thread.currentThread());
-            }
-            PackageManagerService pm = mInjector.getPackageManagerService();
-            List<String> packagesToOptimize;
-            if (packageNames == null) {
-                packagesToOptimize = mDexOptHelper.getOptimizablePackages(pm.snapshotComputer());
-            } else {
-                packagesToOptimize = packageNames;
-            }
-            return runIdleOptimization(pm, packagesToOptimize, /* isPostBootUpdate= */ false);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-            markDexOptCompleted();
-        }
-    }
-
-    /**
-     * Cancels currently running any idle optimization tasks started from JobScheduler
-     * or runIdleOptimization call.
-     *
-     * <p>This is only for shell command and only root or shell user can use this.
-     */
-    public void cancelBackgroundDexoptJob() throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-        enforceRootOrShell();
-        Binder.withCleanCallingIdentity(() -> cancelDexOptAndWaitForCompletion());
-    }
-
-    /**
-     * Sets a flag that disables jobs from being started from JobScheduler.
-     *
-     * This state is not persistent and is only retained in this service instance.
-     *
-     * This is intended for shell command use and only root or shell users can call it.
-     *
-     * @param disable True if JobScheduler invocations should be disabled, false otherwise.
-     */
-    public void setDisableJobSchedulerJobs(boolean disable) throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-        enforceRootOrShell();
-        synchronized (mLock) {
-            mDisableJobSchedulerJobs = disable;
-        }
-    }
-
-    /** Adds listener for package update */
-    public void addPackagesUpdatedListener(PackagesUpdatedListener listener)
-            throws LegacyDexoptDisabledException {
-        // TODO(b/251903639): Evaluate whether this needs to support ART Service or not.
-        Installer.checkLegacyDexoptDisabled();
-        synchronized (mLock) {
-            mPackagesUpdatedListeners.add(listener);
-        }
-    }
-
-    /** Removes package update listener */
-    public void removePackagesUpdatedListener(PackagesUpdatedListener listener)
-            throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-        synchronized (mLock) {
-            mPackagesUpdatedListeners.remove(listener);
-        }
-    }
-
-    /**
-     * Notifies package change and removes the package from the failed package list so that
-     * the package can run dexopt again.
-     */
-    public void notifyPackageChanged(String packageName) throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-        // The idle maintenance job skips packages which previously failed to
-        // compile. The given package has changed and may successfully compile
-        // now. Remove it from the list of known failing packages.
-        synchronized (mLock) {
-            mFailedPackageNamesPrimary.remove(packageName);
-            mFailedPackageNamesSecondary.remove(packageName);
-        }
-    }
-
-    /** For BackgroundDexOptJobService to dispatch onStartJob event */
-    /* package */ boolean onStartJob(BackgroundDexOptJobService job, JobParameters params) {
-        Slog.i(TAG, "onStartJob:" + params.getJobId());
-
-        boolean isPostBootUpdateJob = params.getJobId() == JOB_POST_BOOT_UPDATE;
-        // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
-        // the checks above. This check is not "live" - the value is determined by a background
-        // restart with a period of ~1 minute.
-        PackageManagerService pm = mInjector.getPackageManagerService();
-        if (pm.isStorageLow()) {
-            Slog.w(TAG, "Low storage, skipping this run");
-            markPostBootUpdateCompleted(params);
-            return false;
-        }
-
-        List<String> pkgs = mDexOptHelper.getOptimizablePackages(pm.snapshotComputer());
-        if (pkgs.isEmpty()) {
-            Slog.i(TAG, "No packages to optimize");
-            markPostBootUpdateCompleted(params);
-            return false;
-        }
-
-        mThermalStatusCutoff = mInjector.getDexOptThermalCutoff();
-
-        synchronized (mLock) {
-            if (mDisableJobSchedulerJobs) {
-                Slog.i(TAG, "JobScheduler invocations disabled");
-                return false;
-            }
-            if (mDexOptThread != null && mDexOptThread.isAlive()) {
-                // Other task is already running.
-                return false;
-            }
-            if (!isPostBootUpdateJob && !mFinishedPostBootUpdate) {
-                // Post boot job not finished yet. Run post boot job first.
-                return false;
-            }
-            try {
-                resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread(
-                        "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
-                        () -> {
-                            TimingsTraceAndSlog tr =
-                                    new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_DALVIK);
-                            tr.traceBegin("jobExecution");
-                            boolean completed = false;
-                            boolean fatalError = false;
-                            try {
-                                completed = runIdleOptimization(
-                                        pm, pkgs, params.getJobId() == JOB_POST_BOOT_UPDATE);
-                            } catch (LegacyDexoptDisabledException e) {
-                                Slog.wtf(TAG, e);
-                            } catch (RuntimeException e) {
-                                fatalError = true;
-                                throw e;
-                            } finally { // Those cleanup should be done always.
-                                tr.traceEnd();
-                                Slog.i(TAG,
-                                        "dexopt finishing. jobid:" + params.getJobId()
-                                                + " completed:" + completed);
-
-                                writeStatsLog(params);
-
-                                if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
-                                    if (completed) {
-                                        markPostBootUpdateCompleted(params);
-                                    }
-                                }
-                                // Reschedule when cancelled. No need to reschedule when failed with
-                                // fatal error because it's likely to fail again.
-                                job.jobFinished(params, !completed && !fatalError);
-                                markDexOptCompleted();
-                            }
-                        }));
-            } catch (LegacyDexoptDisabledException e) {
-                Slog.wtf(TAG, e);
-            }
-        }
-        return true;
-    }
-
-    /** For BackgroundDexOptJobService to dispatch onStopJob event */
-    /* package */ boolean onStopJob(BackgroundDexOptJobService job, JobParameters params) {
-        Slog.i(TAG, "onStopJob:" + params.getJobId());
-        // This cannot block as it is in main thread, thus dispatch to a newly created thread
-        // and cancel it from there. As this event does not happen often, creating a new thread
-        // is justified rather than having one thread kept permanently.
-        mInjector.createAndStartThread("DexOptCancel", () -> {
-            try {
-                cancelDexOptAndWaitForCompletion();
-            } catch (LegacyDexoptDisabledException e) {
-                Slog.wtf(TAG, e);
-            }
-        });
-        // Always reschedule for cancellation.
-        return true;
-    }
-
-    /**
-     * Cancels pending dexopt and wait for completion of the cancellation. This can block the caller
-     * until cancellation is done.
-     */
-    private void cancelDexOptAndWaitForCompletion() throws LegacyDexoptDisabledException {
-        synchronized (mLock) {
-            if (mDexOptThread == null) {
-                return;
-            }
-            if (mDexOptCancellingThread != null && mDexOptCancellingThread.isAlive()) {
-                // No control, just wait
-                waitForDexOptThreadToFinishLocked();
-                // Do not wait for other cancellation's complete. That will be handled by the next
-                // start flow.
-                return;
-            }
-            mDexOptCancellingThread = Thread.currentThread();
-            // Take additional caution to make sure that we do not leave this call
-            // with controlDexOptBlockingLocked(true) state.
-            try {
-                controlDexOptBlockingLocked(true);
-                waitForDexOptThreadToFinishLocked();
-            } finally {
-                // Reset to default states regardless of previous states
-                mDexOptCancellingThread = null;
-                mDexOptThread = null;
-                controlDexOptBlockingLocked(false);
-                mLock.notifyAll();
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void waitForDexOptThreadToFinishLocked() {
-        TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
-        // This tracing section doesn't have any correspondence in ART Service - it never waits for
-        // cancellation to finish.
-        tr.traceBegin("waitForDexOptThreadToFinishLocked");
-        try {
-            // Wait but check in regular internal to see if the thread is still alive.
-            while (mDexOptThread != null && mDexOptThread.isAlive()) {
-                mLock.wait(CANCELLATION_WAIT_CHECK_INTERVAL_MS);
-            }
-        } catch (InterruptedException e) {
-            Slog.w(TAG, "Interrupted while waiting for dexopt thread");
-            Thread.currentThread().interrupt();
-        }
-        tr.traceEnd();
-    }
-
-    private void markDexOptCompleted() {
-        synchronized (mLock) {
-            if (mDexOptThread != Thread.currentThread()) {
-                throw new IllegalStateException(
-                        "Only mDexOptThread can mark completion, mDexOptThread:" + mDexOptThread
-                        + " current:" + Thread.currentThread());
-            }
-            mDexOptThread = null;
-            // Other threads may be waiting for completion.
-            mLock.notifyAll();
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void resetStatesForNewDexOptRunLocked(Thread thread)
-            throws LegacyDexoptDisabledException {
-        mDexOptThread = thread;
-        mLastCancelledPackages.clear();
-        controlDexOptBlockingLocked(false);
-    }
-
-    private void enforceRootOrShell() {
-        int uid = mInjector.getCallingUid();
-        if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
-            throw new SecurityException("Should be shell or root user");
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void controlDexOptBlockingLocked(boolean block) throws LegacyDexoptDisabledException {
-        PackageManagerService pm = mInjector.getPackageManagerService();
-        mDexOptHelper.controlDexOptBlocking(block);
-    }
-
-    private void scheduleAJob(int jobId) {
-        JobScheduler js = mInjector.getJobScheduler();
-        JobInfo.Builder builder =
-                new JobInfo.Builder(jobId, sDexoptServiceName).setRequiresDeviceIdle(true);
-        if (jobId == JOB_IDLE_OPTIMIZE) {
-            builder.setRequiresCharging(true).setPeriodic(IDLE_OPTIMIZATION_PERIOD);
-        }
-        js.schedule(builder.build());
-    }
-
-    private long getLowStorageThreshold() {
-        long lowThreshold = mInjector.getDataDirStorageLowBytes();
-        if (lowThreshold == 0) {
-            Slog.e(TAG, "Invalid low storage threshold");
-        }
-
-        return lowThreshold;
-    }
-
-    private void logStatus(int status) {
-        switch (status) {
-            case STATUS_OK:
-                Slog.i(TAG, "Idle optimizations completed.");
-                break;
-            case STATUS_ABORT_NO_SPACE_LEFT:
-                Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
-                break;
-            case STATUS_ABORT_BY_CANCELLATION:
-                Slog.w(TAG, "Idle optimizations aborted by cancellation.");
-                break;
-            case STATUS_ABORT_THERMAL:
-                Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
-                break;
-            case STATUS_ABORT_BATTERY:
-                Slog.w(TAG, "Idle optimizations aborted by low battery.");
-                break;
-            case STATUS_DEX_OPT_FAILED:
-                Slog.w(TAG, "Idle optimizations failed from dexopt.");
-                break;
-            default:
-                Slog.w(TAG, "Idle optimizations ended with unexpected code: " + status);
-                break;
-        }
-    }
-
-    /**
-     * Returns whether we've successfully run the job. Note that it will return true even if some
-     * packages may have failed compiling.
-     */
-    private boolean runIdleOptimization(PackageManagerService pm, List<String> pkgs,
-            boolean isPostBootUpdate) throws LegacyDexoptDisabledException {
-        synchronized (mLock) {
-            mLastExecutionStatus = STATUS_UNSPECIFIED;
-            mLastExecutionStartUptimeMs = SystemClock.uptimeMillis();
-            mLastExecutionDurationMs = -1;
-        }
-
-        int status = STATUS_UNSPECIFIED;
-        try {
-            long lowStorageThreshold = getLowStorageThreshold();
-            status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
-            logStatus(status);
-            return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED;
-        } catch (RuntimeException e) {
-            status = STATUS_FATAL_ERROR;
-            throw e;
-        } finally {
-            synchronized (mLock) {
-                mLastExecutionStatus = status;
-                mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs;
-            }
-        }
-    }
-
-    /** Gets the size of the directory. It uses recursion to go over all files. */
-    private long getDirectorySize(File f) {
-        long size = 0;
-        if (f.isDirectory()) {
-            for (File file : f.listFiles()) {
-                size += getDirectorySize(file);
-            }
-        } else {
-            size = f.length();
-        }
-        return size;
-    }
-
-    /** Gets the size of a package. */
-    private long getPackageSize(@NonNull Computer snapshot, String pkg) {
-        // TODO(b/251903639): Make this in line with the calculation in
-        // `DexOptHelper.DexoptDoneHandler`.
-        PackageInfo info = snapshot.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
-        long size = 0;
-        if (info != null && info.applicationInfo != null) {
-            File path = Paths.get(info.applicationInfo.sourceDir).toFile();
-            if (path.isFile()) {
-                path = path.getParentFile();
-            }
-            size += getDirectorySize(path);
-            if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
-                for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
-                    File pathSplitSourceDir = Paths.get(splitSourceDir).toFile();
-                    if (pathSplitSourceDir.isFile()) {
-                        pathSplitSourceDir = pathSplitSourceDir.getParentFile();
-                    }
-                    if (path.getAbsolutePath().equals(pathSplitSourceDir.getAbsolutePath())) {
-                        continue;
-                    }
-                    size += getDirectorySize(pathSplitSourceDir);
-                }
-            }
-            return size;
-        }
-        return 0;
-    }
-
-    @Status
-    private int idleOptimizePackages(PackageManagerService pm, List<String> pkgs,
-            long lowStorageThreshold, boolean isPostBootUpdate)
-            throws LegacyDexoptDisabledException {
-        ArraySet<String> updatedPackages = new ArraySet<>();
-
-        try {
-            boolean supportSecondaryDex = mInjector.supportSecondaryDex();
-
-            if (supportSecondaryDex) {
-                @Status int result = reconcileSecondaryDexFiles();
-                if (result != STATUS_OK) {
-                    return result;
-                }
-            }
-
-            // Only downgrade apps when space is low on device.
-            // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
-            // up disk before user hits the actual lowStorageThreshold.
-            long lowStorageThresholdForDowngrade =
-                    LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * lowStorageThreshold;
-            boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
-            if (DEBUG) {
-                Slog.d(TAG, "Should Downgrade " + shouldDowngrade);
-            }
-            if (shouldDowngrade) {
-                final Computer snapshot = pm.snapshotComputer();
-                Set<String> unusedPackages =
-                        snapshot.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
-                if (DEBUG) {
-                    Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
-                }
-
-                if (!unusedPackages.isEmpty()) {
-                    for (String pkg : unusedPackages) {
-                        @Status int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
-                        if (abortCode != STATUS_OK) {
-                            // Should be aborted by the scheduler.
-                            return abortCode;
-                        }
-                        @DexOptResult
-                        int downgradeResult = downgradePackage(snapshot, pm, pkg,
-                                /* isForPrimaryDex= */ true, isPostBootUpdate);
-                        if (downgradeResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
-                            updatedPackages.add(pkg);
-                        }
-                        @Status
-                        int status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
-                        if (status != STATUS_OK) {
-                            return status;
-                        }
-                        if (supportSecondaryDex) {
-                            downgradeResult = downgradePackage(snapshot, pm, pkg,
-                                    /* isForPrimaryDex= */ false, isPostBootUpdate);
-                            status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
-                            if (status != STATUS_OK) {
-                                return status;
-                            }
-                        }
-                    }
-
-                    pkgs = new ArrayList<>(pkgs);
-                    pkgs.removeAll(unusedPackages);
-                }
-            }
-
-            return optimizePackages(pkgs, lowStorageThreshold, updatedPackages, isPostBootUpdate);
-        } finally {
-            // Always let the pinner service know about changes.
-            // TODO(b/251903639): ART Service does this for all dexopts, while the code below only
-            // runs for background jobs. We should try to make them behave the same.
-            notifyPinService(updatedPackages);
-            // Only notify IORap the primary dex opt, because we don't want to
-            // invalidate traces unnecessary due to b/161633001 and that it's
-            // better to have a trace than no trace at all.
-            notifyPackagesUpdated(updatedPackages);
-        }
-    }
-
-    @Status
-    private int optimizePackages(List<String> pkgs, long lowStorageThreshold,
-            ArraySet<String> updatedPackages, boolean isPostBootUpdate)
-            throws LegacyDexoptDisabledException {
-        boolean supportSecondaryDex = mInjector.supportSecondaryDex();
-
-        // Keep the error if there is any error from any package.
-        @Status int status = STATUS_OK;
-
-        // Other than cancellation, all packages will be processed even if an error happens
-        // in a package.
-        for (String pkg : pkgs) {
-            int abortCode = abortIdleOptimizations(lowStorageThreshold);
-            if (abortCode != STATUS_OK) {
-                // Either aborted by the scheduler or no space left.
-                return abortCode;
-            }
-
-            @DexOptResult
-            int primaryResult = optimizePackage(pkg, true /* isForPrimaryDex */, isPostBootUpdate);
-            if (primaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) {
-                return STATUS_ABORT_BY_CANCELLATION;
-            }
-            if (primaryResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
-                updatedPackages.add(pkg);
-            } else if (primaryResult == PackageDexOptimizer.DEX_OPT_FAILED) {
-                status = convertPackageDexOptimizerStatusToInternal(primaryResult);
-            }
-
-            if (!supportSecondaryDex) {
-                continue;
-            }
-
-            @DexOptResult
-            int secondaryResult =
-                    optimizePackage(pkg, false /* isForPrimaryDex */, isPostBootUpdate);
-            if (secondaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) {
-                return STATUS_ABORT_BY_CANCELLATION;
-            }
-            if (secondaryResult == PackageDexOptimizer.DEX_OPT_FAILED) {
-                status = convertPackageDexOptimizerStatusToInternal(secondaryResult);
-            }
-        }
-        return status;
-    }
-
-    /**
-     * Try to downgrade the package to a smaller compilation filter.
-     * eg. if the package is in speed-profile the package will be downgraded to verify.
-     * @param pm PackageManagerService
-     * @param pkg The package to be downgraded.
-     * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
-     * @return PackageDexOptimizer.DEX_*
-     */
-    @DexOptResult
-    private int downgradePackage(@NonNull Computer snapshot, PackageManagerService pm, String pkg,
-            boolean isForPrimaryDex, boolean isPostBootUpdate)
-            throws LegacyDexoptDisabledException {
-        if (DEBUG) {
-            Slog.d(TAG, "Downgrading " + pkg);
-        }
-        if (isCancelling()) {
-            return PackageDexOptimizer.DEX_OPT_CANCELLED;
-        }
-        int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
-        String filter = getCompilerFilterForReason(reason);
-        int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE;
-
-        if (isProfileGuidedCompilerFilter(filter)) {
-            // We don't expect updates in current profiles to be significant here, but
-            // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
-            // unconditionally enabled for profile guided filters when ART Service is called instead
-            // of the legacy PackageDexOptimizer implementation.
-            dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
-        }
-
-        if (!isPostBootUpdate) {
-            dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
-        }
-
-        long package_size_before = getPackageSize(snapshot, pkg);
-        int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
-        if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
-            // This applies for system apps or if packages location is not a directory, i.e.
-            // monolithic install.
-            if (!pm.canHaveOatDir(snapshot, pkg)) {
-                // For apps that don't have the oat directory, instead of downgrading,
-                // remove their compiler artifacts from dalvik cache.
-                pm.deleteOatArtifactsOfPackage(snapshot, pkg);
-            } else {
-                result = performDexOptPrimary(pkg, reason, filter, dexoptFlags);
-            }
-        } else {
-            result = performDexOptSecondary(pkg, reason, filter, dexoptFlags);
-        }
-
-        if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
-            final Computer newSnapshot = pm.snapshotComputer();
-            FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before,
-                    getPackageSize(newSnapshot, pkg), /*aggressive=*/false);
-        }
-        return result;
-    }
-
-    @Status
-    private int reconcileSecondaryDexFiles() throws LegacyDexoptDisabledException {
-        // TODO(calin): should we denylist packages for which we fail to reconcile?
-        for (String p : mInjector.getDexManager().getAllPackagesWithSecondaryDexFiles()) {
-            if (isCancelling()) {
-                return STATUS_ABORT_BY_CANCELLATION;
-            }
-            mInjector.getDexManager().reconcileSecondaryDexFiles(p);
-        }
-        return STATUS_OK;
-    }
-
-    /**
-     *
-     * Optimize package if needed. Note that there can be no race between
-     * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
-     * @param pkg The package to be downgraded.
-     * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
-     * @param isPostBootUpdate is post boot update or not.
-     * @return PackageDexOptimizer#DEX_OPT_*
-     */
-    @DexOptResult
-    private int optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate)
-            throws LegacyDexoptDisabledException {
-        int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
-                                      : PackageManagerService.REASON_BACKGROUND_DEXOPT;
-        String filter = getCompilerFilterForReason(reason);
-
-        int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE;
-        if (!isPostBootUpdate) {
-            dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
-                    | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
-        }
-
-        if (isProfileGuidedCompilerFilter(filter)) {
-            // Ensure DEXOPT_CHECK_FOR_PROFILES_UPDATES is enabled if the filter is profile guided,
-            // to replicate behaviour that will be unconditionally enabled when ART Service is
-            // called instead of the legacy PackageDexOptimizer implementation.
-            dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
-        }
-
-        // System server share the same code path as primary dex files.
-        // PackageManagerService will select the right optimization path for it.
-        if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
-            return performDexOptPrimary(pkg, reason, filter, dexoptFlags);
-        } else {
-            return performDexOptSecondary(pkg, reason, filter, dexoptFlags);
-        }
-    }
-
-    @DexOptResult
-    private int performDexOptPrimary(String pkg, int reason, String filter, int dexoptFlags)
-            throws LegacyDexoptDisabledException {
-        DexoptOptions dexoptOptions =
-                new DexoptOptions(pkg, reason, filter, /*splitName=*/null, dexoptFlags);
-        return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/true,
-                () -> mDexOptHelper.performDexOptWithStatus(dexoptOptions));
-    }
-
-    @DexOptResult
-    private int performDexOptSecondary(String pkg, int reason, String filter, int dexoptFlags)
-            throws LegacyDexoptDisabledException {
-        DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, filter, /*splitName=*/null,
-                dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
-        return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/false,
-                ()
-                        -> mDexOptHelper.performDexOpt(dexoptOptions)
-                        ? PackageDexOptimizer.DEX_OPT_PERFORMED
-                        : PackageDexOptimizer.DEX_OPT_FAILED);
-    }
-
-    /**
-     * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
-     * the package is added to the list of failed packages.
-     * Return one of following result:
-     *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
-     *  {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
-     *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
-     *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
-     */
-    @DexOptResult
-    private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
-            ThrowingCheckedSupplier<Integer, LegacyDexoptDisabledException> performDexOptWrapper)
-            throws LegacyDexoptDisabledException {
-        ArraySet<String> failedPackageNames;
-        synchronized (mLock) {
-            failedPackageNames =
-                    isForPrimaryDex ? mFailedPackageNamesPrimary : mFailedPackageNamesSecondary;
-            if (failedPackageNames.contains(pkg)) {
-                // Skip previously failing package
-                return PackageDexOptimizer.DEX_OPT_SKIPPED;
-            }
-        }
-        int result = performDexOptWrapper.get();
-        if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
-            synchronized (mLock) {
-                failedPackageNames.add(pkg);
-            }
-        } else if (result == PackageDexOptimizer.DEX_OPT_CANCELLED) {
-            synchronized (mLock) {
-                mLastCancelledPackages.add(pkg);
-            }
-        }
-        return result;
-    }
-
-    @Status
-    private int convertPackageDexOptimizerStatusToInternal(@DexOptResult int pdoStatus) {
-        switch (pdoStatus) {
-            case PackageDexOptimizer.DEX_OPT_CANCELLED:
-                return STATUS_ABORT_BY_CANCELLATION;
-            case PackageDexOptimizer.DEX_OPT_FAILED:
-                return STATUS_DEX_OPT_FAILED;
-            case PackageDexOptimizer.DEX_OPT_PERFORMED:
-            case PackageDexOptimizer.DEX_OPT_SKIPPED:
-                return STATUS_OK;
-            default:
-                Slog.e(TAG, "Unkknown error code from PackageDexOptimizer:" + pdoStatus,
-                        new RuntimeException());
-                return STATUS_DEX_OPT_FAILED;
-        }
-    }
-
-    /** Evaluate whether or not idle optimizations should continue. */
-    @Status
-    private int abortIdleOptimizations(long lowStorageThreshold) {
-        if (isCancelling()) {
-            // JobScheduler requested an early abort.
-            return STATUS_ABORT_BY_CANCELLATION;
-        }
-
-        // Abort background dexopt if the device is in a moderate or stronger thermal throttling
-        // state.
-        int thermalStatus = mInjector.getCurrentThermalStatus();
-        if (DEBUG) {
-            Log.d(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
-        }
-        if (thermalStatus >= mThermalStatusCutoff) {
-            return STATUS_ABORT_THERMAL;
-        }
-
-        if (mInjector.isBatteryLevelLow()) {
-            return STATUS_ABORT_BATTERY;
-        }
-
-        long usableSpace = mInjector.getDataDirUsableSpace();
-        if (usableSpace < lowStorageThreshold) {
-            // Rather bail than completely fill up the disk.
-            Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
-            return STATUS_ABORT_NO_SPACE_LEFT;
-        }
-
-        return STATUS_OK;
-    }
-
-    // Evaluate whether apps should be downgraded.
-    private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) {
-        if (mInjector.getDataDirUsableSpace() < lowStorageThresholdForDowngrade) {
-            return true;
-        }
-
-        return false;
-    }
-
-    private boolean isCancelling() {
-        synchronized (mLock) {
-            return mDexOptCancellingThread != null;
-        }
-    }
-
-    private void markPostBootUpdateCompleted(JobParameters params) {
-        if (params.getJobId() != JOB_POST_BOOT_UPDATE) {
-            return;
-        }
-        synchronized (mLock) {
-            if (!mFinishedPostBootUpdate) {
-                mFinishedPostBootUpdate = true;
-            }
-        }
-        // Safe to do this outside lock.
-        mInjector.getJobScheduler().cancel(JOB_POST_BOOT_UPDATE);
-    }
-
-    private void notifyPinService(ArraySet<String> updatedPackages) {
-        PinnerService pinnerService = mInjector.getPinnerService();
-        if (pinnerService != null) {
-            Slog.i(TAG, "Pinning optimized code " + updatedPackages);
-            pinnerService.update(updatedPackages, false /* force */);
-        }
-    }
-
-    /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
-    private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
-        synchronized (mLock) {
-            for (PackagesUpdatedListener listener : mPackagesUpdatedListeners) {
-                listener.onPackagesUpdated(updatedPackages);
-            }
-        }
-    }
-
-    private void writeStatsLog(JobParameters params) {
-        @Status int status;
-        long durationMs;
-        long durationIncludingSleepMs;
-        synchronized (mLock) {
-            status = mLastExecutionStatus;
-            durationMs = mLastExecutionDurationMs;
-        }
-
-        mStatsLogger.write(status, params.getStopReason(), durationMs);
-    }
-
-    /** Injector pattern for testing purpose */
-    @VisibleForTesting
-    static final class Injector {
-        private final Context mContext;
-        private final DexManager mDexManager;
-        private final PackageManagerService mPackageManagerService;
-        private final File mDataDir = Environment.getDataDirectory();
-
-        Injector(Context context, DexManager dexManager, PackageManagerService pm) {
-            mContext = context;
-            mDexManager = dexManager;
-            mPackageManagerService = pm;
-        }
-
-        int getCallingUid() {
-            return Binder.getCallingUid();
-        }
-
-        Context getContext() {
-            return mContext;
-        }
-
-        PackageManagerService getPackageManagerService() {
-            return mPackageManagerService;
-        }
-
-        DexOptHelper getDexOptHelper() {
-            return new DexOptHelper(getPackageManagerService());
-        }
-
-        JobScheduler getJobScheduler() {
-            return mContext.getSystemService(JobScheduler.class);
-        }
-
-        DexManager getDexManager() {
-            return mDexManager;
-        }
-
-        PinnerService getPinnerService() {
-            return LocalServices.getService(PinnerService.class);
-        }
-
-        boolean isBackgroundDexOptDisabled() {
-            return SystemProperties.getBoolean(
-                    "pm.dexopt.disable_bg_dexopt" /* key */, false /* default */);
-        }
-
-        boolean isBatteryLevelLow() {
-            return LocalServices.getService(BatteryManagerInternal.class).getBatteryLevelLow();
-        }
-
-        long getDowngradeUnusedAppsThresholdInMillis() {
-            String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
-            String sysPropValue = SystemProperties.get(sysPropKey);
-            if (sysPropValue == null || sysPropValue.isEmpty()) {
-                Slog.w(TAG, "SysProp " + sysPropKey + " not set");
-                return Long.MAX_VALUE;
-            }
-            return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
-        }
-
-        boolean supportSecondaryDex() {
-            return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
-        }
-
-        long getDataDirUsableSpace() {
-            return mDataDir.getUsableSpace();
-        }
-
-        long getDataDirStorageLowBytes() {
-            return mContext.getSystemService(StorageManager.class).getStorageLowBytes(mDataDir);
-        }
-
-        int getCurrentThermalStatus() {
-            IThermalService thermalService = IThermalService.Stub.asInterface(
-                    ServiceManager.getService(Context.THERMAL_SERVICE));
-            try {
-                return thermalService.getCurrentThermalStatus();
-            } catch (RemoteException e) {
-                return STATUS_ABORT_THERMAL;
-            }
-        }
-
-        int getDexOptThermalCutoff() {
-            return SystemProperties.getInt(
-                    "dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT);
-        }
-
-        Thread createAndStartThread(String name, Runnable target) {
-            Thread thread = new Thread(target, name);
-            Slog.i(TAG, "Starting thread:" + name);
-            thread.start();
-            return thread;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b5476fd..9480c8e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -137,7 +137,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -419,7 +418,6 @@
     private final PackageDexOptimizer mPackageDexOptimizer;
     private final DexManager mDexManager;
     private final CompilerStats mCompilerStats;
-    private final BackgroundDexOptService mBackgroundDexOptService;
     private final PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
     private final CrossProfileIntentResolverEngine mCrossProfileIntentResolverEngine;
 
@@ -472,7 +470,6 @@
         mPackageDexOptimizer = args.service.mPackageDexOptimizer;
         mDexManager = args.service.getDexManager();
         mCompilerStats = args.service.mCompilerStats;
-        mBackgroundDexOptService = args.service.mBackgroundDexOptService;
         mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
         mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine(
                 mUserManager, mDomainVerificationManager, mDefaultAppProvider, mContext);
@@ -3093,40 +3090,7 @@
                 }
                 ipw.println("Dexopt state:");
                 ipw.increaseIndent();
-                if (DexOptHelper.useArtService()) {
-                    DexOptHelper.dumpDexoptState(ipw, packageName);
-                } else {
-                    Collection<? extends PackageStateInternal> pkgSettings;
-                    if (setting != null) {
-                        pkgSettings = Collections.singletonList(setting);
-                    } else {
-                        pkgSettings = mSettings.getPackages().values();
-                    }
-
-                    for (PackageStateInternal pkgSetting : pkgSettings) {
-                        final AndroidPackage pkg = pkgSetting.getPkg();
-                        if (pkg == null || pkg.isApex()) {
-                            // Skip APEX which is not dex-optimized
-                            continue;
-                        }
-                        final String pkgName = pkg.getPackageName();
-                        ipw.println("[" + pkgName + "]");
-                        ipw.increaseIndent();
-
-                        // TODO(b/251903639): Call into ART Service.
-                        try {
-                            mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
-                                    mDexManager.getPackageUseInfoOrDefault(pkgName));
-                        } catch (LegacyDexoptDisabledException e) {
-                            throw new RuntimeException(e);
-                        }
-                        ipw.decreaseIndent();
-                    }
-                    ipw.println("BgDexopt state:");
-                    ipw.increaseIndent();
-                    mBackgroundDexOptService.dump(ipw);
-                    ipw.decreaseIndent();
-                }
+                DexOptHelper.dumpDexoptState(ipw, packageName);
                 ipw.decreaseIndent();
                 break;
             }
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index ecfc768..51793f6 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -23,7 +23,6 @@
 
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
 import static com.android.server.pm.ApexManager.ActiveApexInfo;
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_MAINLINE_UPDATE;
@@ -32,10 +31,7 @@
 import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
-import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
 import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_APEX_PKG;
 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
 import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
@@ -45,7 +41,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
-import android.app.role.RoleManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -56,8 +51,6 @@
 import android.content.pm.IStagedApexObserver;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.pm.SharedLibraryInfo;
-import android.content.pm.dex.ArtManager;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -83,8 +76,6 @@
 import com.android.server.art.model.ArtFlags;
 import com.android.server.art.model.DexoptParams;
 import com.android.server.art.model.DexoptResult;
-import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageDexOptimizer.DexOptResult;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
@@ -131,228 +122,6 @@
     }
 
     /**
-     * Performs dexopt on the set of packages in {@code packages} and returns an int array
-     * containing statistics about the invocation. The array consists of three elements,
-     * which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
-     * and {@code numberOfPackagesFailed}.
-     */
-    public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates,
-            final int compilationReason, boolean bootComplete)
-            throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-        int numberOfPackagesVisited = 0;
-        int numberOfPackagesOptimized = 0;
-        int numberOfPackagesSkipped = 0;
-        int numberOfPackagesFailed = 0;
-        final int numberOfPackagesToDexopt = packageStates.size();
-
-        for (var packageState : packageStates) {
-            var pkg = packageState.getAndroidPackage();
-            numberOfPackagesVisited++;
-
-            boolean useProfileForDexopt = false;
-
-            if ((mPm.isFirstBoot() || mPm.isDeviceUpgrading()) && packageState.isSystem()) {
-                // Copy over initial preopt profiles since we won't get any JIT samples for methods
-                // that are already compiled.
-                File profileFile = new File(getPrebuildProfilePath(pkg));
-                // Copy profile if it exists.
-                if (profileFile.exists()) {
-                    try {
-                        // We could also do this lazily before calling dexopt in
-                        // PackageDexOptimizer to prevent this happening on first boot. The issue
-                        // is that we don't have a good way to say "do this only once".
-                        if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
-                                pkg.getUid(), pkg.getPackageName(),
-                                ArtManager.getProfileName(null))) {
-                            Log.e(TAG, "Installer failed to copy system profile!");
-                        } else {
-                            // Disabled as this causes speed-profile compilation during first boot
-                            // even if things are already compiled.
-                            // useProfileForDexopt = true;
-                        }
-                    } catch (InstallerException | RuntimeException e) {
-                        Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
-                                e);
-                    }
-                } else {
-                    PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(
-                            pkg.getPackageName());
-                    // Handle compressed APKs in this path. Only do this for stubs with profiles to
-                    // minimize the number off apps being speed-profile compiled during first boot.
-                    // The other paths will not change the filter.
-                    if (disabledPs != null && disabledPs.getPkg().isStub()) {
-                        // The package is the stub one, remove the stub suffix to get the normal
-                        // package and APK names.
-                        String systemProfilePath = getPrebuildProfilePath(disabledPs.getPkg())
-                                .replace(STUB_SUFFIX, "");
-                        profileFile = new File(systemProfilePath);
-                        // If we have a profile for a compressed APK, copy it to the reference
-                        // location.
-                        // Note that copying the profile here will cause it to override the
-                        // reference profile every OTA even though the existing reference profile
-                        // may have more data. We can't copy during decompression since the
-                        // directories are not set up at that point.
-                        if (profileFile.exists()) {
-                            try {
-                                // We could also do this lazily before calling dexopt in
-                                // PackageDexOptimizer to prevent this happening on first boot. The
-                                // issue is that we don't have a good way to say "do this only
-                                // once".
-                                if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
-                                        pkg.getUid(), pkg.getPackageName(),
-                                        ArtManager.getProfileName(null))) {
-                                    Log.e(TAG, "Failed to copy system profile for stub package!");
-                                } else {
-                                    useProfileForDexopt = true;
-                                }
-                            } catch (InstallerException | RuntimeException e) {
-                                Log.e(TAG, "Failed to copy profile "
-                                        + profileFile.getAbsolutePath() + " ", e);
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (!mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) {
-                if (DEBUG_DEXOPT) {
-                    Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
-                }
-                numberOfPackagesSkipped++;
-                continue;
-            }
-
-            if (DEBUG_DEXOPT) {
-                Log.i(TAG, "Updating app " + numberOfPackagesVisited + " of "
-                        + numberOfPackagesToDexopt + ": " + pkg.getPackageName());
-            }
-
-            int pkgCompilationReason = compilationReason;
-            if (useProfileForDexopt) {
-                // Use background dexopt mode to try and use the profile. Note that this does not
-                // guarantee usage of the profile.
-                pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
-            }
-
-            int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
-
-            String filter = getCompilerFilterForReason(pkgCompilationReason);
-            if (isProfileGuidedCompilerFilter(filter)) {
-                // DEXOPT_CHECK_FOR_PROFILES_UPDATES used to be false to avoid merging profiles
-                // during boot which might interfere with background compilation (b/28612421).
-                // However those problems were related to the verify-profile compiler filter which
-                // doesn't exist any more, so enable it again.
-                dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
-            }
-
-            if (compilationReason == REASON_FIRST_BOOT) {
-                // TODO: This doesn't cover the upgrade case, we should check for this too.
-                dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
-            }
-            int primaryDexOptStatus = performDexOptTraced(
-                    new DexoptOptions(pkg.getPackageName(), pkgCompilationReason, filter,
-                            /*splitName*/ null, dexoptFlags));
-
-            switch (primaryDexOptStatus) {
-                case PackageDexOptimizer.DEX_OPT_PERFORMED:
-                    numberOfPackagesOptimized++;
-                    break;
-                case PackageDexOptimizer.DEX_OPT_SKIPPED:
-                    numberOfPackagesSkipped++;
-                    break;
-                case PackageDexOptimizer.DEX_OPT_CANCELLED:
-                    // ignore this case
-                    break;
-                case PackageDexOptimizer.DEX_OPT_FAILED:
-                    numberOfPackagesFailed++;
-                    break;
-                default:
-                    Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStatus);
-                    break;
-            }
-        }
-
-        return new int[]{numberOfPackagesOptimized, numberOfPackagesSkipped,
-                numberOfPackagesFailed};
-    }
-
-    /**
-     * Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
-     * compiles it if needed.
-     */
-    private void checkAndDexOptSystemUi(int reason) throws LegacyDexoptDisabledException {
-        Computer snapshot = mPm.snapshotComputer();
-        String sysUiPackageName =
-                mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
-        AndroidPackage pkg = snapshot.getPackage(sysUiPackageName);
-        if (pkg == null) {
-            Log.w(TAG, "System UI package " + sysUiPackageName + " is not found for dexopting");
-            return;
-        }
-
-        String defaultCompilerFilter = getCompilerFilterForReason(reason);
-        String targetCompilerFilter =
-                SystemProperties.get("dalvik.vm.systemuicompilerfilter", defaultCompilerFilter);
-        String compilerFilter;
-
-        if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
-            compilerFilter = "verify";
-            File profileFile = new File(getPrebuildProfilePath(pkg));
-
-            // Copy the profile to the reference profile path if it exists. Installd can only use a
-            // profile at the reference profile path for dexopt.
-            if (profileFile.exists()) {
-                try {
-                    synchronized (mPm.mInstallLock) {
-                        if (mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
-                                    pkg.getUid(), pkg.getPackageName(),
-                                    ArtManager.getProfileName(null))) {
-                            compilerFilter = targetCompilerFilter;
-                        } else {
-                            Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath());
-                        }
-                    }
-                } catch (InstallerException | RuntimeException e) {
-                    Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath(), e);
-                }
-            }
-        } else {
-            compilerFilter = targetCompilerFilter;
-        }
-
-        performDexoptPackage(sysUiPackageName, reason, compilerFilter);
-    }
-
-    private void dexoptLauncher(int reason) throws LegacyDexoptDisabledException {
-        Computer snapshot = mPm.snapshotComputer();
-        RoleManager roleManager = mPm.mContext.getSystemService(RoleManager.class);
-        for (var packageName : roleManager.getRoleHolders(RoleManager.ROLE_HOME)) {
-            AndroidPackage pkg = snapshot.getPackage(packageName);
-            if (pkg == null) {
-                Log.w(TAG, "Launcher package " + packageName + " is not found for dexopting");
-            } else {
-                performDexoptPackage(packageName, reason, "speed-profile");
-            }
-        }
-    }
-
-    private void performDexoptPackage(@NonNull String packageName, int reason,
-            @NonNull String compilerFilter) throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-
-        // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
-        // unconditionally enabled for profile guided filters when ART Service is called instead of
-        // the legacy PackageDexOptimizer implementation.
-        int dexoptFlags = isProfileGuidedCompilerFilter(compilerFilter)
-                ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
-                : 0;
-
-        performDexOptTraced(new DexoptOptions(
-                packageName, reason, compilerFilter, null /* splitName */, dexoptFlags));
-    }
-
-    /**
      * Called during startup to do any boot time dexopting. This can occasionally be time consuming
      * (30+ seconds) and the function will block until it is complete.
      */
@@ -377,35 +146,9 @@
 
         final long startTime = System.nanoTime();
 
-        if (useArtService()) {
-            mBootDexoptStartTime = startTime;
-            getArtManagerLocal().onBoot(DexoptOptions.convertToArtServiceDexoptReason(reason),
-                    null /* progressCallbackExecutor */, null /* progressCallback */);
-        } else {
-            try {
-                // System UI and the launcher are important to user experience, so we check them
-                // after a mainline update or OTA. They may need to be re-compiled in these cases.
-                checkAndDexOptSystemUi(reason);
-                dexoptLauncher(reason);
-
-                if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
-                    return;
-                }
-
-                final Computer snapshot = mPm.snapshotComputer();
-
-                // TODO(b/251903639): Align this with how ART Service selects packages for boot
-                // compilation.
-                List<PackageStateInternal> pkgSettings =
-                        getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
-
-                final int[] stats =
-                        performDexOptUpgrade(pkgSettings, reason, false /* bootComplete */);
-                reportBootDexopt(startTime, stats[0], stats[1], stats[2]);
-            } catch (LegacyDexoptDisabledException e) {
-                throw new RuntimeException(e);
-            }
-        }
+        mBootDexoptStartTime = startTime;
+        getArtManagerLocal().onBoot(DexoptOptions.convertToArtServiceDexoptReason(reason),
+                null /* progressCallbackExecutor */, null /* progressCallback */);
     }
 
     private void reportBootDexopt(long startTime, int numDexopted, int numSkipped, int numFailed) {
@@ -450,15 +193,7 @@
 
         @DexOptResult int dexoptStatus;
         if (options.isDexoptOnlySecondaryDex()) {
-            if (useArtService()) {
-                dexoptStatus = performDexOptWithArtService(options, 0 /* extraFlags */);
-            } else {
-                try {
-                    return mPm.getDexManager().dexoptSecondaryDex(options);
-                } catch (LegacyDexoptDisabledException e) {
-                    throw new RuntimeException(e);
-                }
-            }
+            dexoptStatus = performDexOptWithArtService(options, 0 /* extraFlags */);
         } else {
             dexoptStatus = performDexOptWithStatus(options);
         }
@@ -491,39 +226,11 @@
     // if the package can now be considered up to date for the given filter.
     @DexOptResult
     private int performDexOptInternal(DexoptOptions options) {
-        if (useArtService()) {
-            return performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
-        }
-
-        AndroidPackage p;
-        PackageSetting pkgSetting;
-        synchronized (mPm.mLock) {
-            p = mPm.mPackages.get(options.getPackageName());
-            pkgSetting = mPm.mSettings.getPackageLPr(options.getPackageName());
-            if (p == null || pkgSetting == null) {
-                // Package could not be found. Report failure.
-                return PackageDexOptimizer.DEX_OPT_FAILED;
-            }
-            if (p.isApex()) {
-                // APEX needs no dexopt
-                return PackageDexOptimizer.DEX_OPT_SKIPPED;
-            }
-            mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
-            mPm.mCompilerStats.maybeWriteAsync();
-        }
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
-        } catch (LegacyDexoptDisabledException e) {
-            throw new RuntimeException(e);
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
-        }
+        return performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
     }
 
     /**
-     * Performs dexopt on the given package using ART Service. May only be called when ART Service
-     * is enabled, i.e. when {@link useArtService} returns true.
+     * Performs dexopt on the given package using ART Service.
      */
     @DexOptResult
     private int performDexOptWithArtService(DexoptOptions options,
@@ -545,91 +252,6 @@
         }
     }
 
-    @DexOptResult
-    private int performDexOptInternalWithDependenciesLI(
-            AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options)
-            throws LegacyDexoptDisabledException {
-        if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
-            // This needs to be done in odrefresh in early boot, for security reasons.
-            throw new IllegalArgumentException("Cannot dexopt the system server");
-        }
-
-        // 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 = options.isForce()
-                ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPm.mPackageDexOptimizer)
-                : mPm.mPackageDexOptimizer;
-
-        // Dexopt all dependencies first. Note: we ignore the return value and march on
-        // on errors.
-        // Note that we are going to call performDexOpt on those libraries as many times as
-        // they are referenced in packages. When we do a batch of performDexOpt (for example
-        // at boot, or background job), the passed 'targetCompilerFilter' stays the same,
-        // and the first package that uses the library will dexopt it. The
-        // others will see that the compiled code for the library is up to date.
-        Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
-        final String[] instructionSets = getAppDexInstructionSets(
-                pkgSetting.getPrimaryCpuAbi(),
-                pkgSetting.getSecondaryCpuAbi());
-        if (!deps.isEmpty()) {
-            DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(),
-                    options.getCompilationReason(), options.getCompilerFilter(),
-                    options.getSplitName(),
-                    options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY);
-            for (SharedLibraryInfo info : deps) {
-                Computer snapshot = mPm.snapshotComputer();
-                AndroidPackage depPackage = snapshot.getPackage(info.getPackageName());
-                PackageStateInternal depPackageStateInternal =
-                        snapshot.getPackageStateInternal(info.getPackageName());
-                if (depPackage != null && depPackageStateInternal != null) {
-                    // TODO: Analyze and investigate if we (should) profile libraries.
-                    pdo.performDexOpt(depPackage, depPackageStateInternal, instructionSets,
-                            mPm.getOrCreateCompilerPackageStats(depPackage),
-                            mPm.getDexManager().getPackageUseInfoOrDefault(
-                                    depPackage.getPackageName()), libraryOptions);
-                } else {
-                    // TODO(ngeoffray): Support dexopting system shared libraries.
-                }
-            }
-        }
-
-        return pdo.performDexOpt(p, pkgSetting, instructionSets,
-                mPm.getOrCreateCompilerPackageStats(p),
-                mPm.getDexManager().getPackageUseInfoOrDefault(p.getPackageName()), options);
-    }
-
-    /** @deprecated For legacy shell command only. */
-    @Deprecated
-    public void forceDexOpt(@NonNull Computer snapshot, String packageName)
-            throws LegacyDexoptDisabledException {
-        PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt");
-
-        final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
-        final AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
-        if (packageState == null || pkg == null) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-        if (pkg.isApex()) {
-            throw new IllegalArgumentException("Can't dexopt APEX package: " + packageName);
-        }
-
-        Trace.traceBegin(TRACE_TAG_DALVIK, "dexopt");
-
-        // Whoever is calling forceDexOpt wants a compiled package.
-        // Don't use profiles since that may cause compilation to be skipped.
-        DexoptOptions options = new DexoptOptions(packageName, REASON_CMDLINE,
-                getDefaultCompilerFilter(), null /* splitName */,
-                DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
-
-        @DexOptResult int res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
-
-        Trace.traceEnd(TRACE_TAG_DALVIK);
-        if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
-            throw new IllegalStateException("Failed to dexopt: " + res);
-        }
-    }
-
     public boolean performDexOptMode(@NonNull Computer snapshot, String packageName,
             String targetCompilerFilter, boolean force, boolean bootComplete, String splitName) {
         if (!PackageManagerServiceUtils.isSystemOrRootOrShell()
@@ -872,10 +494,6 @@
         }
     }
 
-    /*package*/ void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
-        mPm.mPackageDexOptimizer.controlDexOptBlocking(block);
-    }
-
     /**
      * Dumps the dexopt state for the given package, or all packages if it is null.
      */
@@ -935,19 +553,9 @@
     }
 
     /**
-     * Returns true if ART Service should be used for package optimization.
-     */
-    public static boolean useArtService() {
-        return SystemProperties.getBoolean("dalvik.vm.useartservice", false);
-    }
-
-    /**
      * Returns {@link DexUseManagerLocal} if ART Service should be used for package optimization.
      */
     public static @Nullable DexUseManagerLocal getDexUseManagerLocal() {
-        if (!useArtService()) {
-            return null;
-        }
         try {
             return LocalManagerRegistry.getManagerOrThrow(DexUseManagerLocal.class);
         } catch (ManagerNotFoundException e) {
@@ -1039,10 +647,6 @@
      */
     public static void initializeArtManagerLocal(
             @NonNull Context systemContext, @NonNull PackageManagerService pm) {
-        if (!useArtService()) {
-            return;
-        }
-
         ArtManagerLocal artManager = new ArtManagerLocal(systemContext);
         artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run,
                 pm.getDexOptHelper().new DexoptDoneHandler());
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index ae68018..c559892 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -46,10 +46,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
 import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.APP_METADATA_FILE_NAME;
-import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
-import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
-import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
@@ -173,7 +170,6 @@
 import com.android.server.SystemConfig;
 import com.android.server.art.model.DexoptResult;
 import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
@@ -272,8 +268,6 @@
         final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
         final PackageSetting originalPkgSetting = request.getScanRequestOriginalPackageSetting();
         final String realPkgName = request.getRealPackageName();
-        final List<String> changedAbiCodePath =
-                useArtService() ? null : request.getChangedAbiCodePath();
         final PackageSetting pkgSetting;
         if (request.getScanRequestPackageSetting() != null) {
             SharedUserSetting requestSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(
@@ -449,23 +443,6 @@
         }
         pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails);
 
-        // The conditional on useArtService() for changedAbiCodePath above means this is skipped
-        // when ART Service is in use, since it has its own dex file GC.
-        if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
-            for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
-                final String codePathString = changedAbiCodePath.get(i);
-                try {
-                    synchronized (mPm.mInstallLock) {
-                        mPm.mInstaller.rmdex(codePathString,
-                                getDexCodeInstructionSet(getPreferredInstructionSet()));
-                    }
-                } catch (LegacyDexoptDisabledException e) {
-                    throw new RuntimeException(e);
-                } catch (Installer.InstallerException ignored) {
-                }
-            }
-        }
-
         final int userId = request.getUserId();
         // Modify state for the given package setting
         commitPackageSettings(pkg, pkgSetting, oldPkgSetting, reconciledPkg);
@@ -2538,20 +2515,6 @@
                         pkg.getBaseApkPath(), pkg.getSplitCodePaths());
             }
 
-            // ART Service handles this on demand instead.
-            if (!useArtService() && pkg != null) {
-                // Prepare the application profiles for the new code paths.
-                // This needs to be done before invoking dexopt so that any install-time profile
-                // can be used for optimizations.
-                try {
-                    mArtManagerService.prepareAppProfiles(pkg,
-                            mPm.resolveUserIds(installRequest.getUserId()),
-                            /* updateReferenceProfileContent= */ true);
-                } catch (LegacyDexoptDisabledException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-
             // Construct the DexoptOptions early to see if we should skip running dexopt.
             //
             // Do not run PackageDexOptimizer through the local performDexOpt
@@ -2602,36 +2565,11 @@
 
                 realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
 
-                if (useArtService()) {
-                    DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
-                            installRequest, dexoptOptions);
-                    installRequest.onDexoptFinished(dexOptResult);
-                } else {
-                    try {
-                        mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
-                                null /* instructionSets */,
-                                mPm.getOrCreateCompilerPackageStats(pkg),
-                                mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
-                    } catch (LegacyDexoptDisabledException e) {
-                        throw new RuntimeException(e);
-                    }
-                }
+                DexoptResult dexOptResult =
+                        DexOptHelper.dexoptPackageUsingArtService(installRequest, dexoptOptions);
+                installRequest.onDexoptFinished(dexOptResult);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
-
-            if (!useArtService()) {
-                // Notify BackgroundDexOptService that the package has been changed.
-                // If this is an update of a package which used to fail to compile,
-                // BackgroundDexOptService will remove it from its denylist.
-                // ART Service currently doesn't support this and will retry packages in every
-                // background dexopt.
-                // TODO: Layering violation
-                try {
-                    BackgroundDexOptService.getService().notifyPackageChanged(packageName);
-                } catch (LegacyDexoptDisabledException e) {
-                    throw new RuntimeException(e);
-                }
-            }
         }
         PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
                 incrementalStorages);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 34903d1..8038c9a 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,8 +16,6 @@
 
 package com.android.server.pm;
 
-import static com.android.server.pm.DexOptHelper.useArtService;
-
 import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -97,15 +95,6 @@
      */
     public static final int PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES = 3;
 
-    /**
-     * The results of {@code getOdexVisibility}. See
-     * {@link #getOdexVisibility(String, String, String)} for details.
-     */
-    public static final int ODEX_NOT_FOUND = 0;
-    public static final int ODEX_IS_PUBLIC = 1;
-    public static final int ODEX_IS_PRIVATE = 2;
-
-
     public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE;
     public static final int FLAG_STORAGE_CE = IInstalld.FLAG_STORAGE_CE;
     public static final int FLAG_STORAGE_EXTERNAL = IInstalld.FLAG_STORAGE_EXTERNAL;
@@ -611,37 +600,7 @@
     }
 
     /**
-     * Runs dex optimization.
-     *
-     * @param apkPath Path of target APK
-     * @param uid UID of the package
-     * @param pkgName Name of the package
-     * @param instructionSet Target instruction set to run dex optimization.
-     * @param dexoptNeeded Necessary dex optimization for this request. Check
-     *        {@link dalvik.system.DexFile#NO_DEXOPT_NEEDED},
-     *        {@link dalvik.system.DexFile#DEX2OAT_FROM_SCRATCH},
-     *        {@link dalvik.system.DexFile#DEX2OAT_FOR_BOOT_IMAGE}, and
-     *        {@link dalvik.system.DexFile#DEX2OAT_FOR_FILTER}.
-     * @param outputPath Output path of generated dex optimization.
-     * @param dexFlags Check {@code DEXOPT_*} for allowed flags.
-     * @param compilerFilter Compiler filter like "verify", "speed-profile". Check
-     *                       {@code art/libartbase/base/compiler_filter.cc} for full list.
-     * @param volumeUuid UUID of the volume where the package data is stored. {@code null}
-     *                   represents internal storage.
-     * @param classLoaderContext This encodes the class loader chain (class loader type + class
-     *                           path) in a format compatible to dex2oat. Check
-     *                           {@code DexoptUtils.processContextForDexLoad} for further details.
-     * @param seInfo Selinux context to set for generated outputs.
-     * @param downgrade If set, allows downgrading {@code compilerFilter}. If downgrading is not
-     *                  allowed and requested {@code compilerFilter} is considered as downgrade,
-     *                  the request will be ignored.
-     * @param targetSdkVersion Target SDK version of the package.
-     * @param profileName Name of reference profile file.
-     * @param dexMetadataPath Specifies the location of dex metadata file.
-     * @param compilationReason Specifies the reason for the compilation like "install".
-     * @return {@code true} if {@code dexopt} is completed. {@code false} if it was cancelled.
-     *
-     * @throws InstallerException if {@code dexopt} fails.
+     * This function only remains to allow overriding in OtaDexoptService.
      */
     public boolean dexopt(String apkPath, int uid, String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter,
@@ -650,98 +609,7 @@
             @Nullable String profileName, @Nullable String dexMetadataPath,
             @Nullable String compilationReason)
             throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        assertValidInstructionSet(instructionSet);
-        BlockGuard.getVmPolicy().onPathAccess(apkPath);
-        BlockGuard.getVmPolicy().onPathAccess(outputPath);
-        BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
-        if (!checkBeforeRemote()) return false;
-        try {
-            return mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
-                    dexFlags, compilerFilter, volumeUuid, classLoaderContext, seInfo, downgrade,
-                    targetSdkVersion, profileName, dexMetadataPath, compilationReason);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    /**
-     * Enables or disables dex optimization blocking.
-     *
-     * <p> Enabling blocking will also involve cancelling pending dexopt call and killing child
-     * processes forked from installd to run dexopt. The pending dexopt call will return false
-     * when it is cancelled.
-     *
-     * @param block set to true to enable blocking / false to disable blocking.
-     */
-    public void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        try {
-            mInstalld.controlDexOptBlocking(block);
-        } catch (Exception e) {
-            Slog.w(TAG, "blockDexOpt failed", e);
-        }
-    }
-
-    /**
-     * Analyzes the ART profiles of the given package, possibly merging the information
-     * into the reference profile. Returns whether or not we should optimize the package
-     * based on how much information is in the profile.
-     *
-     * @return one of {@link #PROFILE_ANALYSIS_OPTIMIZE},
-     *         {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA},
-     *         {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES}
-     */
-    public int mergeProfiles(int uid, String packageName, String profileName)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
-        try {
-            return mInstalld.mergeProfiles(uid, packageName, profileName);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    /**
-     * Dumps profiles associated with a package in a human readable format.
-     */
-    public boolean dumpProfiles(int uid, String packageName, String profileName, String codePath,
-            boolean dumpClassesAndMethods)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return false;
-        BlockGuard.getVmPolicy().onPathAccess(codePath);
-        try {
-            return mInstalld.dumpProfiles(uid, packageName, profileName, codePath,
-                    dumpClassesAndMethods);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    public boolean copySystemProfile(String systemProfile, int uid, String packageName,
-            String profileName) throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return false;
-        try {
-            return mInstalld.copySystemProfile(systemProfile, uid, packageName, profileName);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    public void rmdex(String codePath, String instructionSet)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        assertValidInstructionSet(instructionSet);
-        if (!checkBeforeRemote()) return;
-        BlockGuard.getVmPolicy().onPathAccess(codePath);
-        try {
-            mInstalld.rmdex(codePath, instructionSet);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
+        throw new LegacyDexoptDisabledException();
     }
 
     /**
@@ -757,43 +625,6 @@
         }
     }
 
-    public void clearAppProfiles(String packageName, String profileName)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return;
-        try {
-            mInstalld.clearAppProfiles(packageName, profileName);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    public void destroyAppProfiles(String packageName)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return;
-        try {
-            mInstalld.destroyAppProfiles(packageName);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    /**
-     * Deletes the reference profile with the given name of the given package.
-     * @throws InstallerException if the deletion fails.
-     */
-    public void deleteReferenceProfile(String packageName, String profileName)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return;
-        try {
-            mInstalld.deleteReferenceProfile(packageName, profileName);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
     public void createUserData(String uuid, int userId, int userSerial, int flags)
             throws InstallerException {
         if (!checkBeforeRemote()) return;
@@ -889,40 +720,6 @@
         }
     }
 
-    /**
-     * Deletes the optimized artifacts generated by ART and returns the number
-     * of freed bytes.
-     */
-    public long deleteOdex(String packageName, String apkPath, String instructionSet,
-            String outputPath) throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return -1;
-        BlockGuard.getVmPolicy().onPathAccess(apkPath);
-        BlockGuard.getVmPolicy().onPathAccess(outputPath);
-        try {
-            return mInstalld.deleteOdex(packageName, apkPath, instructionSet, outputPath);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
-            String[] isas, @Nullable String volumeUuid, int flags)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        for (int i = 0; i < isas.length; i++) {
-            assertValidInstructionSet(isas[i]);
-        }
-        if (!checkBeforeRemote()) return false;
-        BlockGuard.getVmPolicy().onPathAccess(apkPath);
-        try {
-            return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas,
-                    volumeUuid, flags);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
     public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
             @Nullable String volumeUuid, int flags) throws InstallerException {
         if (!checkBeforeRemote()) return new byte[0];
@@ -934,28 +731,6 @@
         }
     }
 
-    public boolean createProfileSnapshot(int appId, String packageName, String profileName,
-            String classpath) throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return false;
-        try {
-            return mInstalld.createProfileSnapshot(appId, packageName, profileName, classpath);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    public void destroyProfileSnapshot(String packageName, String profileName)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return;
-        try {
-            mInstalld.destroyProfileSnapshot(packageName, profileName);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
     public void invalidateMounts() throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
@@ -999,30 +774,6 @@
     }
 
     /**
-     * Prepares the app profile for the package at the given path:
-     * <ul>
-     *   <li>Creates the current profile for the given user ID, unless the user ID is
-     *     {@code UserHandle.USER_NULL}.</li>
-     *   <li>Merges the profile from the dex metadata file (if present) into the reference
-     *     profile.</li>
-     * </ul>
-     */
-    public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
-            String profileName, String codePath, String dexMetadataPath)
-            throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return false;
-        BlockGuard.getVmPolicy().onPathAccess(codePath);
-        BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
-        try {
-            return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,
-                    dexMetadataPath);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    /**
      * Snapshots user data of the given package.
      *
      * @param pkg name of the package to snapshot user data for.
@@ -1152,34 +903,6 @@
     }
 
     /**
-     * Returns the visibility of the optimized artifacts.
-     *
-     * @param packageName name of the package.
-     * @param apkPath path to the APK.
-     * @param instructionSet instruction set of the optimized artifacts.
-     * @param outputPath path to the directory that contains the optimized artifacts (i.e., the
-     *   directory that {@link #dexopt} outputs to).
-     *
-     * @return {@link #ODEX_NOT_FOUND} if the optimized artifacts are not found, or
-     *   {@link #ODEX_IS_PUBLIC} if the optimized artifacts are accessible by all apps, or
-     *   {@link #ODEX_IS_PRIVATE} if the optimized artifacts are only accessible by this app.
-     *
-     * @throws InstallerException if failed to get the visibility of the optimized artifacts.
-     */
-    public int getOdexVisibility(String packageName, String apkPath, String instructionSet,
-            String outputPath) throws InstallerException, LegacyDexoptDisabledException {
-        checkLegacyDexoptDisabled();
-        if (!checkBeforeRemote()) return -1;
-        BlockGuard.getVmPolicy().onPathAccess(apkPath);
-        BlockGuard.getVmPolicy().onPathAccess(outputPath);
-        try {
-            return mInstalld.getOdexVisibility(packageName, apkPath, instructionSet, outputPath);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    /**
      * Returns an auth token for the provided writable FD.
      *
      * @param authFd a file descriptor to proof that the caller can write to the file.
@@ -1247,14 +970,4 @@
             super("Invalid call to legacy dexopt method while ART Service is in use.");
         }
     }
-
-    /**
-     * Throws LegacyDexoptDisabledException if ART Service should be used instead of the
-     * {@link android.os.IInstalld} method that follows this method call.
-     */
-    public static void checkLegacyDexoptDisabled() throws LegacyDexoptDisabledException {
-        if (useArtService()) {
-            throw new LegacyDexoptDisabledException();
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index c8bc56c..85aee86 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -11,7 +11,6 @@
 
 # dex
 per-file AbstractStatsBase.java = file:dex/OWNERS
-per-file BackgroundDexOptService.java = file:dex/OWNERS
 per-file CompilerStats.java = file:dex/OWNERS
 per-file DexOptHelper.java = file:dex/OWNERS
 per-file DynamicCodeLoggingService.java = file:dex/OWNERS
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index ea082cf..5b326fd 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -305,13 +304,10 @@
                     throws InstallerException {
                 final StringBuilder builder = new StringBuilder();
 
-                if (useArtService()) {
-                    if ((dexFlags & DEXOPT_SECONDARY_DEX) != 0) {
-                        // installd may change the reference profile in place for secondary dex
-                        // files, which isn't safe with the lock free approach in ART Service.
-                        throw new IllegalArgumentException(
-                                "Invalid OTA dexopt call for secondary dex");
-                    }
+                if ((dexFlags & DEXOPT_SECONDARY_DEX) != 0) {
+                    // installd may change the reference profile in place for secondary dex
+                    // files, which isn't safe with the lock free approach in ART Service.
+                    throw new IllegalArgumentException("Invalid OTA dexopt call for secondary dex");
                 }
 
                 // The current version. For v10, see b/115993344.
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 8a4080f..396fa22 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -18,7 +18,6 @@
 
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
 
-import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
 import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
 import static com.android.server.pm.Installer.DEXOPT_ENABLE_HIDDEN_API_CHECKS;
@@ -53,7 +52,6 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.DexMetadataHelper;
-import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -67,7 +65,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.apphibernation.AppHibernationManagerInternal;
 import com.android.server.pm.Installer.InstallerException;
@@ -92,7 +89,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.Random;
 
 /**
@@ -130,9 +126,8 @@
     private final Object mInstallLock;
 
     /**
-     * This should be accessed only through {@link #getInstallerLI()} with {@link #mInstallLock}
-     * or {@link #getInstallerWithoutLock()} without the lock. Check both methods for further
-     * details on when to use each of them.
+     * This should be accessed only through {@link #getInstallerLI()} with
+     * {@link #mInstallLock}.
      */
     private final Installer mInstaller;
 
@@ -248,15 +243,6 @@
     }
 
     /**
-     * Cancels currently running dex optimization.
-     */
-    void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
-        // This method should not hold mInstallLock as cancelling should be possible while
-        // the lock is held by other thread running performDexOpt.
-        getInstallerWithoutLock().controlDexOptBlocking(block);
-    }
-
-    /**
      * Performs dexopt on all code paths of the given package.
      * It assumes the install lock is held.
      */
@@ -334,7 +320,7 @@
             final boolean isUsedByOtherApps;
             if (options.isDexoptAsSharedLibrary()) {
                 isUsedByOtherApps = true;
-            } else if (useArtService()) {
+            } else {
                 // We get here when collecting dexopt commands in OTA preopt, even when ART Service
                 // is in use. packageUseInfo isn't useful in that case since the legacy dex use
                 // database hasn't been updated. So we'd have to query ART Service instead, but it
@@ -342,8 +328,6 @@
                 // That means such apps will get preopted wrong, and we'll leave it to a later
                 // background dexopt after reboot instead.
                 isUsedByOtherApps = false;
-            } else {
-                isUsedByOtherApps = packageUseInfo.isUsedByOtherApps(path);
             }
 
             String compilerFilter = getRealCompilerFilter(pkg, options.getCompilerFilter());
@@ -439,12 +423,10 @@
                     }
                 }
             } finally {
+                // ART Service is always enabled, so we should only arrive here
+                // during OTA preopt, and there should be no cloud profile.
                 if (cloudProfileName != null) {
-                    try {
-                        mInstaller.deleteReferenceProfile(pkg.getPackageName(), cloudProfileName);
-                    } catch (InstallerException e) {
-                        Slog.w(TAG, "Failed to cleanup cloud profile", e);
-                    }
+                    throw new LegacyDexoptDisabledException();
                 }
             }
         }
@@ -457,30 +439,15 @@
      *
      * @return true on success, or false otherwise.
      */
-    @GuardedBy("mInstallLock")
     private boolean prepareCloudProfile(AndroidPackage pkg, String profileName, String path,
             @Nullable String dexMetadataPath) throws LegacyDexoptDisabledException {
         if (dexMetadataPath != null) {
-            if (mInstaller.isIsolated()) {
-                // If the installer is isolated, the two calls to it below will return immediately,
-                // so this only short-circuits that a bit. We need to do it to avoid the
-                // LegacyDexoptDisabledException getting thrown first, when we get here during OTA
-                // preopt and ART Service is enabled.
-                return true;
+            // ART Service is always enabled, so we should only arrive here
+            // during OTA preopt, i.e. when the installer is isolated.
+            if (!mInstaller.isIsolated()) {
+                throw new LegacyDexoptDisabledException();
             }
-
-            try {
-                // Make sure we don't keep any existing contents.
-                mInstaller.deleteReferenceProfile(pkg.getPackageName(), profileName);
-
-                final int appId = UserHandle.getAppId(pkg.getUid());
-                mInstaller.prepareAppProfile(pkg.getPackageName(), UserHandle.USER_NULL, appId,
-                        profileName, path, dexMetadataPath);
-                return true;
-            } catch (InstallerException e) {
-                Slog.w(TAG, "Failed to prepare cloud profile", e);
-                return false;
-            }
+            return true;
         } else {
             return false;
         }
@@ -554,37 +521,6 @@
         return getReasonName(compilationReason) + annotation;
     }
 
-    /**
-     * Performs dexopt on the secondary dex {@code path} belonging to the app {@code info}.
-     *
-     * @return
-     *      DEX_OPT_FAILED if there was any exception during dexopt
-     *      DEX_OPT_PERFORMED if dexopt was performed successfully on the given path.
-     * NOTE that DEX_OPT_PERFORMED for secondary dex files includes the case when the dex file
-     * didn't need an update. That's because at the moment we don't get more than success/failure
-     * from installd.
-     *
-     * TODO(calin): Consider adding return codes to installd dexopt invocation (rather than
-     * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
-     * that seems wasteful.
-     */
-    @DexOptResult
-    public int dexOptSecondaryDexPath(ApplicationInfo info, String path,
-            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options)
-            throws LegacyDexoptDisabledException {
-        if (info.uid == -1) {
-            throw new IllegalArgumentException("Dexopt for path " + path + " has invalid uid.");
-        }
-        synchronized (mInstallLock) {
-            final long acquireTime = acquireWakeLockLI(info.uid);
-            try {
-                return dexOptSecondaryDexPathLI(info, path, dexUseInfo, options);
-            } finally {
-                releaseWakeLockLI(acquireTime);
-            }
-        }
-    }
-
     @GuardedBy("mInstallLock")
     private long acquireWakeLockLI(final int uid) {
         // During boot the system doesn't need to instantiate and obtain a wake lock.
@@ -618,69 +554,6 @@
         }
     }
 
-    @GuardedBy("mInstallLock")
-    @DexOptResult
-    private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
-            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options)
-            throws LegacyDexoptDisabledException {
-        String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(),
-                dexUseInfo.isUsedByOtherApps());
-        // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
-        // Secondary dex files are currently not compiled at boot.
-        int dexoptFlags = getDexFlags(info, compilerFilter, options) | DEXOPT_SECONDARY_DEX;
-        // Check the app storage and add the appropriate flags.
-        if (info.deviceProtectedDataDir != null &&
-                FileUtils.contains(info.deviceProtectedDataDir, path)) {
-            dexoptFlags |= DEXOPT_STORAGE_DE;
-        } else if (info.credentialProtectedDataDir != null &&
-                FileUtils.contains(info.credentialProtectedDataDir, path)) {
-            dexoptFlags |= DEXOPT_STORAGE_CE;
-        } else {
-            Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
-            return DEX_OPT_FAILED;
-        }
-        String classLoaderContext = null;
-        if (dexUseInfo.isUnsupportedClassLoaderContext()
-                || dexUseInfo.isVariableClassLoaderContext()) {
-            // If we have an unknown (not yet set), or a variable class loader chain. Just verify
-            // the dex file.
-            compilerFilter = "verify";
-        } else {
-            classLoaderContext = dexUseInfo.getClassLoaderContext();
-        }
-
-        int reason = options.getCompilationReason();
-        Log.d(TAG, "Running dexopt on: " + path
-                + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas()
-                + " reason=" + getReasonName(reason)
-                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
-                + " target-filter=" + compilerFilter
-                + " class-loader-context=" + classLoaderContext);
-
-        try {
-            for (String isa : dexUseInfo.getLoaderIsas()) {
-                // Reuse the same dexopt path as for the primary apks. We don't need all the
-                // arguments as some (dexopNeeded and oatDir) will be computed by installd because
-                // system server cannot read untrusted app content.
-                // TODO(calin): maybe add a separate call.
-                boolean completed = getInstallerLI().dexopt(path, info.uid, info.packageName,
-                        isa, /* dexoptNeeded= */ 0,
-                        /* outputPath= */ null, dexoptFlags,
-                        compilerFilter, info.volumeUuid, classLoaderContext, info.seInfo,
-                        options.isDowngrade(), info.targetSdkVersion, /* profileName= */ null,
-                        /* dexMetadataPath= */ null, getReasonName(reason));
-                if (!completed) {
-                    return DEX_OPT_CANCELLED;
-                }
-            }
-
-            return DEX_OPT_PERFORMED;
-        } catch (InstallerException e) {
-            Slog.w(TAG, "Failed to dexopt", e);
-            return DEX_OPT_FAILED;
-        }
-    }
-
     /**
      * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
      * optimize or not (and in what way).
@@ -697,59 +570,6 @@
     }
 
     /**
-     * Dumps the dexopt state of the given package {@code pkg} to the given {@code PrintWriter}.
-     */
-    void dumpDexoptState(IndentingPrintWriter pw, AndroidPackage pkg,
-            PackageStateInternal pkgSetting, PackageDexUsage.PackageUseInfo useInfo)
-            throws LegacyDexoptDisabledException {
-        final String[] instructionSets = getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(),
-                pkgSetting.getSecondaryCpuAbi());
-        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
-
-        final List<String> paths = AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
-
-        for (String path : paths) {
-            pw.println("path: " + path);
-            pw.increaseIndent();
-
-            for (String isa : dexCodeInstructionSets) {
-                try {
-                    DexFile.OptimizationInfo info = DexFile.getDexFileOptimizationInfo(path, isa);
-                    pw.println(isa + ": [status=" + info.getStatus()
-                            +"] [reason=" + info.getReason() + "]");
-                } catch (IOException ioe) {
-                    pw.println(isa + ": [Exception]: " + ioe.getMessage());
-                }
-            }
-
-            if (useInfo.isUsedByOtherApps(path)) {
-                pw.println("used by other apps: " + useInfo.getLoadingPackages(path));
-            }
-
-            Map<String, PackageDexUsage.DexUseInfo> dexUseInfoMap = useInfo.getDexUseInfoMap();
-
-            if (!dexUseInfoMap.isEmpty()) {
-                pw.println("known secondary dex files:");
-                pw.increaseIndent();
-                for (Map.Entry<String, PackageDexUsage.DexUseInfo> e : dexUseInfoMap.entrySet()) {
-                    String dex = e.getKey();
-                    PackageDexUsage.DexUseInfo dexUseInfo = e.getValue();
-                    pw.println(dex);
-                    pw.increaseIndent();
-                    // TODO(calin): get the status of the oat file (needs installd call)
-                    pw.println("class loader context: " + dexUseInfo.getClassLoaderContext());
-                    if (dexUseInfo.isUsedByOtherApps()) {
-                        pw.println("used by other apps: " + dexUseInfo.getLoadingPackages());
-                    }
-                    pw.decreaseIndent();
-                }
-                pw.decreaseIndent();
-            }
-            pw.decreaseIndent();
-        }
-    }
-
-    /**
      * Returns the compiler filter that should be used to optimize the secondary dex.
      * The target filter will be updated if the package code is used by other apps
      * or if it has the safe mode flag set.
@@ -898,14 +718,13 @@
      * Assesses if there's a need to perform dexopt on {@code path} for the given
      * configuration (isa, compiler filter, profile).
      */
-    @GuardedBy("mInstallLock")
     private int getDexoptNeeded(String packageName, String path, String isa, String compilerFilter,
             String classLoaderContext, int profileAnalysisResult, boolean downgrade,
             int dexoptFlags, String oatDir) throws LegacyDexoptDisabledException {
         // Allow calls from OtaDexoptService even when ART Service is in use. The installer is
         // isolated in that case so later calls to it won't call into installd anyway.
         if (!mInstaller.isIsolated()) {
-            Installer.checkLegacyDexoptDisabled();
+            throw new LegacyDexoptDisabledException();
         }
 
         final boolean shouldBePublic = (dexoptFlags & DEXOPT_PUBLIC) != 0;
@@ -953,16 +772,9 @@
     }
 
     /** Returns true if the current artifacts of the app are private to the app itself. */
-    @GuardedBy("mInstallLock")
     private boolean isOdexPrivate(String packageName, String path, String isa, String oatDir)
             throws LegacyDexoptDisabledException {
-        try {
-            return mInstaller.getOdexVisibility(packageName, path, isa, oatDir)
-                    == Installer.ODEX_IS_PRIVATE;
-        } catch (InstallerException e) {
-            Slog.w(TAG, "Failed to get odex visibility for " + path, e);
-            return false;
-        }
+        throw new LegacyDexoptDisabledException();
     }
 
     /**
@@ -976,22 +788,7 @@
      */
     private int analyseProfiles(AndroidPackage pkg, int uid, String profileName,
             String compilerFilter) throws LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-
-        // Check if we are allowed to merge and if the compiler filter is profile guided.
-        if (!isProfileGuidedCompilerFilter(compilerFilter)) {
-            return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
-        }
-        // Merge profiles. It returns whether or not there was an updated in the profile info.
-        try {
-            synchronized (mInstallLock) {
-                return getInstallerLI().mergeProfiles(uid, pkg.getPackageName(), profileName);
-            }
-        } catch (InstallerException e) {
-            Slog.w(TAG, "Failed to merge profiles", e);
-            // We don't need to optimize if we failed to merge.
-            return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
-        }
+        throw new LegacyDexoptDisabledException();
     }
 
     /**
@@ -1101,7 +898,7 @@
 
     /**
      * Returns {@link #mInstaller} with {@link #mInstallLock}. This should be used for all
-     * {@link #mInstaller} access unless {@link #getInstallerWithoutLock()} is allowed.
+     * {@link #mInstaller} access.
      */
     @GuardedBy("mInstallLock")
     private Installer getInstallerLI() {
@@ -1109,14 +906,6 @@
     }
 
     /**
-     * Returns {@link #mInstaller} without lock. This should be used only inside
-     * {@link #controlDexOptBlocking(boolean)}.
-     */
-    private Installer getInstallerWithoutLock() {
-        return mInstaller;
-    }
-
-    /**
      * Injector for {@link PackageDexOptimizer} dependencies
      */
     interface Injector {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 35cb5b0..d215822 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -41,9 +41,6 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
-import static com.android.server.pm.DexOptHelper.useArtService;
-import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
-import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.isInstalledByAdb;
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
@@ -216,10 +213,8 @@
 import com.android.server.compat.CompatChange;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.ArtManagerService;
-import com.android.server.pm.dex.ArtUtils;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
 import com.android.server.pm.local.PackageManagerLocalImpl;
@@ -820,8 +815,6 @@
 
     // TODO(b/260124949): Remove these.
     final PackageDexOptimizer mPackageDexOptimizer;
-    @Nullable
-    final BackgroundDexOptService mBackgroundDexOptService; // null when ART Service is in use.
     // DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
     // is used by other apps).
     private final DexManager mDexManager;
@@ -1763,16 +1756,6 @@
                 new DefaultSystemWrapper(),
                 LocalServices::getService,
                 context::getSystemService,
-                (i, pm) -> {
-                    if (useArtService()) {
-                        return null;
-                    }
-                    try {
-                        return new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm);
-                    } catch (LegacyDexoptDisabledException e) {
-                        throw new RuntimeException(e);
-                    }
-                },
                 (i, pm) -> IBackupManager.Stub.asInterface(ServiceManager.getService(
                         Context.BACKUP_SERVICE)),
                 (i, pm) -> new SharedLibrariesImpl(pm, i),
@@ -1916,7 +1899,6 @@
         mApexManager = testParams.apexManager;
         mArtManagerService = testParams.artManagerService;
         mAvailableFeatures = testParams.availableFeatures;
-        mBackgroundDexOptService = testParams.backgroundDexOptService;
         mDefParseFlags = testParams.defParseFlags;
         mDefaultAppProvider = testParams.defaultAppProvider;
         mLegacyPermissionManager = testParams.legacyPermissionManagerInternal;
@@ -2113,7 +2095,6 @@
         mPackageDexOptimizer = injector.getPackageDexOptimizer();
         mDexManager = injector.getDexManager();
         mDynamicCodeLogger = injector.getDynamicCodeLogger();
-        mBackgroundDexOptService = injector.getBackgroundDexOptService();
         mArtManagerService = injector.getArtManagerService();
         mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
         mSharedLibraries = mInjector.getSharedLibrariesImpl();
@@ -2369,19 +2350,6 @@
                                 null /*scannedPackage*/,
                                 mInjector.getAbiHelper().getAdjustedAbiForSharedUser(
                                         setting.getPackageStates(), null /*scannedPackage*/));
-                if (!useArtService() && // Skip for ART Service since it has its own dex file GC.
-                        changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
-                    for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
-                        final String codePathString = changedAbiCodePath.get(i);
-                        try {
-                            mInstaller.rmdex(codePathString,
-                                    getDexCodeInstructionSet(getPreferredInstructionSet()));
-                        } catch (LegacyDexoptDisabledException e) {
-                            throw new RuntimeException(e);
-                        } catch (InstallerException ignored) {
-                        }
-                    }
-                }
                 // Adjust seInfo to ensure apps which share a sharedUserId are placed in the same
                 // SELinux domain.
                 setting.fixSeInfoLocked();
@@ -4309,16 +4277,6 @@
                     }
                 });
 
-        if (!useArtService()) {
-            // The background dexopt job is scheduled in DexOptHelper.initializeArtManagerLocal when
-            // ART Service is in use.
-            try {
-                mBackgroundDexOptService.systemReady();
-            } catch (LegacyDexoptDisabledException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
         // Prune unused static shared libraries which have been cached a period of time
         schedulePruneUnusedStaticSharedLibraries(false /* delay */);
 
@@ -6903,46 +6861,6 @@
             }
         }
 
-        /** @deprecated For legacy shell command only. */
-        @Override
-        @Deprecated
-        public void legacyDumpProfiles(String packageName, boolean dumpClassesAndMethods)
-                throws LegacyDexoptDisabledException {
-            final Computer snapshot = snapshotComputer();
-            AndroidPackage pkg = snapshot.getPackage(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-
-            synchronized (mInstallLock) {
-                Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "dump profiles");
-                mArtManagerService.dumpProfiles(pkg, dumpClassesAndMethods);
-                Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-            }
-        }
-
-        /** @deprecated For legacy shell command only. */
-        @Override
-        @Deprecated
-        public void legacyForceDexOpt(String packageName) throws LegacyDexoptDisabledException {
-            mDexOptHelper.forceDexOpt(snapshotComputer(), packageName);
-        }
-
-        /** @deprecated For legacy shell command only. */
-        @Override
-        @Deprecated
-        public void legacyReconcileSecondaryDexFiles(String packageName)
-                throws LegacyDexoptDisabledException {
-            final Computer snapshot = snapshotComputer();
-            if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) {
-                return;
-            } else if (snapshot.isInstantAppInternal(
-                               packageName, UserHandle.getCallingUserId(), Process.SYSTEM_UID)) {
-                return;
-            }
-            mDexManager.reconcileSecondaryDexFiles(packageName);
-        }
-
         @Override
         @SuppressWarnings("GuardedBy")
         public void updateRuntimePermissionsFingerprint(@UserIdInt int userId) {
@@ -7512,33 +7430,20 @@
         PackageManagerServiceUtils.enforceSystemOrRootOrShell(
                 "Only the system or shell can delete oat artifacts");
 
-        if (DexOptHelper.useArtService()) {
-            // TODO(chiuwinson): Retrieve filtered snapshot from Computer instance instead.
-            try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
-                            PackageManagerServiceUtils.getPackageManagerLocal()
-                                    .withFilteredSnapshot()) {
-                try {
-                    DeleteResult res = DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
-                            filteredSnapshot, packageName);
-                    return res.getFreedBytes();
-                } catch (IllegalArgumentException e) {
-                    Log.e(TAG, e.toString());
-                    return -1;
-                } catch (IllegalStateException e) {
-                    Slog.wtfStack(TAG, e.toString());
-                    return -1;
-                }
-            }
-        } else {
-            PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
-            if (packageState == null || packageState.getPkg() == null) {
-                return -1; // error code of deleteOptimizedFiles
-            }
+        // TODO(chiuwinson): Retrieve filtered snapshot from Computer instance instead.
+        try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
+                        PackageManagerServiceUtils.getPackageManagerLocal()
+                                .withFilteredSnapshot()) {
             try {
-                return mDexManager.deleteOptimizedFiles(
-                        ArtUtils.createArtPackageInfo(packageState.getPkg(), packageState));
-            } catch (LegacyDexoptDisabledException e) {
-                throw new RuntimeException(e);
+                DeleteResult res = DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
+                        filteredSnapshot, packageName);
+                return res.getFreedBytes();
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, e.toString());
+                return -1;
+            } catch (IllegalStateException e) {
+                Slog.wtfStack(TAG, e.toString());
+                return -1;
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 049737d..83f3b16 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.app.backup.IBackupManager;
 import android.content.ComponentName;
@@ -138,8 +137,6 @@
     private final Singleton<DomainVerificationManagerInternal>
             mDomainVerificationManagerInternalProducer;
     private final Singleton<Handler> mHandlerProducer;
-    private final Singleton<BackgroundDexOptService>
-            mBackgroundDexOptService; // TODO(b/260124949): Remove this.
     private final Singleton<IBackupManager> mIBackupManager;
     private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer;
     private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer;
@@ -180,7 +177,6 @@
             SystemWrapper systemWrapper,
             ServiceProducer getLocalServiceProducer,
             ServiceProducer getSystemServiceProducer,
-            Producer<BackgroundDexOptService> backgroundDexOptService,
             Producer<IBackupManager> iBackupManager,
             Producer<SharedLibrariesImpl> sharedLibrariesProducer,
             Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer,
@@ -234,7 +230,6 @@
                 new Singleton<>(
                         domainVerificationManagerInternalProducer);
         mHandlerProducer = new Singleton<>(handlerProducer);
-        mBackgroundDexOptService = new Singleton<>(backgroundDexOptService);
         mIBackupManager = new Singleton<>(iBackupManager);
         mSharedLibrariesProducer = new Singleton<>(sharedLibrariesProducer);
         mCrossProfileIntentFilterHelperProducer = new Singleton<>(
@@ -409,11 +404,6 @@
         return getLocalService(ActivityManagerInternal.class);
     }
 
-    @Nullable
-    public BackgroundDexOptService getBackgroundDexOptService() {
-        return mBackgroundDexOptService.get(this, mPackageManager);
-    }
-
     public IBackupManager getIBackupManager() {
         return mIBackupManager.get(this, mPackageManager);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 2d79718..289373e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -105,7 +105,6 @@
     public boolean isEngBuild;
     public boolean isUserDebugBuild;
     public int sdkInt = Build.VERSION.SDK_INT;
-    public @Nullable BackgroundDexOptService backgroundDexOptService;
     public final String incrementalVersion = Build.VERSION.INCREMENTAL;
     public BroadcastHelper broadcastHelper;
     public AppDataHelper appDataHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 4fb9b56..a9e1725 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -29,7 +29,6 @@
 
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
 import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.accounts.IAccountManager;
 import android.annotation.NonNull;
@@ -67,7 +66,6 @@
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
-import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
 import android.content.pm.parsing.ApkLite;
@@ -102,8 +100,6 @@
 import android.os.incremental.V4Signature;
 import android.os.storage.StorageManager;
 import android.permission.PermissionManager;
-import android.system.ErrnoException;
-import android.system.Os;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -123,25 +119,20 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.art.ArtManagerLocal;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.PermissionAllowlist;
 import com.android.server.pm.verify.domain.DomainVerificationShell;
 
-import dalvik.system.DexFile;
-
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 import libcore.util.HexEncoding;
 
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
 import java.security.SecureRandom;
@@ -154,7 +145,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CompletableFuture;
@@ -400,15 +390,7 @@
                     return runGetDomainVerificationAgent();
                 default: {
                     if (ART_SERVICE_COMMANDS.contains(cmd)) {
-                        if (DexOptHelper.useArtService()) {
-                            return runArtServiceCommand();
-                        } else {
-                            try {
-                                return runLegacyDexoptCommand(cmd);
-                            } catch (LegacyDexoptDisabledException e) {
-                                throw new RuntimeException(e);
-                            }
-                        }
+                        return runArtServiceCommand();
                     }
 
                     Boolean domainVerificationResult =
@@ -438,40 +420,6 @@
         return -1;
     }
 
-    private int runLegacyDexoptCommand(@NonNull String cmd)
-            throws RemoteException, LegacyDexoptDisabledException {
-        Installer.checkLegacyDexoptDisabled();
-
-        if (!PackageManagerServiceUtils.isRootOrShell(Binder.getCallingUid())) {
-            throw new SecurityException("Dexopt shell commands need root or shell access");
-        }
-
-        switch (cmd) {
-            case "compile":
-                return runCompile();
-            case "reconcile-secondary-dex-files":
-                return runreconcileSecondaryDexFiles();
-            case "force-dex-opt":
-                return runForceDexOpt();
-            case "bg-dexopt-job":
-                return runBgDexOpt();
-            case "cancel-bg-dexopt-job":
-                return cancelBgDexOptJob();
-            case "delete-dexopt":
-                return runDeleteDexOpt();
-            case "dump-profiles":
-                return runDumpProfiles();
-            case "snapshot-profile":
-                return runSnapshotProfile();
-            case "art":
-                getOutPrintWriter().println("ART Service not enabled");
-                return -1;
-            default:
-                // Can't happen.
-                throw new IllegalArgumentException();
-        }
-    }
-
     /**
      * Shows module info
      *
@@ -2067,340 +2015,6 @@
         }
     }
 
-    private int runCompile() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        boolean forceCompilation = false;
-        boolean allPackages = false;
-        boolean clearProfileData = false;
-        String compilerFilter = null;
-        String compilationReason = null;
-        boolean secondaryDex = false;
-        String split = null;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-a":
-                    allPackages = true;
-                    break;
-                case "-c":
-                    clearProfileData = true;
-                    break;
-                case "-f":
-                    forceCompilation = true;
-                    break;
-                case "-m":
-                    compilerFilter = getNextArgRequired();
-                    break;
-                case "-r":
-                    compilationReason = getNextArgRequired();
-                    break;
-                case "--check-prof":
-                    getNextArgRequired();
-                    pw.println("Warning: Ignoring obsolete flag --check-prof "
-                            + "- it is unconditionally enabled now");
-                    break;
-                case "--reset":
-                    forceCompilation = true;
-                    clearProfileData = true;
-                    compilationReason = "install";
-                    break;
-                case "--secondary-dex":
-                    secondaryDex = true;
-                    break;
-                case "--split":
-                    split = getNextArgRequired();
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        final boolean compilerFilterGiven = compilerFilter != null;
-        final boolean compilationReasonGiven = compilationReason != null;
-        // Make sure exactly one of -m, or -r is given.
-        if (compilerFilterGiven && compilationReasonGiven) {
-            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") "
-                    + "at the same time");
-            return 1;
-        }
-        if (!compilerFilterGiven && !compilationReasonGiven) {
-            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation "
-                    + "reason (\"-r\")");
-            return 1;
-        }
-
-        if (allPackages && split != null) {
-            pw.println("-a cannot be specified together with --split");
-            return 1;
-        }
-
-        if (secondaryDex && split != null) {
-            pw.println("--secondary-dex cannot be specified together with --split");
-            return 1;
-        }
-
-        String targetCompilerFilter = null;
-        if (compilerFilterGiven) {
-            if (!DexFile.isValidCompilerFilter(compilerFilter)) {
-                pw.println("Error: \"" + compilerFilter +
-                        "\" is not a valid compilation filter.");
-                return 1;
-            }
-            targetCompilerFilter = compilerFilter;
-        }
-        if (compilationReasonGiven) {
-            int reason = -1;
-            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
-                if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
-                        compilationReason)) {
-                    reason = i;
-                    break;
-                }
-            }
-            if (reason == -1) {
-                pw.println("Error: Unknown compilation reason: " + compilationReason);
-                return 1;
-            }
-            targetCompilerFilter =
-                    PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
-        }
-
-
-        List<String> packageNames = null;
-        if (allPackages) {
-            packageNames = mInterface.getAllPackages();
-            // Compiling the system server is only supported from odrefresh, so skip it.
-            packageNames.removeIf(packageName -> PLATFORM_PACKAGE_NAME.equals(packageName));
-        } else {
-            String packageName = getNextArg();
-            if (packageName == null) {
-                pw.println("Error: package name not specified");
-                return 1;
-            }
-            packageNames = Collections.singletonList(packageName);
-        }
-
-        List<String> failedPackages = new ArrayList<>();
-        int index = 0;
-        for (String packageName : packageNames) {
-            if (clearProfileData) {
-                mInterface.clearApplicationProfileData(packageName);
-            }
-
-            if (allPackages) {
-                pw.println(++index + "/" + packageNames.size() + ": " + packageName);
-                pw.flush();
-            }
-
-            final boolean result = secondaryDex
-                    ? mInterface.performDexOptSecondary(
-                            packageName, targetCompilerFilter, forceCompilation)
-                    : mInterface.performDexOptMode(packageName, true /* checkProfiles */,
-                            targetCompilerFilter, forceCompilation, true /* bootComplete */, split);
-            if (!result) {
-                failedPackages.add(packageName);
-            }
-        }
-
-        if (failedPackages.isEmpty()) {
-            pw.println("Success");
-            return 0;
-        } else if (failedPackages.size() == 1) {
-            pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
-            return 1;
-        } else {
-            pw.print("Failure: the following packages could not be compiled: ");
-            boolean is_first = true;
-            for (String packageName : failedPackages) {
-                if (is_first) {
-                    is_first = false;
-                } else {
-                    pw.print(", ");
-                }
-                pw.print(packageName);
-            }
-            pw.println();
-            return 1;
-        }
-    }
-
-    private int runreconcileSecondaryDexFiles()
-            throws RemoteException, LegacyDexoptDisabledException {
-        String packageName = getNextArg();
-        mPm.legacyReconcileSecondaryDexFiles(packageName);
-        return 0;
-    }
-
-    public int runForceDexOpt() throws RemoteException, LegacyDexoptDisabledException {
-        mPm.legacyForceDexOpt(getNextArgRequired());
-        return 0;
-    }
-
-    private int runBgDexOpt() throws RemoteException, LegacyDexoptDisabledException {
-        String opt = getNextOption();
-
-        if (opt == null) {
-            List<String> packageNames = new ArrayList<>();
-            String arg;
-            while ((arg = getNextArg()) != null) {
-                packageNames.add(arg);
-            }
-            if (!BackgroundDexOptService.getService().runBackgroundDexoptJob(
-                        packageNames.isEmpty() ? null : packageNames)) {
-                getOutPrintWriter().println("Failure");
-                return -1;
-            }
-        } else {
-            String extraArg = getNextArg();
-            if (extraArg != null) {
-                getErrPrintWriter().println("Invalid argument: " + extraArg);
-                return -1;
-            }
-
-            switch (opt) {
-                case "--cancel":
-                    return cancelBgDexOptJob();
-
-                case "--disable":
-                    BackgroundDexOptService.getService().setDisableJobSchedulerJobs(true);
-                    break;
-
-                case "--enable":
-                    BackgroundDexOptService.getService().setDisableJobSchedulerJobs(false);
-                    break;
-
-                default:
-                    getErrPrintWriter().println("Unknown option: " + opt);
-                    return -1;
-            }
-        }
-
-        getOutPrintWriter().println("Success");
-        return 0;
-    }
-
-    private int cancelBgDexOptJob() throws RemoteException, LegacyDexoptDisabledException {
-        BackgroundDexOptService.getService().cancelBackgroundDexoptJob();
-        getOutPrintWriter().println("Success");
-        return 0;
-    }
-
-    private int runDeleteDexOpt() throws RemoteException {
-        PrintWriter pw = getOutPrintWriter();
-        String packageName = getNextArg();
-        if (TextUtils.isEmpty(packageName)) {
-            pw.println("Error: no package name");
-            return 1;
-        }
-        long freedBytes = mPm.deleteOatArtifactsOfPackage(packageName);
-        if (freedBytes < 0) {
-            pw.println("Error: delete failed");
-            return 1;
-        }
-        pw.println("Success: freed " + freedBytes + " bytes");
-        Slog.i(TAG, "delete-dexopt " + packageName + " ,freed " + freedBytes + " bytes");
-        return 0;
-    }
-
-    private int runDumpProfiles() throws RemoteException, LegacyDexoptDisabledException {
-        final PrintWriter pw = getOutPrintWriter();
-        boolean dumpClassesAndMethods = false;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "--dump-classes-and-methods":
-                    dumpClassesAndMethods = true;
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        String packageName = getNextArg();
-        mPm.legacyDumpProfiles(packageName, dumpClassesAndMethods);
-        return 0;
-    }
-
-    private int runSnapshotProfile() throws RemoteException {
-        PrintWriter pw = getOutPrintWriter();
-
-        // Parse the arguments
-        final String packageName = getNextArg();
-        final boolean isBootImage = "android".equals(packageName);
-
-        String codePath = null;
-        String opt;
-        while ((opt = getNextArg()) != null) {
-            switch (opt) {
-                case "--code-path":
-                    if (isBootImage) {
-                        pw.write("--code-path cannot be used for the boot image.");
-                        return -1;
-                    }
-                    codePath = getNextArg();
-                    break;
-                default:
-                    pw.write("Unknown arg: " + opt);
-                    return -1;
-            }
-        }
-
-        // If no code path was explicitly requested, select the base code path.
-        String baseCodePath = null;
-        if (!isBootImage) {
-            PackageInfo packageInfo = mInterface.getPackageInfo(packageName, /* flags */ 0,
-                    /* userId */0);
-            if (packageInfo == null) {
-                pw.write("Package not found " + packageName);
-                return -1;
-            }
-            baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-            if (codePath == null) {
-                codePath = baseCodePath;
-            }
-        }
-
-        // Create the profile snapshot.
-        final SnapshotRuntimeProfileCallback callback = new SnapshotRuntimeProfileCallback();
-        // The calling package is needed to debug permission access.
-        final String callingPackage = (Binder.getCallingUid() == Process.ROOT_UID)
-                ? "root" : "com.android.shell";
-        final int profileType = isBootImage
-                ? ArtManager.PROFILE_BOOT_IMAGE : ArtManager.PROFILE_APPS;
-        if (!mInterface.getArtManager().isRuntimeProfilingEnabled(profileType, callingPackage)) {
-            pw.println("Error: Runtime profiling is not enabled");
-            return -1;
-        }
-        mInterface.getArtManager().snapshotRuntimeProfile(profileType, packageName,
-                codePath, callback, callingPackage);
-        if (!callback.waitTillDone()) {
-            pw.println("Error: callback not called");
-            return callback.mErrCode;
-        }
-
-        // Copy the snapshot profile to the output profile file.
-        try (InputStream inStream = new AutoCloseInputStream(callback.mProfileReadFd)) {
-            final String outputFileSuffix = isBootImage || Objects.equals(baseCodePath, codePath)
-                    ? "" : ("-" + new File(codePath).getName());
-            final String outputProfilePath =
-                    ART_PROFILE_SNAPSHOT_DEBUG_LOCATION + packageName + outputFileSuffix + ".prof";
-            try (OutputStream outStream = new FileOutputStream(outputProfilePath)) {
-                Streams.copy(inStream, outStream);
-            }
-            // Give read permissions to the other group.
-            Os.chmod(outputProfilePath, /*mode*/ DEFAULT_FILE_ACCESS_MODE);
-        } catch (IOException | ErrnoException e) {
-            pw.println("Error when reading the profile fd: " + e.getMessage());
-            e.printStackTrace(pw);
-            return -1;
-        }
-        return 0;
-    }
-
     private ArrayList<String> getRemainingArgs() {
         ArrayList<String> args = new ArrayList<>();
         String arg;
@@ -5212,11 +4826,7 @@
         pw.println("  get-domain-verification-agent");
         pw.println("    Displays the component name of the domain verification agent on device.");
         pw.println("");
-        if (DexOptHelper.useArtService()) {
-            printArtServiceHelp();
-        } else {
-            printLegacyDexoptHelp();
-        }
+        printArtServiceHelp();
         pw.println("");
         mDomainVerificationShell.printHelp(pw);
         pw.println("");
@@ -5235,75 +4845,6 @@
         ipw.decreaseIndent();
     }
 
-    private void printLegacyDexoptHelp() {
-        final PrintWriter pw = getOutPrintWriter();
-        pw.println("  compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]");
-        pw.println("          [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)");
-        pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".  Options are:");
-        pw.println("      -a: compile all packages");
-        pw.println("      -c: clear profile data before compiling");
-        pw.println("      -f: force compilation even if not needed");
-        pw.println("      -m: select compilation mode");
-        pw.println("          MODE is one of the dex2oat compiler filters:");
-        pw.println("            verify");
-        pw.println("            speed-profile");
-        pw.println("            speed");
-        pw.println("      -r: select compilation reason");
-        pw.println("          REASON is one of:");
-        for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
-            pw.println("            " + PackageManagerServiceCompilerMapping.REASON_STRINGS[i]);
-        }
-        pw.println("      --reset: restore package to its post-install state");
-        pw.println("      --check-prof (true | false): ignored - this is always true");
-        pw.println("      --secondary-dex: compile app secondary dex files");
-        pw.println("      --split SPLIT: compile only the given split name");
-        pw.println("");
-        pw.println("  force-dex-opt PACKAGE");
-        pw.println("    Force immediate execution of dex opt for the given PACKAGE.");
-        pw.println("");
-        pw.println("  delete-dexopt PACKAGE");
-        pw.println("    Delete dex optimization results for the given PACKAGE.");
-        pw.println("");
-        pw.println("  bg-dexopt-job [PACKAGE... | --cancel | --disable | --enable]");
-        pw.println("    Controls the background job that optimizes dex files:");
-        pw.println("    Without flags, run background optimization immediately on the given");
-        pw.println("    PACKAGEs, or all packages if none is specified, and wait until the job");
-        pw.println("    finishes. Note that the command only runs the background optimizer logic.");
-        pw.println("    It will run even if the device is not in the idle maintenance mode. If a");
-        pw.println("    job is already running (including one started automatically by the");
-        pw.println("    system) it will wait for it to finish before starting. A background job");
-        pw.println("    will not be started automatically while one started this way is running.");
-        pw.println("      --cancel: Cancels any currently running background optimization job");
-        pw.println("        immediately. This cancels jobs started either automatically by the");
-        pw.println("        system or through this command. Note that cancelling a currently");
-        pw.println("        running bg-dexopt-job command requires running this command from a");
-        pw.println("        separate adb shell.");
-        pw.println("      --disable: Disables background jobs from being started by the job");
-        pw.println("        scheduler. Does not affect bg-dexopt-job invocations from the shell.");
-        pw.println("        Does not imply --cancel. This state will be lost when the");
-        pw.println("        system_server process exits.");
-        pw.println("      --enable: Enables background jobs to be started by the job scheduler");
-        pw.println("        again, if previously disabled by --disable.");
-        pw.println("  cancel-bg-dexopt-job");
-        pw.println("    Same as bg-dexopt-job --cancel.");
-        pw.println("");
-        pw.println("  reconcile-secondary-dex-files TARGET-PACKAGE");
-        pw.println("    Reconciles the package secondary dex files with the generated oat files.");
-        pw.println("");
-        pw.println("  dump-profiles [--dump-classes-and-methods] TARGET-PACKAGE");
-        pw.println("    Dumps method/class profile files to");
-        pw.println("    " + ART_PROFILE_SNAPSHOT_DEBUG_LOCATION
-                + "TARGET-PACKAGE-primary.prof.txt.");
-        pw.println("      --dump-classes-and-methods: passed along to the profman binary to");
-        pw.println("        switch to the format used by 'profman --create-profile-from'.");
-        pw.println("");
-        pw.println("  snapshot-profile TARGET-PACKAGE [--code-path path]");
-        pw.println("    Take a snapshot of the package profiles to");
-        pw.println("    " + ART_PROFILE_SNAPSHOT_DEBUG_LOCATION
-                + "TARGET-PACKAGE[-code-path].prof");
-        pw.println("    If TARGET-PACKAGE=android it will take a snapshot of the boot image");
-    }
-
     private static class LocalIntentReceiver {
         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
 
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 70352be..6b12781 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -23,7 +23,6 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
-import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
 import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
@@ -49,7 +48,6 @@
 import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -511,32 +509,9 @@
         }
 
         removeCodePathLI(codeFile);
-        removeDexFilesLI(allCodePaths, instructionSets);
-    }
 
-    @GuardedBy("mPm.mInstallLock")
-    private void removeDexFilesLI(@NonNull List<String> allCodePaths,
-                                  @Nullable  String[] instructionSets) {
-        if (!allCodePaths.isEmpty()) {
-            if (instructionSets == null) {
-                throw new IllegalStateException("instructionSet == null");
-            }
-            // TODO(b/265813358): ART Service currently doesn't support deleting optimized artifacts
-            // relative to an arbitrary APK path. Skip this and rely on its file GC instead.
-            if (!DexOptHelper.useArtService()) {
-                String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
-                for (String codePath : allCodePaths) {
-                    for (String dexCodeInstructionSet : dexCodeInstructionSets) {
-                        try {
-                            mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
-                        } catch (LegacyDexoptDisabledException e) {
-                            throw new RuntimeException(e);
-                        } catch (Installer.InstallerException ignored) {
-                        }
-                    }
-                }
-            }
-        }
+        // TODO(b/265813358): ART Service currently doesn't support deleting optimized artifacts
+        // relative to an arbitrary APK path. Skip this and rely on its file GC instead.
     }
 
     void cleanUpForMoveInstall(String volumeUuid, String packageName, String fromCodePath) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index ae47aa8..e49dc82 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -28,7 +27,6 @@
 import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.ArtManager.ProfileType;
 import android.content.pm.dex.ArtManagerInternal;
-import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
 import android.content.pm.dex.PackageOptimizationInfo;
 import android.os.Binder;
@@ -39,8 +37,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.system.Os;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -53,22 +49,17 @@
 import com.android.server.art.ArtManagerLocal;
 import com.android.server.pm.DexOptHelper;
 import com.android.server.pm.Installer;
-import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageManagerServiceCompilerMapping;
 import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageStateInternal;
 
 import dalvik.system.DexFile;
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
 
-import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -259,91 +250,27 @@
         }
 
         // All good, create the profile snapshot.
-        if (DexOptHelper.useArtService()) {
-            ParcelFileDescriptor fd;
+        ParcelFileDescriptor fd;
 
-            try (PackageManagerLocal.FilteredSnapshot snapshot =
-                            PackageManagerServiceUtils.getPackageManagerLocal()
-                                    .withFilteredSnapshot()) {
-                fd = DexOptHelper.getArtManagerLocal().snapshotAppProfile(
-                        snapshot, packageName, splitName);
-            } catch (IllegalArgumentException e) {
-                // ArtManagerLocal.snapshotAppProfile couldn't find the package or split. Since
-                // we've checked them above this can only happen due to race, i.e. the package got
-                // removed. So let's report it as SNAPSHOT_FAILED_PACKAGE_NOT_FOUND even if it was
-                // for the split.
-                // TODO(mast): Reuse the same snapshot to avoid this race.
-                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
-                return;
-            } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
-                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-                return;
-            }
-
-            postSuccess(packageName, fd, callback);
-        } else {
-            int appId = UserHandle.getAppId(info.applicationInfo.uid);
-            if (appId < 0) {
-                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-                Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
-                return;
-            }
-
-            try {
-                createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
-                        appId, callback);
-                // Destroy the snapshot, we no longer need it.
-                destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
-            } catch (LegacyDexoptDisabledException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    private void createProfileSnapshot(String packageName, String profileName, String classpath,
-            int appId, ISnapshotRuntimeProfileCallback callback)
-            throws LegacyDexoptDisabledException {
-        // Ask the installer to snapshot the profile.
-        try {
-            if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
-                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-                return;
-            }
-        } catch (InstallerException e) {
+        try (PackageManagerLocal.FilteredSnapshot snapshot =
+                        PackageManagerServiceUtils.getPackageManagerLocal()
+                                .withFilteredSnapshot()) {
+            fd = DexOptHelper.getArtManagerLocal().snapshotAppProfile(
+                    snapshot, packageName, splitName);
+        } catch (IllegalArgumentException e) {
+            // ArtManagerLocal.snapshotAppProfile couldn't find the package or split. Since
+            // we've checked them above this can only happen due to race, i.e. the package got
+            // removed. So let's report it as SNAPSHOT_FAILED_PACKAGE_NOT_FOUND even if it was
+            // for the split.
+            // TODO(mast): Reuse the same snapshot to avoid this race.
+            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
+            return;
+        } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
             postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
             return;
         }
 
-        // Open the snapshot and invoke the callback.
-        File snapshotProfile = ArtManager.getProfileSnapshotFileForName(packageName, profileName);
-
-        ParcelFileDescriptor fd = null;
-        try {
-            fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
-            if (fd == null || !fd.getFileDescriptor().valid()) {
-                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-            } else {
-                postSuccess(packageName, fd, callback);
-            }
-        } catch (FileNotFoundException e) {
-            Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":"
-                    + snapshotProfile, e);
-            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-        }
-    }
-
-    private void destroyProfileSnapshot(String packageName, String profileName)
-            throws LegacyDexoptDisabledException {
-        if (DEBUG) {
-            Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
-        }
-
-        try {
-            mInstaller.destroyProfileSnapshot(packageName, profileName);
-        } catch (InstallerException e) {
-            Slog.e(TAG, "Failed to destroy profile snapshot for " + packageName + ":" + profileName,
-                    e);
-        }
+        postSuccess(packageName, fd, callback);
     }
 
     @Override
@@ -368,42 +295,19 @@
     }
 
     private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
-        if (DexOptHelper.useArtService()) {
-            ParcelFileDescriptor fd;
+        ParcelFileDescriptor fd;
 
-            try (PackageManagerLocal.FilteredSnapshot snapshot =
-                            PackageManagerServiceUtils.getPackageManagerLocal()
-                                    .withFilteredSnapshot()) {
-                fd = DexOptHelper.getArtManagerLocal().snapshotBootImageProfile(snapshot);
-            } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
-                postError(callback, BOOT_IMAGE_ANDROID_PACKAGE,
-                        ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-                return;
-            }
-
-            postSuccess(BOOT_IMAGE_ANDROID_PACKAGE, fd, callback);
-        } else {
-            // Combine the profiles for boot classpath and system server classpath.
-            // This avoids having yet another type of profiles and simplifies the processing.
-            String classpath = String.join(
-                    ":", Os.getenv("BOOTCLASSPATH"), Os.getenv("SYSTEMSERVERCLASSPATH"));
-
-            final String standaloneSystemServerJars = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
-            if (standaloneSystemServerJars != null) {
-                classpath = String.join(":", classpath, standaloneSystemServerJars);
-            }
-
-            try {
-                // Create the snapshot.
-                createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME,
-                        classpath,
-                        /*appId*/ -1, callback);
-                // Destroy the snapshot, we no longer need it.
-                destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
-            } catch (LegacyDexoptDisabledException e) {
-                throw new RuntimeException(e);
-            }
+        try (PackageManagerLocal.FilteredSnapshot snapshot =
+                        PackageManagerServiceUtils.getPackageManagerLocal()
+                                .withFilteredSnapshot()) {
+            fd = DexOptHelper.getArtManagerLocal().snapshotBootImageProfile(snapshot);
+        } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
+            postError(callback, BOOT_IMAGE_ANDROID_PACKAGE,
+                    ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+            return;
         }
+
+        postSuccess(BOOT_IMAGE_ANDROID_PACKAGE, fd, callback);
     }
 
     /**
@@ -451,117 +355,6 @@
         });
     }
 
-    /**
-     * Prepare the application profiles.
-     * For all code paths:
-     *   - create the current primary profile to save time at app startup time.
-     *   - copy the profiles from the associated dex metadata file to the reference profile.
-     */
-    public void prepareAppProfiles(AndroidPackage pkg, @UserIdInt int user,
-            boolean updateReferenceProfileContent) throws LegacyDexoptDisabledException {
-        final int appId = UserHandle.getAppId(pkg.getUid());
-        if (user < 0) {
-            Slog.wtf(TAG, "Invalid user id: " + user);
-            return;
-        }
-        if (appId < 0) {
-            Slog.wtf(TAG, "Invalid app id: " + appId);
-            return;
-        }
-        try {
-            ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
-            for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
-                String codePath = codePathsProfileNames.keyAt(i);
-                String profileName = codePathsProfileNames.valueAt(i);
-                String dexMetadataPath = null;
-                // Passing the dex metadata file to the prepare method will update the reference
-                // profile content. As such, we look for the dex metadata file only if we need to
-                // perform an update.
-                if (updateReferenceProfileContent) {
-                    File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
-                    dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
-                }
-                synchronized (mInstaller) {
-                    boolean result = mInstaller.prepareAppProfile(pkg.getPackageName(), user, appId,
-                            profileName, codePath, dexMetadataPath);
-                    if (!result) {
-                        Slog.e(TAG, "Failed to prepare profile for " +
-                                pkg.getPackageName() + ":" + codePath);
-                    }
-                }
-            }
-        } catch (InstallerException e) {
-            Slog.e(TAG, "Failed to prepare profile for " + pkg.getPackageName(), e);
-        }
-    }
-
-    /**
-     * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
-     */
-    public void prepareAppProfiles(AndroidPackage pkg, int[] user,
-            boolean updateReferenceProfileContent) throws LegacyDexoptDisabledException {
-        for (int i = 0; i < user.length; i++) {
-            prepareAppProfiles(pkg, user[i], updateReferenceProfileContent);
-        }
-    }
-
-    /**
-     * Clear the profiles for the given package.
-     */
-    public void clearAppProfiles(AndroidPackage pkg) throws LegacyDexoptDisabledException {
-        try {
-            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
-            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
-                String profileName = packageProfileNames.valueAt(i);
-                mInstaller.clearAppProfiles(pkg.getPackageName(), profileName);
-            }
-        } catch (InstallerException e) {
-            Slog.w(TAG, String.valueOf(e));
-        }
-    }
-
-    /**
-     * Dumps the profiles for the given package.
-     */
-    public void dumpProfiles(AndroidPackage pkg, boolean dumpClassesAndMethods)
-            throws LegacyDexoptDisabledException {
-        final int sharedGid = UserHandle.getSharedAppGid(pkg.getUid());
-        try {
-            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
-            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
-                String codePath = packageProfileNames.keyAt(i);
-                String profileName = packageProfileNames.valueAt(i);
-                mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath,
-                                        dumpClassesAndMethods);
-            }
-        } catch (InstallerException e) {
-            Slog.w(TAG, "Failed to dump profiles", e);
-        }
-    }
-
-    /**
-     * Build the profiles names for all the package code paths (excluding resource only paths).
-     * Return the map [code path -> profile name].
-     */
-    private ArrayMap<String, String> getPackageProfileNames(AndroidPackage pkg) {
-        ArrayMap<String, String> result = new ArrayMap<>();
-        if (pkg.isDeclaredHavingCode()) {
-            result.put(pkg.getBaseApkPath(), ArtManager.getProfileName(null));
-        }
-
-        String[] splitCodePaths = pkg.getSplitCodePaths();
-        int[] splitFlags = pkg.getSplitFlags();
-        String[] splitNames = pkg.getSplitNames();
-        if (!ArrayUtils.isEmpty(splitCodePaths)) {
-            for (int i = 0; i < splitCodePaths.length; i++) {
-                if ((splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
-                    result.put(splitCodePaths[i], ArtManager.getProfileName(splitNames[i]));
-                }
-            }
-        }
-        return result;
-    }
-
     // Constants used for logging compilation filter to TRON.
     // DO NOT CHANGE existing values.
     //
@@ -792,6 +585,7 @@
                 String packageName, String activityName, long version) {
             // For example: /data/misc/iorapd/com.google.android.GoogleCamera/
             // 60092239/com.android.camera.CameraLauncher/compiled_traces/compiled_trace.pb
+            // TODO(b/258223472): Clean up iorap code.
             Path tracePath = Paths.get(IORAP_DIR,
                                        packageName,
                                        Long.toString(version),
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index 57f4a5d..a24a231 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -22,13 +22,11 @@
 import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK;
 import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK;
 
-import android.app.job.JobParameters;
 import android.os.SystemClock;
 import android.util.Slog;
 import android.util.jar.StrictJarFile;
 
 import com.android.internal.art.ArtStatsLog;
-import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.PackageManagerService;
 
 import java.io.IOException;
@@ -303,42 +301,4 @@
                     ArtStatsLog.ART_DATUM_REPORTED__UFFD_SUPPORT__ART_UFFD_SUPPORT_UNKNOWN);
         }
     }
-
-    private static final Map<Integer, Integer> STATUS_MAP =
-            Map.of(BackgroundDexOptService.STATUS_UNSPECIFIED,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN,
-                    BackgroundDexOptService.STATUS_OK,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED,
-                    BackgroundDexOptService.STATUS_ABORT_BY_CANCELLATION,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_CANCELLATION,
-                    BackgroundDexOptService.STATUS_ABORT_NO_SPACE_LEFT,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_NO_SPACE_LEFT,
-                    BackgroundDexOptService.STATUS_ABORT_THERMAL,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_THERMAL,
-                    BackgroundDexOptService.STATUS_ABORT_BATTERY,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BATTERY,
-                    BackgroundDexOptService.STATUS_DEX_OPT_FAILED,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED,
-                    BackgroundDexOptService.STATUS_FATAL_ERROR,
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_FATAL_ERROR);
-
-    /** Helper class to write background dexopt job stats to statsd. */
-    public static class BackgroundDexoptJobStatsLogger {
-        /** Writes background dexopt job stats to statsd. */
-        public void write(@BackgroundDexOptService.Status int status,
-                          @JobParameters.StopReason int cancellationReason,
-                          long durationMs) {
-            ArtStatsLog.write(
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
-                    STATUS_MAP.getOrDefault(status,
-                            ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN),
-                    cancellationReason,
-                    durationMs,
-                    0, // deprecated, used to be durationIncludingSleepMs
-                    0, // optimizedPackagesCount
-                    0, // packagesDependingOnBootClasspathCount
-                    0, // totalPackagesCount
-                    ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__PASS__PASS_UNKNOWN);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 78c13f8..e93d320 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -17,7 +17,6 @@
 package com.android.server.pm.dex;
 
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
 
 import static java.util.function.Function.identity;
@@ -31,12 +30,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackagePartitions;
 import android.os.BatteryManager;
-import android.os.FileUtils;
 import android.os.PowerManager;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.os.storage.StorageManager;
 import android.util.Log;
 import android.util.Slog;
 import android.util.jar.StrictJarFile;
@@ -44,8 +40,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.Installer;
-import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.PackageDexOptimizer;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageManagerServiceUtils;
@@ -54,8 +48,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -496,60 +488,6 @@
     }
 
     /**
-     * Perform dexopt on with the given {@code options} on the secondary dex files.
-     * @return true if all secondary dex files were processed successfully (compiled or skipped
-     *         because they don't need to be compiled)..
-     */
-    public boolean dexoptSecondaryDex(DexoptOptions options) throws LegacyDexoptDisabledException {
-        if (isPlatformPackage(options.getPackageName())) {
-            // We could easily redirect to #dexoptSystemServer in this case. But there should be
-            // no-one calling this method directly for system server.
-            // As such we prefer to abort in this case.
-            Slog.wtf(TAG, "System server jars should be optimized with dexoptSystemServer");
-            return false;
-        }
-
-        PackageDexOptimizer pdo = getPackageDexOptimizer(options);
-        String packageName = options.getPackageName();
-        PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
-        if (useInfo.getDexUseInfoMap().isEmpty()) {
-            if (DEBUG) {
-                Slog.d(TAG, "No secondary dex use for package:" + packageName);
-            }
-            // Nothing to compile, return true.
-            return true;
-        }
-        boolean success = true;
-        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
-            String dexPath = entry.getKey();
-            DexUseInfo dexUseInfo = entry.getValue();
-
-            PackageInfo pkg;
-            try {
-                pkg = getPackageManager().getPackageInfo(packageName, /*flags*/0,
-                    dexUseInfo.getOwnerUserId());
-            } catch (RemoteException e) {
-                throw new AssertionError(e);
-            }
-            // It may be that the package gets uninstalled while we try to compile its
-            // secondary dex files. If that's the case, just ignore.
-            // Note that we don't break the entire loop because the package might still be
-            // installed for other users.
-            if (pkg == null) {
-                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
-                        + " for user " + dexUseInfo.getOwnerUserId());
-                mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
-                continue;
-            }
-
-            int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
-                    dexUseInfo, options);
-            success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
-        }
-        return success;
-    }
-
-    /**
      * Select the dex optimizer based on the force parameter.
      * Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
      * the necessary dexopt flags to make sure that compilation is not skipped. This avoid
@@ -564,101 +502,6 @@
     }
 
     /**
-     * Reconcile the information we have about the secondary dex files belonging to
-     * {@code packagName} and the actual dex files. For all dex files that were
-     * deleted, update the internal records and delete any generated oat files.
-     */
-    public void reconcileSecondaryDexFiles(String packageName)
-            throws LegacyDexoptDisabledException {
-        PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
-        if (useInfo.getDexUseInfoMap().isEmpty()) {
-            if (DEBUG) {
-                Slog.d(TAG, "No secondary dex use for package:" + packageName);
-            }
-            // Nothing to reconcile.
-            return;
-        }
-
-        boolean updated = false;
-        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
-            String dexPath = entry.getKey();
-            DexUseInfo dexUseInfo = entry.getValue();
-            PackageInfo pkg = null;
-            try {
-                // Note that we look for the package in the PackageManager just to be able
-                // to get back the real app uid and its storage kind. These are only used
-                // to perform extra validation in installd.
-                // TODO(calin): maybe a bit overkill.
-                pkg = getPackageManager().getPackageInfo(packageName, /*flags*/0,
-                    dexUseInfo.getOwnerUserId());
-            } catch (RemoteException ignore) {
-                // Can't happen, DexManager is local.
-            }
-            if (pkg == null) {
-                // It may be that the package was uninstalled while we process the secondary
-                // dex files.
-                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
-                        + " for user " + dexUseInfo.getOwnerUserId());
-                // Update the usage and continue, another user might still have the package.
-                updated = mPackageDexUsage.removeUserPackage(
-                        packageName, dexUseInfo.getOwnerUserId()) || updated;
-                continue;
-            }
-
-            // Special handle system server files.
-            // We don't need an installd call because we have permissions to check if the file
-            // exists.
-            if (isPlatformPackage(packageName)) {
-                if (!Files.exists(Paths.get(dexPath))) {
-                    if (DEBUG) {
-                        Slog.w(TAG, "A dex file previously loaded by System Server does not exist "
-                                + " anymore: " + dexPath);
-                    }
-                    updated = mPackageDexUsage.removeUserPackage(
-                            packageName, dexUseInfo.getOwnerUserId()) || updated;
-                }
-                continue;
-            }
-
-            // This is a regular application.
-            ApplicationInfo info = pkg.applicationInfo;
-            int flags = 0;
-            if (info.deviceProtectedDataDir != null &&
-                    FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
-                flags |= StorageManager.FLAG_STORAGE_DE;
-            } else if (info.credentialProtectedDataDir!= null &&
-                    FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
-                flags |= StorageManager.FLAG_STORAGE_CE;
-            } else {
-                Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
-                updated = mPackageDexUsage.removeDexFile(
-                        packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
-                continue;
-            }
-
-            boolean dexStillExists = true;
-            synchronized(mInstallLock) {
-                try {
-                    String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
-                    dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
-                            info.uid, isas, info.volumeUuid, flags);
-                } catch (InstallerException e) {
-                    Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
-                            " : " + e.getMessage());
-                }
-            }
-            if (!dexStillExists) {
-                updated = mPackageDexUsage.removeDexFile(
-                        packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
-            }
-
-        }
-        if (updated) {
-            mPackageDexUsage.maybeWriteAsync();
-        }
-    }
-
-    /**
      * Return all packages that contain records of secondary dex files.
      */
     public Set<String> getAllPackagesWithSecondaryDexFiles() {
@@ -852,33 +695,6 @@
         return isBtmCritical;
     }
 
-    /**
-     * Deletes all the optimizations files generated by ART.
-     * This is best effort, and the method will log but not throw errors
-     * for individual deletes
-     *
-     * @param packageInfo the package information.
-     * @return the number of freed bytes or -1 if there was an error in the process.
-     */
-    public long deleteOptimizedFiles(ArtPackageInfo packageInfo)
-            throws LegacyDexoptDisabledException {
-        long freedBytes = 0;
-        boolean hadErrors = false;
-        final String packageName = packageInfo.getPackageName();
-        for (String codePath : packageInfo.getCodePaths()) {
-            for (String isa : packageInfo.getInstructionSets()) {
-                try {
-                    freedBytes += mInstaller.deleteOdex(packageName, codePath, isa,
-                            packageInfo.getOatDir());
-                } catch (InstallerException e) {
-                    Log.e(TAG, "Failed deleting oat files for " + codePath, e);
-                    hadErrors = true;
-                }
-            }
-        }
-        return hadErrors ? -1 : freedBytes;
-    }
-
     public static class RegisterDexModuleResult {
         public RegisterDexModuleResult() {
             this(false, null);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
deleted file mode 100644
index 9a7ee4d..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
- * Copyright (C) 2021 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 static com.android.server.pm.BackgroundDexOptService.STATUS_DEX_OPT_FAILED;
-import static com.android.server.pm.BackgroundDexOptService.STATUS_FATAL_ERROR;
-import static com.android.server.pm.BackgroundDexOptService.STATUS_OK;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.annotation.Nullable;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.HandlerThread;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.PinnerService;
-import com.android.server.pm.dex.DexManager;
-import com.android.server.pm.dex.DexoptOptions;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.stream.Collectors;
-
-@RunWith(MockitoJUnitRunner.class)
-public final class BackgroundDexOptServiceUnitTest {
-    private static final String TAG = BackgroundDexOptServiceUnitTest.class.getSimpleName();
-
-    private static final long USABLE_SPACE_NORMAL = 1_000_000_000;
-    private static final long STORAGE_LOW_BYTES = 1_000_000;
-
-    private static final long TEST_WAIT_TIMEOUT_MS = 10_000;
-
-    private static final String PACKAGE_AAA = "aaa";
-    private static final List<String> DEFAULT_PACKAGE_LIST = List.of(PACKAGE_AAA, "bbb");
-    private int mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED;
-
-    // Store expected dexopt sequence for verification.
-    private ArrayList<DexOptInfo> mDexInfoSequence = new ArrayList<>();
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private PackageManagerService mPackageManager;
-    @Mock
-    private DexOptHelper mDexOptHelper;
-    @Mock
-    private DexManager mDexManager;
-    @Mock
-    private PinnerService mPinnerService;
-    @Mock
-    private JobScheduler mJobScheduler;
-    @Mock
-    private BackgroundDexOptService.Injector mInjector;
-    @Mock
-    private BackgroundDexOptJobService mJobServiceForPostBoot;
-    @Mock
-    private BackgroundDexOptJobService mJobServiceForIdle;
-
-    private final JobParameters mJobParametersForPostBoot =
-            createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
-    private final JobParameters mJobParametersForIdle =
-            createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE);
-
-    private static JobParameters createJobParameters(int jobId) {
-        JobParameters params = mock(JobParameters.class);
-        when(params.getJobId()).thenReturn(jobId);
-        return params;
-    }
-
-    private BackgroundDexOptService mService;
-
-    private StartAndWaitThread mDexOptThread;
-    private StartAndWaitThread mCancelThread;
-
-    @Before
-    public void setUp() throws Exception {
-        // These tests are only applicable to the legacy BackgroundDexOptService and cannot be run
-        // when ART Service is enabled.
-        Assume.assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false));
-
-        when(mInjector.getCallingUid()).thenReturn(Process.FIRST_APPLICATION_UID);
-        when(mInjector.getContext()).thenReturn(mContext);
-        when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper);
-        when(mInjector.getDexManager()).thenReturn(mDexManager);
-        when(mInjector.getPinnerService()).thenReturn(mPinnerService);
-        when(mInjector.getJobScheduler()).thenReturn(mJobScheduler);
-        when(mInjector.getPackageManagerService()).thenReturn(mPackageManager);
-
-        // These mocking can be overwritten in some tests but still keep it here as alternative
-        // takes too many repetitive codes.
-        when(mInjector.getDataDirUsableSpace()).thenReturn(USABLE_SPACE_NORMAL);
-        when(mInjector.getDataDirStorageLowBytes()).thenReturn(STORAGE_LOW_BYTES);
-        when(mInjector.getDexOptThermalCutoff()).thenReturn(PowerManager.THERMAL_STATUS_CRITICAL);
-        when(mInjector.getCurrentThermalStatus()).thenReturn(PowerManager.THERMAL_STATUS_NONE);
-        when(mInjector.supportSecondaryDex()).thenReturn(true);
-        setupDexOptHelper();
-
-        mService = new BackgroundDexOptService(mInjector);
-    }
-
-    private void setupDexOptHelper() {
-        when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(DEFAULT_PACKAGE_LIST);
-        when(mDexOptHelper.performDexOptWithStatus(any())).thenAnswer(inv -> {
-            DexoptOptions opt = inv.getArgument(0);
-            if (opt.getPackageName().equals(PACKAGE_AAA)) {
-                return mDexOptResultForPackageAAA;
-            }
-            return PackageDexOptimizer.DEX_OPT_PERFORMED;
-        });
-        when(mDexOptHelper.performDexOpt(any())).thenReturn(true);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        LocalServices.removeServiceForTest(BackgroundDexOptService.class);
-    }
-
-    @Test
-    public void testGetService() {
-        assertThat(BackgroundDexOptService.getService()).isEqualTo(mService);
-    }
-
-    @Test
-    public void testBootCompleted() throws Exception {
-        initUntilBootCompleted();
-    }
-
-    @Test
-    public void testNoExecutionForIdleJobBeforePostBootUpdate() throws Exception {
-        initUntilBootCompleted();
-
-        assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isFalse();
-    }
-
-    @Test
-    public void testNoExecutionForLowStorage() throws Exception {
-        initUntilBootCompleted();
-        when(mPackageManager.isStorageLow()).thenReturn(true);
-
-        assertThat(mService.onStartJob(mJobServiceForPostBoot,
-                mJobParametersForPostBoot)).isFalse();
-        verify(mDexOptHelper, never()).performDexOpt(any());
-    }
-
-    @Test
-    public void testNoExecutionForNoOptimizablePackages() throws Exception {
-        initUntilBootCompleted();
-        when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(Collections.emptyList());
-
-        assertThat(mService.onStartJob(mJobServiceForPostBoot,
-                mJobParametersForPostBoot)).isFalse();
-        verify(mDexOptHelper, never()).performDexOpt(any());
-    }
-
-    @Test
-    public void testPostBootUpdateFullRun() throws Exception {
-        initUntilBootCompleted();
-
-        runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
-                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
-    }
-
-    @Test
-    public void testPostBootUpdateFullRunWithPackageFailure() throws Exception {
-        mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED;
-
-        initUntilBootCompleted();
-
-        runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED,
-                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
-
-        assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
-        assertThat(getFailedPackageNamesSecondary()).isEmpty();
-    }
-
-    @Test
-    public void testIdleJobFullRun() throws Exception {
-        initUntilBootCompleted();
-        runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
-                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
-        runFullJob(mJobServiceForIdle, mJobParametersForIdle,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
-                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
-    }
-
-    @Test
-    public void testIdleJobFullRunWithFailureOnceAndSuccessAfterUpdate() throws Exception {
-        mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED;
-
-        initUntilBootCompleted();
-
-        runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED,
-                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
-
-        assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
-        assertThat(getFailedPackageNamesSecondary()).isEmpty();
-
-        runFullJob(mJobServiceForIdle, mJobParametersForIdle,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
-                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
-
-        assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
-        assertThat(getFailedPackageNamesSecondary()).isEmpty();
-
-        mService.notifyPackageChanged(PACKAGE_AAA);
-
-        assertThat(getFailedPackageNamesPrimary()).isEmpty();
-        assertThat(getFailedPackageNamesSecondary()).isEmpty();
-
-        // Succeed this time.
-        mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED;
-
-        runFullJob(mJobServiceForIdle, mJobParametersForIdle,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
-                /* totalJobFinishedWithParams= */ 2, /* expectedSkippedPackage= */ null);
-
-        assertThat(getFailedPackageNamesPrimary()).isEmpty();
-        assertThat(getFailedPackageNamesSecondary()).isEmpty();
-    }
-
-    @Test
-    public void testIdleJobFullRunWithFatalError() throws Exception {
-        initUntilBootCompleted();
-        runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
-                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
-
-        doThrow(RuntimeException.class).when(mDexOptHelper).performDexOptWithStatus(any());
-
-        runFullJob(mJobServiceForIdle, mJobParametersForIdle,
-                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_FATAL_ERROR,
-                /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
-    }
-
-    @Test
-    public void testSystemReadyWhenDisabled() throws Exception {
-        when(mInjector.isBackgroundDexOptDisabled()).thenReturn(true);
-
-        mService.systemReady();
-
-        verify(mContext, never()).registerReceiver(any(), any());
-    }
-
-    @Test
-    public void testStopByCancelFlag() throws Exception {
-        when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
-        initUntilBootCompleted();
-
-        assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
-        ArgumentCaptor<Runnable> argDexOptThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
-        verify(mInjector, atLeastOnce()).createAndStartThread(any(),
-                argDexOptThreadRunnable.capture());
-
-        // Stopping requires a separate thread
-        HandlerThread cancelThread = new HandlerThread("Stopping");
-        cancelThread.start();
-        when(mInjector.createAndStartThread(any(), any())).thenReturn(cancelThread);
-
-        // Cancel
-        assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
-        // Capture Runnable for cancel
-        ArgumentCaptor<Runnable> argCancelThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
-        verify(mInjector, atLeastOnce()).createAndStartThread(any(),
-                argCancelThreadRunnable.capture());
-
-        // Execute cancelling part
-        cancelThread.getThreadHandler().post(argCancelThreadRunnable.getValue());
-
-        verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking(true);
-
-        // Dexopt thread run and cancelled
-        argDexOptThreadRunnable.getValue().run();
-
-        // Wait until cancellation Runnable is completed.
-        assertThat(cancelThread.getThreadHandler().runWithScissors(
-                argCancelThreadRunnable.getValue(), TEST_WAIT_TIMEOUT_MS)).isTrue();
-
-        // Now cancel completed
-        verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true);
-        verifyLastControlDexOptBlockingCall(false);
-    }
-
-    @Test
-    public void testPostUpdateCancelFirst() throws Exception {
-        initUntilBootCompleted();
-        when(mInjector.createAndStartThread(any(), any())).thenAnswer(
-                i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1)));
-
-        // Start
-        assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-        // Cancel
-        assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
-        mCancelThread.runActualRunnable();
-
-        // Wait until cancel has set the flag.
-        verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking(
-                true);
-
-        mDexOptThread.runActualRunnable();
-
-        // All threads should finish.
-        mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-        mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
-
-        // Retry later if post boot job was cancelled
-        verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true);
-        verifyLastControlDexOptBlockingCall(false);
-    }
-
-    @Test
-    public void testPostUpdateCancelLater() throws Exception {
-        initUntilBootCompleted();
-        when(mInjector.createAndStartThread(any(), any())).thenAnswer(
-                i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1)));
-
-        // Start
-        assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-        // Cancel
-        assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
-        // Dexopt thread runs and finishes
-        mDexOptThread.runActualRunnable();
-        mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-
-        mCancelThread.runActualRunnable();
-        mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
-
-        // Already completed before cancel, so no rescheduling.
-        verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, false);
-        verify(mDexOptHelper, never()).controlDexOptBlocking(true);
-    }
-
-    @Test
-    public void testPeriodicJobCancelFirst() throws Exception {
-        initUntilBootCompleted();
-        when(mInjector.createAndStartThread(any(), any())).thenAnswer(
-                i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1)));
-
-        // Start and finish post boot job
-        assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-        mDexOptThread.runActualRunnable();
-        mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-
-        // Start
-        assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue();
-        // Cancel
-        assertThat(mService.onStopJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue();
-
-        mCancelThread.runActualRunnable();
-
-        // Wait until cancel has set the flag.
-        verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking(
-                true);
-
-        mDexOptThread.runActualRunnable();
-
-        // All threads should finish.
-        mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-        mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
-
-        // The job should be rescheduled.
-        verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true /* wantsReschedule */);
-        verifyLastControlDexOptBlockingCall(false);
-    }
-
-    @Test
-    public void testPeriodicJobCancelLater() throws Exception {
-        initUntilBootCompleted();
-        when(mInjector.createAndStartThread(any(), any())).thenAnswer(
-                i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1)));
-
-        // Start and finish post boot job
-        assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-        mDexOptThread.runActualRunnable();
-        mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-
-        // Start
-        assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue();
-        // Cancel
-        assertThat(mService.onStopJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue();
-
-        // Dexopt thread finishes first.
-        mDexOptThread.runActualRunnable();
-        mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-
-        mCancelThread.runActualRunnable();
-        mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
-
-        // Always reschedule for periodic job
-        verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false);
-        verify(mDexOptHelper, never()).controlDexOptBlocking(true);
-    }
-
-    @Test
-    public void testStopByThermal() throws Exception {
-        when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
-        initUntilBootCompleted();
-
-        assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
-        ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
-        verify(mInjector, atLeastOnce()).createAndStartThread(any(), argThreadRunnable.capture());
-
-        // Thermal cancel level
-        when(mInjector.getCurrentThermalStatus()).thenReturn(PowerManager.THERMAL_STATUS_CRITICAL);
-
-        argThreadRunnable.getValue().run();
-
-        verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true);
-        verifyLastControlDexOptBlockingCall(false);
-    }
-
-    @Test
-    public void testRunShellCommandWithInvalidUid() {
-        // Test uid cannot execute the command APIs
-        assertThrows(SecurityException.class, () -> mService.runBackgroundDexoptJob(null));
-    }
-
-    @Test
-    public void testCancelShellCommandWithInvalidUid() {
-        // Test uid cannot execute the command APIs
-        assertThrows(SecurityException.class, () -> mService.cancelBackgroundDexoptJob());
-    }
-
-    @Test
-    public void testDisableJobSchedulerJobs() throws Exception {
-        when(mInjector.getCallingUid()).thenReturn(Process.SHELL_UID);
-        mService.setDisableJobSchedulerJobs(true);
-        assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isFalse();
-        verify(mDexOptHelper, never()).performDexOpt(any());
-        verify(mDexOptHelper, never()).performDexOptWithStatus(any());
-    }
-
-    @Test
-    public void testSetDisableJobSchedulerJobsWithInvalidUid() {
-        // Test uid cannot execute the command APIs
-        assertThrows(SecurityException.class, () -> mService.setDisableJobSchedulerJobs(true));
-    }
-
-    private void initUntilBootCompleted() throws Exception {
-        ArgumentCaptor<BroadcastReceiver> argReceiver = ArgumentCaptor.forClass(
-                BroadcastReceiver.class);
-        ArgumentCaptor<IntentFilter> argIntentFilter = ArgumentCaptor.forClass(IntentFilter.class);
-
-        mService.systemReady();
-
-        verify(mContext).registerReceiver(argReceiver.capture(), argIntentFilter.capture());
-        assertThat(argIntentFilter.getValue().getAction(0)).isEqualTo(Intent.ACTION_BOOT_COMPLETED);
-
-        argReceiver.getValue().onReceive(mContext, null);
-
-        verify(mContext).unregisterReceiver(argReceiver.getValue());
-        ArgumentCaptor<JobInfo> argJobs = ArgumentCaptor.forClass(JobInfo.class);
-        verify(mJobScheduler, times(2)).schedule(argJobs.capture());
-
-        List<Integer> expectedJobIds = Arrays.asList(BackgroundDexOptService.JOB_IDLE_OPTIMIZE,
-                BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
-        List<Integer> jobIds = argJobs.getAllValues().stream().map(job -> job.getId()).collect(
-                Collectors.toList());
-        assertThat(jobIds).containsExactlyElementsIn(expectedJobIds);
-    }
-
-    private void verifyLastControlDexOptBlockingCall(boolean expected) throws Exception {
-        ArgumentCaptor<Boolean> argDexOptBlock = ArgumentCaptor.forClass(Boolean.class);
-        verify(mDexOptHelper, atLeastOnce()).controlDexOptBlocking(argDexOptBlock.capture());
-        assertThat(argDexOptBlock.getValue()).isEqualTo(expected);
-    }
-
-    private void runFullJob(BackgroundDexOptJobService jobService, JobParameters params,
-            boolean expectedReschedule, int expectedStatus, int totalJobFinishedWithParams,
-            @Nullable String expectedSkippedPackage) throws Exception {
-        when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
-        addFullRunSequence(expectedSkippedPackage);
-        assertThat(mService.onStartJob(jobService, params)).isTrue();
-
-        ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
-        verify(mInjector, atLeastOnce()).createAndStartThread(any(), argThreadRunnable.capture());
-
-        try {
-            argThreadRunnable.getValue().run();
-        } catch (RuntimeException e) {
-            if (expectedStatus != STATUS_FATAL_ERROR) {
-                throw e;
-            }
-        }
-
-        verify(jobService, times(totalJobFinishedWithParams)).jobFinished(params,
-                expectedReschedule);
-        // Never block
-        verify(mDexOptHelper, never()).controlDexOptBlocking(true);
-        if (expectedStatus != STATUS_FATAL_ERROR) {
-            verifyPerformDexOpt();
-        }
-        assertThat(getLastExecutionStatus()).isEqualTo(expectedStatus);
-    }
-
-    private void verifyPerformDexOpt() {
-        InOrder inOrder = inOrder(mDexOptHelper);
-        inOrder.verify(mDexOptHelper).getOptimizablePackages(any());
-        for (DexOptInfo info : mDexInfoSequence) {
-            if (info.isPrimary) {
-                verify(mDexOptHelper).performDexOptWithStatus(
-                        argThat((option) -> option.getPackageName().equals(info.packageName)
-                                && !option.isDexoptOnlySecondaryDex()));
-            } else {
-                inOrder.verify(mDexOptHelper).performDexOpt(
-                        argThat((option) -> option.getPackageName().equals(info.packageName)
-                                && option.isDexoptOnlySecondaryDex()));
-            }
-        }
-
-        // Even InOrder cannot check the order if the same call is made multiple times.
-        // To check the order across multiple runs, we reset the mock so that order can be checked
-        // in each call.
-        mDexInfoSequence.clear();
-        reset(mDexOptHelper);
-        setupDexOptHelper();
-    }
-
-    private String findDumpValueForKey(String key) {
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        PrintWriter pw = new PrintWriter(out, true);
-        IndentingPrintWriter writer = new IndentingPrintWriter(pw, "");
-        try {
-            mService.dump(writer);
-            writer.flush();
-            Log.i(TAG, "dump output:" + out.toString());
-            for (String line : out.toString().split(System.lineSeparator())) {
-                String[] vals = line.split(":");
-                if (vals[0].equals(key)) {
-                    if (vals.length == 2) {
-                        return vals[1].strip();
-                    } else {
-                        break;
-                    }
-                }
-            }
-            return "";
-        } finally {
-            writer.close();
-        }
-    }
-
-    List<String> findStringListFromDump(String key) {
-        String values = findDumpValueForKey(key);
-        if (values.isEmpty()) {
-            return Collections.emptyList();
-        }
-        return Arrays.asList(values.split(","));
-    }
-
-    private List<String> getFailedPackageNamesPrimary() {
-        return findStringListFromDump("mFailedPackageNamesPrimary");
-    }
-
-    private List<String> getFailedPackageNamesSecondary() {
-        return findStringListFromDump("mFailedPackageNamesSecondary");
-    }
-
-    private int getLastExecutionStatus() {
-        return Integer.parseInt(findDumpValueForKey("mLastExecutionStatus"));
-    }
-
-    private static class DexOptInfo {
-        public final String packageName;
-        public final boolean isPrimary;
-
-        private DexOptInfo(String packageName, boolean isPrimary) {
-            this.packageName = packageName;
-            this.isPrimary = isPrimary;
-        }
-    }
-
-    private void addFullRunSequence(@Nullable String expectedSkippedPackage) {
-        for (String packageName : DEFAULT_PACKAGE_LIST) {
-            if (packageName.equals(expectedSkippedPackage)) {
-                // only fails primary dexopt in mocking but add secodary
-                mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false));
-            } else {
-                mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ true));
-                mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false));
-            }
-        }
-    }
-
-    private static class StartAndWaitThread extends Thread {
-        private final Runnable mActualRunnable;
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-
-        private StartAndWaitThread(String name, Runnable runnable) {
-            super(name);
-            mActualRunnable = runnable;
-        }
-
-        private void runActualRunnable() {
-            mLatch.countDown();
-        }
-
-        @Override
-        public void run() {
-            // Thread is started but does not run actual code. This is for controlling the execution
-            // order while still meeting Thread.isAlive() check.
-            try {
-                mLatch.await();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-            mActualRunnable.run();
-        }
-    }
-
-    private Thread createAndStartExecutionThread(String name, Runnable runnable) {
-        final boolean isDexOptThread = !name.equals("DexOptCancel");
-        StartAndWaitThread thread = new StartAndWaitThread(name, runnable);
-        if (isDexOptThread) {
-            mDexOptThread = thread;
-        } else {
-            mCancelThread = thread;
-        }
-        thread.start();
-        return thread;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 7891661..643dcec 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -111,6 +111,8 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerService;
 
+import com.google.common.collect.Range;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -155,6 +157,7 @@
     private UserController mUserController;
     private TestInjector mInjector;
     private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
+    private final HashMap<Integer, UserInfo> mUserInfos = new HashMap<>();
 
     private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ };
 
@@ -587,25 +590,25 @@
 
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
-        int numerOfUserSwitches = 1;
+        int numberOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
-                numerOfUserSwitches, false);
+                numberOfUserSwitches, false);
         // running: user 0, USER_ID
         assertTrue(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
                 mUserController.getRunningUsersLU());
 
-        numerOfUserSwitches++;
+        numberOfUserSwitches++;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
-                numerOfUserSwitches, false);
+                numberOfUserSwitches, false);
         // running: user 0, USER_ID, USER_ID1
         assertFalse(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}),
                 mUserController.getRunningUsersLU());
 
-        numerOfUserSwitches++;
+        numberOfUserSwitches++;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
-                numerOfUserSwitches, false);
+                numberOfUserSwitches, false);
         UserState ussUser2 = mUserStates.get(TEST_USER_ID2);
         // skip middle step and call this directly.
         mUserController.finishUserSwitch(ussUser2);
@@ -631,20 +634,20 @@
 
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
-        int numerOfUserSwitches = 1;
+        int numberOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
-                numerOfUserSwitches, false);
+                numberOfUserSwitches, false);
         // running: user 0, USER_ID
         assertTrue(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
                 mUserController.getRunningUsersLU());
-        numerOfUserSwitches++;
+        numberOfUserSwitches++;
 
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
-                numerOfUserSwitches, true);
+                numberOfUserSwitches, true);
         // running: user 0, USER_ID1
         // stopped + unlocked: USER_ID
-        numerOfUserSwitches++;
+        numberOfUserSwitches++;
         assertTrue(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1}),
                 mUserController.getRunningUsersLU());
@@ -659,7 +662,7 @@
                 .lockCeStorage(anyInt());
 
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
-                numerOfUserSwitches, true);
+                numberOfUserSwitches, true);
         // running: user 0, USER_ID2
         // stopped + unlocked: USER_ID1
         // stopped + locked: USER_ID
@@ -675,6 +678,105 @@
     }
 
     /**
+     * Test that, in getRunningUsersLU, parents come after their profile, even if the profile was
+     * started afterwards.
+     */
+    @Test
+    public void testRunningUsersListOrder_parentAfterProfile() {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+
+        final int PARENT_ID = 200;
+        final int PROFILE1_ID = 201;
+        final int PROFILE2_ID = 202;
+        final int FG_USER_ID = 300;
+        final int BG_USER_ID = 400;
+
+        setUpUser(PARENT_ID, 0).profileGroupId = PARENT_ID;
+        setUpUser(PROFILE1_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+        setUpUser(PROFILE2_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+        setUpUser(FG_USER_ID, 0).profileGroupId = FG_USER_ID;
+        setUpUser(BG_USER_ID, 0).profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
+        mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
+
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID}),
+                mUserController.getRunningUsersLU());
+
+        int numberOfUserSwitches = 1;
+        addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
+                numberOfUserSwitches, false);
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID, PARENT_ID}),
+                mUserController.getRunningUsersLU());
+
+        assertThat(mUserController.startProfile(PROFILE1_ID, true, null)).isTrue();
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID}),
+                mUserController.getRunningUsersLU());
+
+        numberOfUserSwitches++;
+        addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
+                numberOfUserSwitches, false);
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID}),
+                mUserController.getRunningUsersLU());
+
+        mUserController.startUser(BG_USER_ID, USER_START_MODE_BACKGROUND);
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, BG_USER_ID, FG_USER_ID}),
+                mUserController.getRunningUsersLU());
+
+        assertThat(mUserController.startProfile(PROFILE2_ID, true, null)).isTrue();
+        // Note for the future:
+        // It is not absolutely essential that PROFILE1 come before PROFILE2,
+        // nor that PROFILE1 come before BG_USER. We can change that policy later if we'd like.
+        // The important thing is that PROFILE1 and PROFILE2 precede PARENT,
+        // and that everything precedes OTHER.
+        assertEquals(Arrays.asList(new Integer[] {
+                SYSTEM_USER_ID, PROFILE1_ID, BG_USER_ID, PROFILE2_ID, PARENT_ID, FG_USER_ID}),
+                mUserController.getRunningUsersLU());
+    }
+
+    /**
+     * Test that, in getRunningUsersLU, the current user is always at the end, even if background
+     * users were started subsequently.
+     */
+    @Test
+    public void testRunningUsersListOrder_currentAtEnd() {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+
+        final int CURRENT_ID = 200;
+        final int PROFILE_ID = 201;
+        final int BG_USER_ID = 400;
+
+        setUpUser(CURRENT_ID, 0).profileGroupId = CURRENT_ID;
+        setUpUser(PROFILE_ID, UserInfo.FLAG_PROFILE).profileGroupId = CURRENT_ID;
+        setUpUser(BG_USER_ID, 0).profileGroupId = BG_USER_ID;
+        mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
+
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID}),
+                mUserController.getRunningUsersLU());
+
+        addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false);
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID, CURRENT_ID}),
+                mUserController.getRunningUsersLU());
+
+        mUserController.startUser(BG_USER_ID, USER_START_MODE_BACKGROUND);
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID, BG_USER_ID, CURRENT_ID}),
+                mUserController.getRunningUsersLU());
+
+        assertThat(mUserController.startProfile(PROFILE_ID, true, null)).isTrue();
+        assertEquals(Arrays.asList(
+                new Integer[] {SYSTEM_USER_ID, BG_USER_ID, PROFILE_ID, CURRENT_ID}),
+                mUserController.getRunningUsersLU());
+    }
+
+    /**
      * Test locking user with mDelayUserDataLocking false.
      */
     @Test
@@ -785,6 +887,52 @@
         verifyUserUnassignedFromDisplayNeverCalled(TEST_USER_ID);
     }
 
+    /** Test that stopping a profile doesn't also stop its parent, even if it's in background. */
+    @Test
+    public void testStopProfile_doesNotStopItsParent() throws Exception {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+
+        final Range<Integer> RUNNING_RANGE =
+                Range.closed(UserState.STATE_BOOTING, UserState.STATE_RUNNING_UNLOCKED);
+
+        final int PARENT_ID = TEST_USER_ID1;
+        final int PROFILE_ID = TEST_USER_ID2;
+        final int OTHER_ID = TEST_USER_ID3;
+
+        setUpUser(PARENT_ID, 0).profileGroupId = PARENT_ID;
+        setUpUser(PROFILE_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+        setUpUser(OTHER_ID, 0).profileGroupId = OTHER_ID;
+        mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
+
+        // Start the parent in the background
+        boolean started = mUserController.startUser(PARENT_ID, USER_START_MODE_BACKGROUND);
+        assertWithMessage("startUser(%s)", PARENT_ID).that(started).isTrue();
+        assertThat(mUserController.getStartedUserState(PARENT_ID).state).isIn(RUNNING_RANGE);
+
+        // Start the profile
+        started = mUserController.startProfile(PROFILE_ID, true, null);
+        assertWithMessage("startProfile(%s)", PROFILE_ID).that(started).isTrue();
+        assertThat(mUserController.getStartedUserState(PARENT_ID).state).isIn(RUNNING_RANGE);
+        assertThat(mUserController.getStartedUserState(PROFILE_ID).state).isIn(RUNNING_RANGE);
+
+        // Start an unrelated user
+        started = mUserController.startUser(OTHER_ID, USER_START_MODE_FOREGROUND);
+        assertWithMessage("startUser(%s)", OTHER_ID).that(started).isTrue();
+        assertThat(mUserController.getStartedUserState(PARENT_ID).state).isIn(RUNNING_RANGE);
+        assertThat(mUserController.getStartedUserState(PROFILE_ID).state).isIn(RUNNING_RANGE);
+        assertThat(mUserController.getStartedUserState(OTHER_ID).state).isIn(RUNNING_RANGE);
+
+        // Stop the profile and assert that its (background) parent didn't stop too
+        boolean stopped = mUserController.stopProfile(PROFILE_ID);
+        assertWithMessage("stopProfile(%s)", PROFILE_ID).that(stopped).isTrue();
+        if (mUserController.getStartedUserState(PROFILE_ID) != null) {
+            assertThat(mUserController.getStartedUserState(PROFILE_ID).state)
+                    .isNotIn(RUNNING_RANGE);
+        }
+        assertThat(mUserController.getStartedUserState(PARENT_ID).state).isIn(RUNNING_RANGE);
+    }
+
     @Test
     public void testStartProfile_disabledProfileFails() {
         setUpUser(TEST_USER_ID1, UserInfo.FLAG_PROFILE | UserInfo.FLAG_DISABLED, /* preCreated= */
@@ -1152,11 +1300,11 @@
         continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
     }
 
-    private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
-        setUpUser(userId, flags, /* preCreated= */ false, /* userType */ null);
+    private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
+        return setUpUser(userId, flags, /* preCreated= */ false, /* userType */ null);
     }
 
-    private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags, boolean preCreated,
+    private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags, boolean preCreated,
             @Nullable String userType) {
         if (userType == null) {
             userType = UserInfo.getDefaultUserType(flags);
@@ -1171,6 +1319,12 @@
         assertThat(userTypeDetails).isNotNull();
         when(mInjector.mUserManagerInternalMock.getUserProperties(eq(userId)))
                 .thenReturn(userTypeDetails.getDefaultUserPropertiesReference());
+
+        mUserInfos.put(userId, userInfo);
+        when(mInjector.mUserManagerMock.getUsers(anyBoolean()))
+                .thenReturn(mUserInfos.values().stream().toList());
+
+        return userInfo;
     }
 
     private static List<String> getActions(List<Intent> intents) {