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) {