summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java7
-rw-r--r--core/java/android/print/PrintManager.java7
-rw-r--r--core/java/android/view/InsetsController.java16
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt16
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt44
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt4
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml20
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/res/xml/qs_header.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java70
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt109
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt243
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt278
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt153
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt39
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java6
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java11
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java6
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java70
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/core/java/com/android/server/pm/ResilientAtomicFile.java4
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/AppWarnings.java9
-rw-r--r--services/core/java/com/android/server/wm/DeviceStateController.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java4
-rw-r--r--services/print/java/com/android/server/print/UserState.java8
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java28
-rw-r--r--services/tests/uiservicestests/AndroidManifest.xml2
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java186
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java1
-rw-r--r--tests/InputMethodStressTest/AndroidTest.xml1
56 files changed, 1517 insertions, 288 deletions
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 7be00a045403..3487e0b1f3e8 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -827,6 +827,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public static final int PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS = 1 << 4;
+ /**
+ * Whether AbiOverride was used when installing this application.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5;
+
/** @hide */
@IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
PRIVATE_FLAG_EXT_PROFILEABLE,
@@ -834,6 +840,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE,
PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK,
PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
+ PRIVATE_FLAG_EXT_CPU_OVERRIDE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationInfoPrivateFlagsExt {}
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 931adb55a686..ef274a56e1d3 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -23,6 +23,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.Application.ActivityLifecycleCallbacks;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -535,7 +536,11 @@ public final class PrintManager {
return null;
}
try {
- mContext.startIntentSender(intent, null, 0, 0, 0);
+ ActivityOptions activityOptions = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ mContext.startIntentSender(intent, null, 0, 0, 0,
+ activityOptions.toBundle());
return new PrintJob(printJob, this);
} catch (SendIntentException sie) {
Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7e4e4022f00f..5019b85ca503 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1099,21 +1099,25 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// TODO: Support a ResultReceiver for IME.
// TODO(b/123718661): Make show() work for multi-session IME.
int typesReady = 0;
+ final boolean imeVisible = mState.isSourceOrDefaultVisible(
+ mImeSourceConsumer.getId(), ime());
for (int type = FIRST; type <= LAST; type = type << 1) {
if ((types & type) == 0) {
continue;
}
@AnimationType final int animationType = getAnimationType(type);
final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
- final boolean isImeAnimation = type == ime();
- if (requestedVisible && animationType == ANIMATION_TYPE_NONE
- || animationType == ANIMATION_TYPE_SHOW) {
+ final boolean isIme = type == ime();
+ var alreadyVisible = requestedVisible && (!isIme || imeVisible)
+ && animationType == ANIMATION_TYPE_NONE;
+ var alreadyAnimatingShow = animationType == ANIMATION_TYPE_SHOW;
+ if (alreadyVisible || alreadyAnimatingShow) {
// no-op: already shown or animating in (because window visibility is
// applied before starting animation).
if (DEBUG) Log.d(TAG, String.format(
"show ignored for type: %d animType: %d requestedVisible: %s",
type, animationType, requestedVisible));
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onCancelled(statsToken,
ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
@@ -1121,13 +1125,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
if (fromIme && animationType == ANIMATION_TYPE_USER) {
// App is already controlling the IME, don't cancel it.
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onFailed(
statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
}
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onProgress(
statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 3d95dd341cc0..c9e76009136a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,6 +81,10 @@ public class SystemUiSystemPropertiesFlags {
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
+
+ /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
+ public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
+ devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
}
//// == End of flags. Everything below this line is the implementation. == ////
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 89871fa7d875..2cd587ffbc45 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
@@ -56,7 +56,19 @@ data class TurbulenceNoiseAnimationConfig(
val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
val pixelDensity: Float = 1f,
val blendMode: BlendMode = DEFAULT_BLEND_MODE,
- val onAnimationEnd: Runnable? = null
+ val onAnimationEnd: Runnable? = null,
+ /**
+ * Variants in noise. Higher number means more contrast; lower number means less contrast but
+ * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate.
+ * Expected range [0, 1].
+ */
+ val lumaMatteBlendFactor: Float = DEFAULT_LUMA_MATTE_BLEND_FACTOR,
+ /**
+ * Offset for the overall brightness in noise. Higher number makes the noise brighter. You may
+ * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected
+ * range [0, 1].
+ */
+ val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS
) {
companion object {
const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -66,6 +78,8 @@ data class TurbulenceNoiseAnimationConfig(
const val DEFAULT_NOISE_SPEED_Z = 0.3f
const val DEFAULT_OPACITY = 150 // full opacity is 255.
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
val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
}
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 d1ba7c4de35c..d3c57c91405a 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
@@ -37,6 +37,8 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uniform float in_opacity;
uniform float in_pixelDensity;
uniform float in_inverseLuma;
+ uniform half in_lumaMatteBlendFactor;
+ uniform half in_lumaMatteOverallBrightness;
layout(color) uniform vec4 in_color;
layout(color) uniform vec4 in_backgroundColor;
"""
@@ -48,18 +50,21 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- float luma = abs(in_inverseLuma - simplex3d(noiseP)) * in_opacity;
+ // 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;
- // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+ // 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.;
// 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_color.a;
- return vec4(color, in_color.a);
+ color = (color + dither.rrr) * in_opacity;
+ return vec4(color, in_opacity);
}
"""
@@ -70,12 +75,15 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- float luma = abs(in_inverseLuma - simplex3d_fractal(noiseP)) * in_opacity;
+ // 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;
// Skip dithering.
- return vec4(color * in_color.a, in_color.a);
+ return vec4(color * in_opacity, in_opacity);
}
"""
@@ -125,6 +133,28 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
}
/**
+ * Sets blend and brightness factors of the luma matte.
+ *
+ * @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.
+ * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise.
+ * Expected input range is [0, 1].
+ *
+ * Example usage: You may want to apply a small number to [lumaMatteBlendFactor], such as 0.2,
+ * which makes the noise look softer. However it makes the overall noise look dim, so you want
+ * offset something like 0.3 for [lumaMatteOverallBrightness] to bring back its overall
+ * brightness.
+ */
+ fun setLumaMatteFactors(
+ lumaMatteBlendFactor: Float = 1f,
+ lumaMatteOverallBrightness: Float = 0f
+ ) {
+ setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor)
+ setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness)
+ }
+
+ /**
* Sets whether to inverse the luminosity of the noise.
*
* By default noise will be used as a luma matte as is. This means that you will see color in
@@ -132,7 +162,7 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
* true.
*/
fun setInverseNoiseLuminosity(inverse: Boolean) {
- setFloatUniform("in_inverseLuma", if (inverse) 1f else 0f)
+ setFloatUniform("in_inverseLuma", if (inverse) -1f else 1f)
}
/** Current noise movements in x, y, and z axes. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index c3e84787d4fb..43d6504fce84 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -215,10 +215,12 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
noiseConfig = config
with(turbulenceNoiseShader) {
setGridCount(config.gridCount)
- setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+ setColor(config.color)
setBackgroundColor(config.backgroundColor)
setSize(config.width, config.height)
setPixelDensity(config.pixelDensity)
+ setInverseNoiseLuminosity(inverse = false)
+ setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
}
paint.blendMode = config.blendMode
}
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
index a8f0cc3a1d92..4a9d41fae1d5 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
@@ -14,20 +14,6 @@ Copyright (C) 2015 The Android Open Source Project
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="3dp"
- android:insetRight="3dp">
- <vector android:width="18dp"
- android:height="18dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M16,16L2.81,2.81L1.39,4.22l4.85,4.85C6.09,9.68 6,10.33 6,11v6H4v2h12.17l3.61,3.61l1.41,-1.41L16,16zM8,17c0,0 0.01,-6.11 0.01,-6.16L14.17,17H8z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,6.5c2.49,0 4,2.02 4,4.5v2.17l2,2V11c0,-3.07 -1.63,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5S10.5,3.17 10.5,4v0.68C9.72,4.86 9.05,5.2 8.46,5.63L9.93,7.1C10.51,6.73 11.2,6.5 12,6.5z"/>
- </vector>
-</inset>
+ android:insetLeft="3dp"
+ android:insetRight="3dp"
+ android:drawable="@drawable/ic_speaker_mute" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8d3ba364da06..4f768cc39b40 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -572,6 +572,7 @@
<dimen name="qs_brightness_margin_bottom">16dp</dimen>
<dimen name="qqs_layout_margin_top">16dp</dimen>
<dimen name="qqs_layout_padding_bottom">24dp</dimen>
+ <item name="qqs_expand_clock_scale" format="float" type="dimen">2.57</item>
<!-- Most of the time it should be the same as notification_side_paddings as it's vertically
aligned with notifications. The exception is split shade when this value becomes
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b48296fe54be..58be3e9d4e2f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2724,8 +2724,8 @@
<string name="media_output_broadcast_last_update_error">Can\u2019t save.</string>
<!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] -->
<string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string>
- <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] -->
- <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string>
+ <!-- The hint message when Broadcast edit is more than 16/254 characters [CHAR LIMIT=60] -->
+ <string name="media_output_broadcast_edit_hint_no_more_than_max">Use fewer than <xliff:g id="length" example="16">%1$d</xliff:g> characters</string>
<!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
<string name="build_number_clip_data_label">Build number</string>
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 52a98984e6e2..8039c68485ca 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -43,8 +43,8 @@
app:layout_constraintBottom_toBottomOf="@id/carrier_group"
/>
<Transform
- android:scaleX="2.57"
- android:scaleY="2.57"
+ android:scaleX="@dimen/qqs_expand_clock_scale"
+ android:scaleY="@dimen/qqs_expand_clock_scale"
/>
</Constraint>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index f86e2ed6eb91..7f706859abb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -614,7 +614,11 @@ public class AuthContainerView extends LinearLayout
return ((AuthBiometricFingerprintView) view).isUdfps();
}
if (view instanceof BiometricPromptLayout) {
- return ((BiometricPromptLayout) view).isUdfps();
+ // this will force the prompt to align itself on the edge of the screen
+ // instead of centering (temporary workaround to prevent small implicit view
+ // from breaking due to the way gravity / margins are set in the legacy
+ // AuthPanelController
+ return true;
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index baaa96efb5f0..d48b9c339d15 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -35,6 +35,7 @@ import android.os.Handler
import android.util.Log
import android.util.RotationUtils
import android.view.Display
+import android.view.DisplayInfo
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
@@ -58,6 +59,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
import java.io.PrintWriter
@@ -129,6 +131,8 @@ constructor(
}
@VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
+ private val displayInfo = DisplayInfo()
+
private val overlayViewParams =
WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
@@ -214,6 +218,23 @@ constructor(
for (requestSource in requests) {
pw.println(" $requestSource.name")
}
+
+ pw.println("overlayView:")
+ pw.println(" width=${overlayView?.width}")
+ pw.println(" height=${overlayView?.height}")
+ pw.println(" boundsOnScreen=${overlayView?.boundsOnScreen}")
+
+ pw.println("displayStateInteractor:")
+ pw.println(" isInRearDisplayMode=${displayStateInteractor?.isInRearDisplayMode?.value}")
+
+ pw.println("sensorProps:")
+ pw.println(" displayId=${displayInfo.uniqueId}")
+ pw.println(" sensorType=${sensorProps?.sensorType}")
+ pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+
+ pw.println("overlayOffsets=$overlayOffsets")
+ pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
+ pw.println("currentRotation=${displayInfo.rotation}")
}
private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) {
@@ -226,6 +247,8 @@ constructor(
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
overlayView = view
val display = context.display!!
+ // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
val offsets =
sensorProps.getLocation(display.uniqueId).let { location ->
if (location == null) {
@@ -239,12 +262,12 @@ constructor(
view.rotation =
display.asSideFpsAnimationRotation(
offsets.isYAligned(),
- getRotationFromDefault(display.rotation)
+ getRotationFromDefault(displayInfo.rotation)
)
lottie.setAnimation(
display.asSideFpsAnimation(
offsets.isYAligned(),
- getRotationFromDefault(display.rotation)
+ getRotationFromDefault(displayInfo.rotation)
)
)
lottie.addLottieOnCompositionLoadedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index e4c4e9aedb56..1dffa80a084f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,8 +19,11 @@ package com.android.systemui.biometrics.ui.binder
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
+import android.view.Surface
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import androidx.core.animation.addListener
@@ -52,7 +55,9 @@ object BiometricViewSizeBinder {
panelViewController: AuthPanelController,
jankListener: BiometricJankListener,
) {
- val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
+ val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
+ val accessibilityManager =
+ requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
fun notifyAccessibilityChanged() {
Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
}
@@ -102,15 +107,26 @@ object BiometricViewSizeBinder {
when {
size.isSmall -> {
iconHolderView.alpha = 1f
+ val bottomInset =
+ windowManager.maximumWindowMetrics.windowInsets
+ .getInsets(WindowInsets.Type.navigationBars())
+ .bottom
iconHolderView.y =
- view.height - iconHolderView.height - iconPadding
+ if (view.isLandscape()) {
+ (view.height - iconHolderView.height - bottomInset) / 2f
+ } else {
+ view.height -
+ iconHolderView.height -
+ iconPadding -
+ bottomInset
+ }
val newHeight =
- iconHolderView.height + 2 * iconPadding.toInt() -
+ iconHolderView.height + (2 * iconPadding.toInt()) -
iconHolderView.paddingTop -
iconHolderView.paddingBottom
panelViewController.updateForContentDimensions(
width,
- newHeight,
+ newHeight + bottomInset,
0, /* animateDurationMs */
)
}
@@ -181,6 +197,11 @@ object BiometricViewSizeBinder {
}
}
+private fun View.isLandscape(): Boolean {
+ val r = context.display.rotation
+ return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
+}
+
private fun TextView.showTextOrHide(forceHide: Boolean = false) {
visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 44e74e7e339b..9db3c22dff84 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -63,7 +63,7 @@ constructor(
val hasCards = response?.walletCards?.isNotEmpty() == true
trySendWithFailureLogging(
state(
- isFeatureEnabled = walletController.isWalletEnabled,
+ isFeatureEnabled = isWalletAvailable(),
hasCard = hasCards,
tileIcon = walletController.walletClient.tileIcon,
),
@@ -100,7 +100,7 @@ constructor(
return when {
!walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- !walletController.isWalletEnabled || queryCards().isEmpty() -> {
+ !isWalletAvailable() || queryCards().isEmpty() -> {
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
instructions =
listOf(
@@ -146,6 +146,11 @@ constructor(
}
}
+ private fun isWalletAvailable() =
+ with(walletController.walletClient) {
+ isWalletServiceAvailable && isWalletFeatureAvailable
+ }
+
private fun state(
isFeatureEnabled: Boolean,
hasCard: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 516fbf5ca12c..14386c1c0fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -76,7 +76,6 @@ import androidx.constraintlayout.widget.ConstraintSet;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.internal.widget.CachingIconView;
@@ -1211,24 +1210,24 @@ public class MediaControlPanel {
private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
return new TurbulenceNoiseAnimationConfig(
- TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+ /* gridCount= */ 2.14f,
TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
- /* noiseMoveSpeedX= */ 0f,
+ /* noiseMoveSpeedX= */ 0.42f,
/* noiseMoveSpeedY= */ 0f,
TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
/* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
- // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
- // Thus, set the background color with alpha 0.
- /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
- TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+ /* backgroundColor= */ Color.BLACK,
+ /* opacity= */ 51,
/* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
/* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
- /* easeInDuration= */ 2500f,
- /* easeOutDuration= */ 2500f,
+ /* easeInDuration= */ 1350f,
+ /* easeOutDuration= */ 1350f,
this.getContext().getResources().getDisplayMetrics().density,
- BlendMode.PLUS,
- /* onAnimationEnd= */ null
+ BlendMode.SCREEN,
+ /* onAnimationEnd= */ null,
+ /* lumaMatteBlendFactor= */ 0.26f,
+ /* lumaMatteOverallBrightness= */ 0.09f
);
}
private void clearButton(final ImageButton button) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 529b980a6e38..b4578e97eda2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -59,6 +59,17 @@ import com.google.zxing.WriterException;
public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private static final String TAG = "MediaOutputBroadcastDialog";
+ static final int METADATA_BROADCAST_NAME = 0;
+ static final int METADATA_BROADCAST_CODE = 1;
+
+ private static final int MAX_BROADCAST_INFO_UPDATE = 3;
+ @VisibleForTesting
+ static final int BROADCAST_CODE_MAX_LENGTH = 16;
+ @VisibleForTesting
+ static final int BROADCAST_CODE_MIN_LENGTH = 4;
+ @VisibleForTesting
+ static final int BROADCAST_NAME_MAX_LENGTH = 254;
+
private ViewStub mBroadcastInfoArea;
private ImageView mBroadcastQrCodeView;
private ImageView mBroadcastNotify;
@@ -68,14 +79,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private ImageView mBroadcastCodeEye;
private Boolean mIsPasswordHide = true;
private ImageView mBroadcastCodeEdit;
- private AlertDialog mAlertDialog;
+ @VisibleForTesting
+ AlertDialog mAlertDialog;
private TextView mBroadcastErrorMessage;
private int mRetryCount = 0;
private String mCurrentBroadcastName;
private String mCurrentBroadcastCode;
private boolean mIsStopbyUpdateBroadcastCode = false;
+ private boolean mIsLeBroadcastAssistantCallbackRegistered;
- private TextWatcher mTextWatcher = new TextWatcher() {
+ private TextWatcher mBroadcastCodeTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
@@ -103,7 +116,9 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
R.string.media_output_broadcast_code_hint_no_less_than_min);
} else if (breakBroadcastCodeRuleTextLengthMoreThanMax) {
mBroadcastErrorMessage.setText(
- R.string.media_output_broadcast_code_hint_no_more_than_max);
+ mContext.getResources().getString(
+ R.string.media_output_broadcast_edit_hint_no_more_than_max,
+ BROADCAST_CODE_MAX_LENGTH));
}
mBroadcastErrorMessage.setVisibility(breakRule ? View.VISIBLE : View.INVISIBLE);
@@ -114,7 +129,40 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
};
- private boolean mIsLeBroadcastAssistantCallbackRegistered;
+ private TextWatcher mBroadcastNameTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (mAlertDialog == null || mBroadcastErrorMessage == null) {
+ return;
+ }
+ boolean breakBroadcastNameRuleTextLengthMoreThanMax =
+ s.length() > BROADCAST_NAME_MAX_LENGTH;
+ boolean breakRule = breakBroadcastNameRuleTextLengthMoreThanMax || (s.length() == 0);
+
+ if (breakBroadcastNameRuleTextLengthMoreThanMax) {
+ mBroadcastErrorMessage.setText(
+ mContext.getResources().getString(
+ R.string.media_output_broadcast_edit_hint_no_more_than_max,
+ BROADCAST_NAME_MAX_LENGTH));
+ }
+ mBroadcastErrorMessage.setVisibility(
+ breakBroadcastNameRuleTextLengthMoreThanMax ? View.VISIBLE : View.INVISIBLE);
+ Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (positiveBtn != null) {
+ positiveBtn.setEnabled(breakRule ? false : true);
+ }
+ }
+ };
private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@@ -187,13 +235,6 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
};
- static final int METADATA_BROADCAST_NAME = 0;
- static final int METADATA_BROADCAST_CODE = 1;
-
- private static final int MAX_BROADCAST_INFO_UPDATE = 3;
- private static final int BROADCAST_CODE_MAX_LENGTH = 16;
- private static final int BROADCAST_CODE_MIN_LENGTH = 4;
-
MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
super(context, broadcastSender, mediaOutputController);
@@ -392,13 +433,12 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
R.layout.media_output_broadcast_update_dialog, null);
final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
editText.setText(editString);
- if (isBroadcastCode) {
- editText.addTextChangedListener(mTextWatcher);
- }
+ editText.addTextChangedListener(
+ isBroadcastCode ? mBroadcastCodeTextWatcher : mBroadcastNameTextWatcher);
mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message);
mAlertDialog = new Builder(mContext)
.setTitle(isBroadcastCode ? R.string.media_output_broadcast_code
- : R.string.media_output_broadcast_name)
+ : R.string.media_output_broadcast_name)
.setView(layout)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.media_output_broadcast_dialog_save,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 897b0e73dca0..5d028307a62d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -65,14 +65,12 @@ import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.DisplayTracker;
-import dagger.Lazy;
-
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
-
+import dagger.Lazy;
public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
public static final String PREFIX = "custom(";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index a066242fd96b..d7ae575724dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -20,6 +20,8 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -45,6 +47,11 @@ abstract class QSPipelineModule {
): CurrentTilesInteractor
@Binds
+ abstract fun provideInstalledTilesPackageRepository(
+ impl: InstalledTilesComponentRepositoryImpl
+ ): InstalledTilesComponentRepository
+
+ @Binds
@IntoMap
@ClassKey(PrototypeCoreStartable::class)
abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
new file mode 100644
index 000000000000..498f403e8c7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 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.qs.pipeline.data.repository
+
+import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+import android.annotation.WorkerThread
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.os.UserHandle
+import android.service.quicksettings.TileService
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.isComponentActuallyEnabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+interface InstalledTilesComponentRepository {
+
+ fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>>
+}
+
+@SysUISingleton
+class InstalledTilesComponentRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val packageManager: PackageManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : InstalledTilesComponentRepository {
+
+ override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
+ conflatedCallbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ trySend(Unit)
+ }
+ }
+ applicationContext.registerReceiverAsUser(
+ receiver,
+ UserHandle.of(userId),
+ INTENT_FILTER,
+ /* broadcastPermission = */ null,
+ /* scheduler = */ null
+ )
+
+ awaitClose { applicationContext.unregisterReceiver(receiver) }
+ }
+ .onStart { emit(Unit) }
+ .map { reloadComponents(userId) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ @WorkerThread
+ private fun reloadComponents(userId: Int): Set<ComponentName> {
+ return packageManager
+ .queryIntentServicesAsUser(INTENT, FLAGS, userId)
+ .mapNotNull { it.serviceInfo }
+ .filter { it.permission == BIND_QUICK_SETTINGS_TILE }
+ .filter { packageManager.isComponentActuallyEnabled(it) }
+ .mapTo(mutableSetOf()) { it.componentName }
+ }
+
+ companion object {
+ private val INTENT_FILTER =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_PACKAGE_ADDED)
+ addAction(Intent.ACTION_PACKAGE_CHANGED)
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
+ addAction(Intent.ACTION_PACKAGE_REPLACED)
+ addDataScheme("package")
+ }
+ private val INTENT = Intent(TileService.ACTION_QS_TILE)
+ private val FLAGS =
+ ResolveInfoFlags.of(
+ (PackageManager.GET_SERVICES or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
+ .toLong()
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 3b2362f2b326..a162d113a3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -42,6 +42,8 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
/** Repository that tracks the current tiles. */
@@ -104,6 +106,8 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : TileSpecRepository {
+ private val mutex = Mutex()
+
private val retailModeTiles by lazy {
resources
.getString(R.string.quick_settings_tiles_retail_mode)
@@ -145,37 +149,40 @@ constructor(
.flowOn(backgroundDispatcher)
}
- override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
- if (tile == TileSpec.Invalid) {
- return
- }
- val tilesList = loadTiles(userId).toMutableList()
- if (tile !in tilesList) {
- if (position < 0 || position >= tilesList.size) {
- tilesList.add(tile)
- } else {
- tilesList.add(position, tile)
+ override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) =
+ mutex.withLock {
+ if (tile == TileSpec.Invalid) {
+ return
+ }
+ val tilesList = loadTiles(userId).toMutableList()
+ if (tile !in tilesList) {
+ if (position < 0 || position >= tilesList.size) {
+ tilesList.add(tile)
+ } else {
+ tilesList.add(position, tile)
+ }
+ storeTiles(userId, tilesList)
}
- storeTiles(userId, tilesList)
}
- }
- override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
- if (tiles.all { it == TileSpec.Invalid }) {
- return
- }
- val tilesList = loadTiles(userId).toMutableList()
- if (tilesList.removeAll(tiles)) {
- storeTiles(userId, tilesList.toList())
+ override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) =
+ mutex.withLock {
+ if (tiles.all { it == TileSpec.Invalid }) {
+ return
+ }
+ val tilesList = loadTiles(userId).toMutableList()
+ if (tilesList.removeAll(tiles)) {
+ storeTiles(userId, tilesList.toList())
+ }
}
- }
- override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
- val filtered = tiles.filter { it != TileSpec.Invalid }
- if (filtered.isNotEmpty()) {
- storeTiles(userId, filtered)
+ override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) =
+ mutex.withLock {
+ val filtered = tiles.filter { it != TileSpec.Invalid }
+ if (filtered.isNotEmpty()) {
+ storeTiles(userId, filtered)
+ }
}
- }
private suspend fun loadTiles(@UserIdInt forUser: Int): List<TileSpec> {
return withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index c579f5c3061c..ff881f767b87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -36,6 +36,7 @@ import com.android.systemui.qs.external.CustomTileStatePersister
import com.android.systemui.qs.external.TileLifecycleManager
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -52,6 +53,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
@@ -117,11 +120,13 @@ interface CurrentTilesInteractor : ProtoDumpable {
* * Platform tiles will be kept between users, with a call to [QSTile.userSwitch]
* * [CustomTile]s will only be destroyed if the user changes.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CurrentTilesInteractorImpl
@Inject
constructor(
private val tileSpecRepository: TileSpecRepository,
+ private val installedTilesComponentRepository: InstalledTilesComponentRepository,
private val userRepository: UserRepository,
private val customTileStatePersister: CustomTileStatePersister,
private val tileFactory: QSFactory,
@@ -141,7 +146,7 @@ constructor(
override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow()
// This variable should only be accessed inside the collect of `startTileCollection`.
- private val specsToTiles = mutableMapOf<TileSpec, QSTile>()
+ private val specsToTiles = mutableMapOf<TileSpec, TileOrNotInstalled>()
private val currentUser = MutableStateFlow(userTracker.userId)
override val userId = currentUser.asStateFlow()
@@ -149,6 +154,20 @@ constructor(
private val _userContext = MutableStateFlow(userTracker.userContext)
override val userContext = _userContext.asStateFlow()
+ private val userAndTiles =
+ currentUser
+ .flatMapLatest { userId ->
+ tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) }
+ }
+ .distinctUntilChanged()
+ .pairwise(UserAndTiles(-1, emptyList()))
+ .flowOn(backgroundDispatcher)
+
+ private val installedPackagesWithTiles =
+ currentUser.flatMapLatest {
+ installedTilesComponentRepository.getInstalledTilesComponents(it)
+ }
+
init {
if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
startTileCollection()
@@ -158,68 +177,98 @@ constructor(
@OptIn(ExperimentalCoroutinesApi::class)
private fun startTileCollection() {
scope.launch {
- userRepository.selectedUserInfo
- .flatMapLatest { user ->
+ launch {
+ userRepository.selectedUserInfo.collect { user ->
currentUser.value = user.id
_userContext.value = userTracker.userContext
- tileSpecRepository.tilesSpecs(user.id).map { user.id to it }
}
- .distinctUntilChanged()
- .pairwise(-1 to emptyList())
- .flowOn(backgroundDispatcher)
- .collect { (old, new) ->
- val newTileList = new.second
- val userChanged = old.first != new.first
- val newUser = new.first
-
- // Destroy all tiles that are not in the new set
- specsToTiles
- .filter { it.key !in newTileList }
- .forEach { entry ->
- logger.logTileDestroyed(
- entry.key,
- if (userChanged) {
- QSPipelineLogger.TileDestroyedReason
- .TILE_NOT_PRESENT_IN_NEW_USER
- } else {
- QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
- }
- )
- entry.value.destroy()
- }
- // MutableMap will keep the insertion order
- val newTileMap = mutableMapOf<TileSpec, QSTile>()
-
- newTileList.forEach { tileSpec ->
- if (tileSpec !in newTileMap) {
- val newTile =
- if (tileSpec in specsToTiles) {
- processExistingTile(
- tileSpec,
- specsToTiles.getValue(tileSpec),
- userChanged,
- newUser
- )
- ?: createTile(tileSpec)
+ }
+
+ launch(backgroundDispatcher) {
+ userAndTiles
+ .combine(installedPackagesWithTiles) { usersAndTiles, packages ->
+ Data(
+ usersAndTiles.previousValue,
+ usersAndTiles.newValue,
+ packages,
+ )
+ }
+ .collectLatest {
+ val newTileList = it.newData.tiles
+ val userChanged = it.oldData.userId != it.newData.userId
+ val newUser = it.newData.userId
+ val components = it.installedComponents
+
+ // Destroy all tiles that are not in the new set
+ specsToTiles
+ .filter {
+ it.key !in newTileList && it.value is TileOrNotInstalled.Tile
+ }
+ .forEach { entry ->
+ logger.logTileDestroyed(
+ entry.key,
+ if (userChanged) {
+ QSPipelineLogger.TileDestroyedReason
+ .TILE_NOT_PRESENT_IN_NEW_USER
+ } else {
+ QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+ }
+ )
+ (entry.value as TileOrNotInstalled.Tile).tile.destroy()
+ }
+ // MutableMap will keep the insertion order
+ val newTileMap = mutableMapOf<TileSpec, TileOrNotInstalled>()
+
+ newTileList.forEach { tileSpec ->
+ if (tileSpec !in newTileMap) {
+ if (
+ tileSpec is TileSpec.CustomTileSpec &&
+ tileSpec.componentName !in components
+ ) {
+ newTileMap[tileSpec] = TileOrNotInstalled.NotInstalled
} else {
- createTile(tileSpec)
+ // Create tile here will never try to create a CustomTile that
+ // is not installed
+ val newTile =
+ if (tileSpec in specsToTiles) {
+ processExistingTile(
+ tileSpec,
+ specsToTiles.getValue(tileSpec),
+ userChanged,
+ newUser
+ )
+ ?: createTile(tileSpec)
+ } else {
+ createTile(tileSpec)
+ }
+ if (newTile != null) {
+ newTileMap[tileSpec] = TileOrNotInstalled.Tile(newTile)
+ }
}
- if (newTile != null) {
- newTileMap[tileSpec] = newTile
}
}
- }
- val resolvedSpecs = newTileMap.keys.toList()
- specsToTiles.clear()
- specsToTiles.putAll(newTileMap)
- _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) }
- if (resolvedSpecs != newTileList) {
- // There were some tiles that couldn't be created. Change the value in the
- // repository
- launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ val resolvedSpecs = newTileMap.keys.toList()
+ specsToTiles.clear()
+ specsToTiles.putAll(newTileMap)
+ _currentSpecsAndTiles.value =
+ newTileMap
+ .filter { it.value is TileOrNotInstalled.Tile }
+ .map {
+ TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
+ }
+ logger.logTilesNotInstalled(
+ newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
+ newUser
+ )
+ if (resolvedSpecs != newTileList) {
+ // There were some tiles that couldn't be created. Change the value in
+ // the
+ // repository
+ launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ }
}
- }
+ }
}
}
@@ -301,42 +350,66 @@ constructor(
private fun processExistingTile(
tileSpec: TileSpec,
- qsTile: QSTile,
+ tileOrNotInstalled: TileOrNotInstalled,
userChanged: Boolean,
user: Int,
): QSTile? {
- return when {
- !qsTile.isAvailable -> {
- logger.logTileDestroyed(
- tileSpec,
- QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
- )
- qsTile.destroy()
- null
- }
- // Tile is in the current list of tiles and available.
- // We have a handful of different cases
- qsTile !is CustomTile -> {
- // The tile is not a custom tile. Make sure they are reset to the correct user
- if (userChanged) {
- qsTile.userSwitch(user)
- logger.logTileUserChanged(tileSpec, user)
+ return when (tileOrNotInstalled) {
+ is TileOrNotInstalled.NotInstalled -> null
+ is TileOrNotInstalled.Tile -> {
+ val qsTile = tileOrNotInstalled.tile
+ when {
+ !qsTile.isAvailable -> {
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+ )
+ qsTile.destroy()
+ null
+ }
+ // Tile is in the current list of tiles and available.
+ // We have a handful of different cases
+ qsTile !is CustomTile -> {
+ // The tile is not a custom tile. Make sure they are reset to the correct
+ // user
+ if (userChanged) {
+ qsTile.userSwitch(user)
+ logger.logTileUserChanged(tileSpec, user)
+ }
+ qsTile
+ }
+ qsTile.user == user -> {
+ // The tile is a custom tile for the same user, just return it
+ qsTile
+ }
+ else -> {
+ // The tile is a custom tile and the user has changed. Destroy it
+ qsTile.destroy()
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
+ )
+ null
+ }
}
- qsTile
- }
- qsTile.user == user -> {
- // The tile is a custom tile for the same user, just return it
- qsTile
- }
- else -> {
- // The tile is a custom tile and the user has changed. Destroy it
- qsTile.destroy()
- logger.logTileDestroyed(
- tileSpec,
- QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
- )
- null
}
}
}
+
+ private sealed interface TileOrNotInstalled {
+ object NotInstalled : TileOrNotInstalled
+
+ @JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled
+ }
+
+ private data class UserAndTiles(
+ val userId: Int,
+ val tiles: List<TileSpec>,
+ )
+
+ private data class Data(
+ val oldData: UserAndTiles,
+ val newData: UserAndTiles,
+ val installedComponents: Set<ComponentName>,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index ff7d2068bc4e..8318ec99e530 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -124,6 +124,18 @@ constructor(
tileListLogBuffer.log(TILE_LIST_TAG, LogLevel.DEBUG, {}, { "Using retail tiles" })
}
+ fun logTilesNotInstalled(tiles: Collection<TileSpec>, user: Int) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = tiles.toString()
+ int1 = user
+ },
+ { "Tiles kept for not installed packages for user $int1: $str1" }
+ )
+ }
+
/** Reasons for destroying an existing tile. */
enum class TileDestroyedReason(val readable: String) {
TILE_REMOVED("Tile removed from current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index f080d3dfab1d..3af75cef3d4c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -34,6 +34,7 @@ import android.view.WindowInsets
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.core.view.doOnLayout
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
@@ -220,6 +221,7 @@ constructor(
override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
override fun dispatchDemoCommand(command: String, args: Bundle) =
clock.dispatchDemoCommand(command, args)
+
override fun onDemoModeStarted() = clock.onDemoModeStarted()
override fun onDemoModeFinished() = clock.onDemoModeFinished()
}
@@ -259,6 +261,7 @@ constructor(
resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
lastInsets?.let { updateConstraintsForInsets(header, it) }
updateResources()
+ updateCarrierGroupPadding()
}
}
@@ -291,6 +294,7 @@ constructor(
privacyIconsController.chipVisibilityListener = chipVisibilityListener
updateVisibility()
updateTransition()
+ updateCarrierGroupPadding()
header.setOnApplyWindowInsetsListener(insetListener)
@@ -298,8 +302,6 @@ constructor(
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
v.pivotX = newPivot
v.pivotY = v.height.toFloat() / 2
-
- mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
}
clock.setOnClickListener { launchClockActivity() }
@@ -359,6 +361,14 @@ constructor(
.load(context, resources.getXml(R.xml.large_screen_shade_header))
}
+ private fun updateCarrierGroupPadding() {
+ clock.doOnLayout {
+ val maxClockWidth =
+ (clock.width * resources.getFloat(R.dimen.qqs_expand_clock_scale)).toInt()
+ mShadeCarrierGroup.setPaddingRelative(maxClockWidth, 0, 0, 0)
+ }
+ }
+
private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
val cutout = insets.displayCutout.also { this.cutout = it }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
new file mode 100644
index 000000000000..891ee0cf66d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.util.kotlin
+
+import android.annotation.WorkerThread
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import com.android.systemui.util.Assert
+
+@WorkerThread
+fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean {
+ Assert.isNotMainThread()
+ return when (getComponentEnabledSetting(componentInfo.componentName)) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> componentInfo.isEnabled
+ else -> false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b24a69292186..b848d2e84faf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2006,14 +2006,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
if (row.anim == null) {
row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
row.anim.setInterpolator(new DecelerateInterpolator());
+ row.anim.addListener(
+ getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
- row.anim.addListener(
- getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
row.anim.start();
} else {
// update slider directly to clamped value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 111b8e83a984..d36e77889810 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -92,8 +92,8 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun affordance_walletNotEnabled_modelIsNone() = runBlockingTest {
- setUpState(isWalletEnabled = false)
+ fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
+ setUpState(isWalletFeatureAvailable = false)
var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +165,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest {
setUpState(
- isWalletEnabled = false,
+ isWalletFeatureAvailable = false,
)
assertThat(underTest.getPickerScreenState())
@@ -183,16 +183,15 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
private fun setUpState(
- isWalletEnabled: Boolean = true,
+ isWalletFeatureAvailable: Boolean = true,
isWalletServiceAvailable: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
hasSelectedCard: Boolean = true,
) {
- whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
-
val walletClient: QuickAccessWalletClient = mock()
whenever(walletClient.tileIcon).thenReturn(ICON)
whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+ whenever(walletClient.isWalletFeatureAvailable).thenReturn(isWalletFeatureAvailable)
whenever(walletController.walletClient).thenReturn(walletClient)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 205fa0f11a26..9dba9b5b3c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -35,6 +35,8 @@ import android.media.session.MediaSessionManager;
import android.os.PowerExemptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
@@ -58,6 +60,8 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.google.common.base.Strings;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -252,4 +256,106 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0);
}
+
+ @Test
+ public void afterTextChanged_nameLengthMoreThanMax_showErrorMessage() {
+ ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_edit);
+ TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_name_summary);
+ broadcastName.setText(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastNameEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 3);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_enterValidNameAfterLengthMoreThanMax_noErrorMessage() {
+ ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_edit);
+ TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_name_summary);
+ broadcastName.setText(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastNameEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String testString = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 2);
+ editText.setText(testString);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+
+ // input the valid text
+ testString = Strings.repeat("b",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH - 100);
+ editText.setText(testString);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_codeLengthMoreThanMax_showErrorMessage() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastCodeEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_CODE_MAX_LENGTH + 1);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_codeLengthLessThanMin_showErrorMessage() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastCodeEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_CODE_MIN_LENGTH - 1);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
new file mode 100644
index 000000000000..18f3837a7d36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2023 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.qs.pipeline.data.repository
+
+import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.quicksettings.TileService
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var packageManager: PackageManager
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+
+ private lateinit var underTest: InstalledTilesComponentRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ // Use the default value set in the ServiceInfo
+ whenever(packageManager.getComponentEnabledSetting(any()))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ // Return empty by default
+ whenever(packageManager.queryIntentServicesAsUser(any(), any<ResolveInfoFlags>(), anyInt()))
+ .thenReturn(emptyList())
+
+ underTest =
+ InstalledTilesComponentRepositoryImpl(
+ context,
+ packageManager,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun registersAndUnregistersBroadcastReceiver() =
+ testScope.runTest {
+ val user = 10
+ val job = launch { underTest.getInstalledTilesComponents(user).collect {} }
+ runCurrent()
+
+ verify(context)
+ .registerReceiverAsUser(
+ capture(receiverCaptor),
+ eq(UserHandle.of(user)),
+ any(),
+ nullable(),
+ nullable(),
+ )
+
+ verify(context, never()).unregisterReceiver(receiverCaptor.value)
+
+ job.cancel()
+ runCurrent()
+ verify(context).unregisterReceiver(receiverCaptor.value)
+ }
+
+ @Test
+ fun intentFilterForCorrectActionsAndScheme() =
+ testScope.runTest {
+ val filterCaptor = argumentCaptor<IntentFilter>()
+
+ backgroundScope.launch { underTest.getInstalledTilesComponents(0).collect {} }
+ runCurrent()
+
+ verify(context)
+ .registerReceiverAsUser(
+ any(),
+ any(),
+ capture(filterCaptor),
+ nullable(),
+ nullable(),
+ )
+
+ with(filterCaptor.value) {
+ assertThat(matchAction(Intent.ACTION_PACKAGE_CHANGED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_ADDED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_REPLACED)).isTrue()
+ assertThat(countActions()).isEqualTo(4)
+
+ assertThat(hasDataScheme("package")).isTrue()
+ assertThat(countDataSchemes()).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun componentsLoadedOnStart() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+ assertThat(componentNames).containsExactly(TEST_COMPONENT)
+ }
+
+ @Test
+ fun componentAdded_foundAfterBroadcast() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+ getRegisteredReceiver().onReceive(context, Intent(Intent.ACTION_PACKAGE_ADDED))
+
+ assertThat(componentNames).containsExactly(TEST_COMPONENT)
+ }
+
+ @Test
+ fun componentWithoutPermission_notValid() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = false, defaultEnabled = true)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+ }
+
+ @Test
+ fun componentNotEnabled_notValid() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = false)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+ }
+
+ private fun getRegisteredReceiver(): BroadcastReceiver {
+ verify(context)
+ .registerReceiverAsUser(
+ capture(receiverCaptor),
+ any(),
+ any(),
+ nullable(),
+ nullable(),
+ )
+
+ return receiverCaptor.value
+ }
+
+ companion object {
+ private val INTENT = Intent(TileService.ACTION_QS_TILE)
+ private val FLAGS =
+ ResolveInfoFlags.of(
+ (PackageManager.MATCH_DIRECT_BOOT_AWARE or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+ PackageManager.GET_SERVICES)
+ .toLong()
+ )
+ private val PERMISSION = BIND_QUICK_SETTINGS_TILE
+
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+
+ private fun matchFlags() =
+ argThat<ResolveInfoFlags> { flags -> flags?.value == FLAGS.value }
+ private fun matchIntent() = argThat<Intent> { intent -> intent.action == INTENT.action }
+
+ private fun ResolveInfo(
+ componentName: ComponentName,
+ hasPermission: Boolean,
+ defaultEnabled: Boolean
+ ): ResolveInfo {
+ val applicationInfo = ApplicationInfo().apply { enabled = true }
+ val serviceInfo =
+ ServiceInfo().apply {
+ packageName = componentName.packageName
+ name = componentName.className
+ if (hasPermission) {
+ permission = PERMISSION
+ }
+ enabled = defaultEnabled
+ this.applicationInfo = applicationInfo
+ }
+ val resolveInfo = ResolveInfo()
+ resolveInfo.serviceInfo = serviceInfo
+ return resolveInfo
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 426ff670802f..e7ad4896810b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.qs.external.TileLifecycleManager
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
@@ -73,6 +74,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
private val userRepository = FakeUserRepository()
+ private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository()
private val tileFactory = FakeQSFactory(::tileCreator)
private val customTileAddedRepository: CustomTileAddedRepository =
FakeCustomTileAddedRepository()
@@ -100,11 +102,13 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
+
setUserTracker(0)
underTest =
CurrentTilesInteractorImpl(
tileSpecRepository = tileSpecRepository,
+ installedTilesComponentRepository = installedTilesPackageRepository,
userRepository = userRepository,
customTileStatePersister = customTileStatePersister,
tileFactory = tileFactory,
@@ -609,6 +613,40 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
assertThat((tileA as FakeQSTile).callbacks).containsExactly(callback)
}
+ @Test
+ fun packageNotInstalled_customTileNotVisible() =
+ testScope.runTest(USER_INFO_0) {
+ installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles!!.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(specs[0])
+ }
+
+ @Test
+ fun packageInstalledLater_customTileAdded() =
+ testScope.runTest(USER_INFO_0) {
+ installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+
+ val tiles by collectLastValue(underTest.currentTiles)
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC, TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles!!.size).isEqualTo(2)
+
+ installedTilesPackageRepository.setInstalledPackagesForUser(
+ USER_INFO_0.id,
+ setOf(TEST_COMPONENT)
+ )
+
+ assertThat(tiles!!.size).isEqualTo(3)
+ assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
+ }
+
private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
this.state = state
this.label = label
@@ -654,6 +692,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
private suspend fun switchUser(user: UserInfo) {
setUserTracker(user.id)
+ installedTilesPackageRepository.setInstalledPackagesForUser(user.id, setOf(TEST_COMPONENT))
userRepository.setSelectedUserInfo(user)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 20da8a619100..2da2e9238d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -78,6 +78,7 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -387,7 +388,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
whenever(clock.isLayoutRtl).thenReturn(false)
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
verify(clock).pivotX = 0f
@@ -400,7 +401,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
whenever(clock.isLayoutRtl).thenReturn(true)
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
verify(clock).pivotX = width.toFloat()
@@ -793,7 +794,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
@Test
fun clockPivotYInCenter() {
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
var height = 100
val width = 50
@@ -825,16 +826,17 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
}
@Test
- fun carrierLeftPaddingIsSetWhenClockLayoutChanges() {
- val width = 200
- whenever(clock.width).thenReturn(width)
- whenever(clock.scaleX).thenReturn(2.57f) // 2.57 comes from qs_header.xml
- val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ fun carrierStartPaddingIsSetOnClockLayout() {
+ val clockWidth = 200
+ val maxClockScale = context.resources.getFloat(R.dimen.qqs_expand_clock_scale)
+ val expectedStartPadding = (clockWidth * maxClockScale).toInt()
+ whenever(clock.width).thenReturn(clockWidth)
- verify(clock).addOnLayoutChangeListener(capture(captor))
- captor.value.onLayoutChange(clock, 0, 0, width, 0, 0, 0, 0, 0)
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
+ captor.allValues.forEach { clock.executeLayoutChange(0, 0, clockWidth, 0, it) }
- verify(carrierGroup).setPaddingRelative(514, 0, 0, 0)
+ verify(carrierGroup).setPaddingRelative(expectedStartPadding, 0, 0, 0)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
new file mode 100644
index 000000000000..2013bb0a547e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 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.util.kotlin
+
+import android.content.ComponentName
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) :
+ SysuiTestCase() {
+
+ @Mock private lateinit var packageManager: PackageManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testComponentActuallyEnabled() {
+ whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+ .thenReturn(testCase.componentEnabledSetting)
+ val componentInfo =
+ mock<ComponentInfo>() {
+ whenever(isEnabled).thenReturn(testCase.componentIsEnabled)
+ whenever(componentName).thenReturn(TEST_COMPONENT)
+ }
+
+ assertThat(packageManager.isComponentActuallyEnabled(componentInfo))
+ .isEqualTo(testCase.expected)
+ }
+
+ internal data class TestCase(
+ @PackageManager.EnabledState val componentEnabledSetting: Int,
+ val componentIsEnabled: Boolean,
+ val expected: Boolean,
+ ) {
+ override fun toString(): String {
+ return "WHEN(" +
+ "componentIsEnabled = $componentIsEnabled, " +
+ "componentEnabledSetting = ${enabledStateToString()}) then " +
+ "EXPECTED = $expected"
+ }
+
+ private fun enabledStateToString() =
+ when (componentEnabledSetting) {
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> "STATE_DEFAULT"
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> "STATE_DISABLED"
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED -> {
+ "STATE_DISABLED_UNTIL_USED"
+ }
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> "STATE_DISABLED_USER"
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> "STATE_ENABLED"
+ else -> "INVALID STATE"
+ }
+ }
+
+ companion object {
+ @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData
+
+ private val testDataComponentIsEnabled =
+ listOf(
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ componentIsEnabled = true,
+ expected = true,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting =
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ componentIsEnabled = true,
+ expected = true,
+ ),
+ )
+
+ private val testDataComponentIsDisabled =
+ listOf(
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ componentIsEnabled = false,
+ expected = true,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting =
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ )
+
+ private val testData = testDataComponentIsDisabled + testDataComponentIsEnabled
+
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
new file mode 100644
index 000000000000..ff6b7d083df7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.qs.pipeline.data.repository
+
+import android.content.ComponentName
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeInstalledTilesComponentRepository : InstalledTilesComponentRepository {
+
+ private val installedComponentsPerUser =
+ mutableMapOf<Int, MutableStateFlow<Set<ComponentName>>>()
+
+ override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
+ return getFlow(userId).asStateFlow()
+ }
+
+ fun setInstalledPackagesForUser(userId: Int, components: Set<ComponentName>) {
+ getFlow(userId).value = components
+ }
+
+ private fun getFlow(userId: Int): MutableStateFlow<Set<ComponentName>> =
+ installedComponentsPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0b5b1cb2902e..4b6d32427d68 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -25,6 +25,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
+import android.app.BroadcastOptions;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.SystemClock;
@@ -257,7 +258,10 @@ class BroadcastProcessQueue {
deferredStatesApplyConsumer.accept(record, recordIndex);
}
- if (record.isReplacePending()) {
+ // Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the
+ // BroadcastOptions delivery group APIs.
+ if (record.isReplacePending()
+ && record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
return replacedBroadcastRecord;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index f6004d7d2b7f..c6165cd3220e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -339,7 +339,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
BroadcastRecord r, String typeForLogging) {
final Intent intent = r.intent;
- for (int i = queue.size() - 1; i > 0; i--) {
+ for (int i = queue.size() - 1; i >= 0; i--) {
final BroadcastRecord old = queue.get(i);
if (old.userId == r.userId && intent.filterEquals(old.intent)) {
if (DEBUG_BROADCAST) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index cbc75401f0b8..93fb91ab8cdd 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -719,11 +719,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private void skipAndCancelReplacedBroadcasts(ArraySet<BroadcastRecord> replacedBroadcasts) {
for (int i = 0; i < replacedBroadcasts.size(); ++i) {
final BroadcastRecord r = replacedBroadcasts.valueAt(i);
- r.resultCode = Activity.RESULT_CANCELED;
- r.resultData = null;
- r.resultExtras = null;
- scheduleResultTo(r);
- notifyFinishBroadcast(r);
+ // Skip all the receivers in the replaced broadcast
+ for (int rcvrIdx = 0; rcvrIdx < r.receivers.size(); ++rcvrIdx) {
+ if (!isDeliveryStateTerminal(r.getDeliveryState(rcvrIdx))) {
+ mBroadcastConsumerSkipAndCanceled.accept(r, rcvrIdx);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d0b6cdce037f..336f0fb5699f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3277,6 +3277,7 @@ public class AudioService extends IAudioService.Stub
if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
streamType = mVolumeControlStream;
} else {
+ // TODO discard activity on a muted stream?
final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
final boolean activeForReal;
if (maybeActiveStreamType == AudioSystem.STREAM_RING
@@ -3480,9 +3481,10 @@ public class AudioService extends IAudioService.Stub
}
} else if (isStreamMutedByRingerOrZenMode(streamTypeAlias) && streamState.mIsMuted) {
// if the stream is currently muted streams by ringer/zen mode
- // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES)
+ // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES) with an unmute or raise
if (direction == AudioManager.ADJUST_TOGGLE_MUTE
- || direction == AudioManager.ADJUST_UNMUTE) {
+ || direction == AudioManager.ADJUST_UNMUTE
+ || direction == AudioManager.ADJUST_RAISE) {
adjustVolume = false;
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index aeff2b0f9af6..b39e8606e189 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.TRIM_LIGH
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -223,6 +224,8 @@ import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -234,6 +237,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.VibrationEffect;
+import android.os.WorkSource;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -559,6 +563,7 @@ public class NotificationManagerService extends SystemService {
private PermissionHelper mPermissionHelper;
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private TelecomManager mTelecomManager;
+ private PowerManager mPowerManager;
private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
final IBinder mForegroundToken = new Binder();
@@ -923,7 +928,7 @@ public class NotificationManagerService extends SystemService {
if (oldFlags != flags) {
summary.getSbn().getNotification().flags = flags;
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -1457,7 +1462,7 @@ public class NotificationManagerService extends SystemService {
// want to adjust the flag behaviour.
mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
r, true /* isAppForeground*/,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -1488,7 +1493,7 @@ public class NotificationManagerService extends SystemService {
mHandler.post(
new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
/* foreground= */ true,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -2233,7 +2238,7 @@ public class NotificationManagerService extends SystemService {
UsageStatsManagerInternal usageStatsManagerInternal,
TelecomManager telecomManager, NotificationChannelLogger channelLogger,
SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
- PermissionManager permissionManager,
+ PermissionManager permissionManager, PowerManager powerManager,
PostNotificationTrackerFactory postNotificationTrackerFactory) {
mHandler = handler;
Resources resources = getContext().getResources();
@@ -2265,6 +2270,7 @@ public class NotificationManagerService extends SystemService {
mDpm = dpm;
mUm = userManager;
mTelecomManager = telecomManager;
+ mPowerManager = powerManager;
mPostNotificationTrackerFactory = postNotificationTrackerFactory;
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -2568,6 +2574,7 @@ public class NotificationManagerService extends SystemService {
getContext().getSystemService(TelecomManager.class),
new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(),
getContext().getSystemService(PermissionManager.class),
+ getContext().getSystemService(PowerManager.class),
new PostNotificationTrackerFactory() {});
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
@@ -2684,7 +2691,7 @@ public class NotificationManagerService extends SystemService {
final boolean isAppForeground =
mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -6577,7 +6584,7 @@ public class NotificationManagerService extends SystemService {
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int incomingUserId, boolean postSilently) {
- PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker();
+ PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid);
boolean enqueued = false;
try {
enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id,
@@ -6589,6 +6596,22 @@ public class NotificationManagerService extends SystemService {
}
}
+ private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) {
+ if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) {
+ // The package probably doesn't have WAKE_LOCK permission and should not require it.
+ return Binder.withCleanCallingIdentity(() -> {
+ WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "NotificationManagerService:post:" + pkg);
+ wakeLock.setWorkSource(new WorkSource(uid, pkg));
+ // TODO(b/275044361): Adjust to a more reasonable number when we have the data.
+ wakeLock.acquire(30_000);
+ return mPostNotificationTrackerFactory.newTracker(wakeLock);
+ });
+ } else {
+ return mPostNotificationTrackerFactory.newTracker(null);
+ }
+ }
+
/**
* @return True if we successfully processed the notification and handed off the task of
* enqueueing it to a background thread; false otherwise.
@@ -7106,7 +7129,7 @@ public class NotificationManagerService extends SystemService {
mHandler.post(
new NotificationManagerService.EnqueueNotificationRunnable(
r.getUser().getIdentifier(), r, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -12168,20 +12191,20 @@ public class NotificationManagerService extends SystemService {
}
interface PostNotificationTrackerFactory {
- default PostNotificationTracker newTracker() {
- return new PostNotificationTracker();
+ default PostNotificationTracker newTracker(@Nullable WakeLock optionalWakelock) {
+ return new PostNotificationTracker(optionalWakelock);
}
}
static class PostNotificationTracker {
@ElapsedRealtimeLong private final long mStartTime;
- @Nullable private NotificationRecordLogger.NotificationReported mReport;
+ @Nullable private final WakeLock mWakeLock;
private boolean mOngoing;
@VisibleForTesting
- PostNotificationTracker() {
- // TODO(b/275044361): (Conditionally) receive a wakelock.
+ PostNotificationTracker(@Nullable WakeLock wakeLock) {
mStartTime = SystemClock.elapsedRealtime();
+ mWakeLock = wakeLock;
mOngoing = true;
if (DBG) {
Slog.d(TAG, "PostNotification: Started");
@@ -12199,9 +12222,8 @@ public class NotificationManagerService extends SystemService {
}
/**
- * Cancels the tracker (TODO(b/275044361): releasing the acquired WakeLock). Either
- * {@link #finish} or {@link #cancel} (exclusively) should be called on this object before
- * it's discarded.
+ * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or
+ * {@link #cancel} (exclusively) should be called on this object before it's discarded.
*/
void cancel() {
if (!isOngoing()) {
@@ -12209,9 +12231,9 @@ public class NotificationManagerService extends SystemService {
return;
}
mOngoing = false;
-
- // TODO(b/275044361): Release wakelock.
-
+ if (mWakeLock != null) {
+ Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+ }
if (DBG) {
long elapsedTime = SystemClock.elapsedRealtime() - mStartTime;
Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms",
@@ -12220,9 +12242,9 @@ public class NotificationManagerService extends SystemService {
}
/**
- * Finishes the tracker (TODO(b/275044361): releasing the acquired WakeLock) and returns the
- * time elapsed since the operation started, in milliseconds. Either {@link #finish} or
- * {@link #cancel} (exclusively) should be called on this object before it's discarded.
+ * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since
+ * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel}
+ * (exclusively) should be called on this object before it's discarded.
*/
@DurationMillisLong
long finish() {
@@ -12232,9 +12254,9 @@ public class NotificationManagerService extends SystemService {
return elapsedTime;
}
mOngoing = false;
-
- // TODO(b/275044361): Release wakelock.
-
+ if (mWakeLock != null) {
+ Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+ }
if (DBG) {
Slog.d(TAG,
TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime));
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 06db5be349d4..50f1673cae44 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2145,7 +2145,7 @@ final class InstallPackageHelper {
final String pkgName = pkg.getPackageName();
final int[] installedForUsers = installRequest.getOriginUsers();
final int installReason = installRequest.getInstallReason();
- final String installerPackageName = installRequest.getSourceInstallerPackageName();
+ final String installerPackageName = installRequest.getInstallerPackageName();
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
synchronized (mPm.mLock) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 95e790450724..34648740d54c 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -366,12 +366,6 @@ final class InstallRequest {
public String getApexModuleName() {
return mApexModuleName;
}
-
- @Nullable
- public String getSourceInstallerPackageName() {
- return mInstallArgs.mInstallSource.mInstallerPackageName;
- }
-
public boolean isRollback() {
return mInstallArgs != null
&& mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 6491fd1b1f98..a9115371413c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -511,7 +511,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
} catch (FileNotFoundException e) {
// Missing sessions are okay, probably first boot
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
Slog.wtf(TAG, "Failed reading install sessions", e);
} finally {
IoUtils.closeQuietly(fis);
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
index 19aa4f8e8d0b..54ca426a6dc3 100644
--- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -230,7 +230,9 @@ final class ResilientAtomicFile implements Closeable {
+ Log.getStackTraceString(e));
}
- mCurrentFile.delete();
+ if (!mCurrentFile.delete()) {
+ throw new IllegalStateException("Failed to remove " + mCurrentFile);
+ }
mCurrentFile = null;
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d108e1487564..d55f85cde5af 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -986,11 +986,14 @@ public class PackageInfoUtils {
}
/** @see ApplicationInfo#privateFlagsExt */
- public static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
+ private static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
@Nullable PackageStateInternal pkgSetting) {
// @formatter:off
- // TODO: Add state specific flags
- return pkgWithoutStateFlags;
+ int flags = pkgWithoutStateFlags;
+ if (pkgSetting != null) {
+ flags |= flag(pkgSetting.getCpuAbiOverride() != null, ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE);
+ }
+ return flags;
// @formatter:on
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b2100e56f44..9f16a8441533 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4281,6 +4281,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
mTaskSupervisor.mStoppingActivities.remove(this);
+ mLetterboxUiController.destroy();
waitingToShow = false;
// Defer removal of this activity when either a child is animating, or app transition is on
@@ -4350,8 +4351,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
}
- mLetterboxUiController.destroy();
-
if (!delayed) {
updateReportedVisibilityLocked();
}
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 123a74dbf597..f7ccc0d91969 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
@@ -178,6 +179,14 @@ class AppWarnings {
* @param r activity record for which the warning may be displayed
*/
public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) {
+ final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt
+ & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0;
+ if (isUsingAbiOverride) {
+ // The abiOverride flag was specified during installation, which means that if the app
+ // is currently running in 32-bit mode, it is intended. Do not show the warning dialog.
+ return;
+ }
+ // The warning dialog can also be disabled for debugging purpose
final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean(
"debug.wm.disable_deprecated_abi_dialog", false);
if (disableDeprecatedAbiDialog) {
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index 31b1069dd022..db4762e5f877 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -111,9 +111,13 @@ final class DeviceStateController {
}
/**
- * @return true if the rotation direction on the Z axis should be reversed.
+ * @return true if the rotation direction on the Z axis should be reversed for the default
+ * display.
*/
- boolean shouldReverseRotationDirectionAroundZAxis() {
+ boolean shouldReverseRotationDirectionAroundZAxis(@NonNull DisplayContent displayContent) {
+ if (!displayContent.isDefaultDisplay) {
+ return false;
+ }
return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8be36f07a040..b681c198538f 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -950,7 +950,7 @@ public class DisplayRotation {
}
void freezeRotation(int rotation) {
- if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+ if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation);
}
@@ -1225,7 +1225,7 @@ public class DisplayRotation {
if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
sensorRotation = -1;
}
- if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+ if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
}
mLastSensorRotation = sensorRotation;
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 774f62d44045..fd478dc12c13 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -31,6 +31,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -245,10 +246,13 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ ActivityOptions activityOptions = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- null, new UserHandle(mUserId)) .getIntentSender();
+ | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ activityOptions.toBundle(), new UserHandle(mUserId)).getIntentSender();
Bundle result = new Bundle();
result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index dc92376263a6..ca4a4048cc00 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -66,6 +66,7 @@ import android.system.StructStat;
import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
@@ -2506,6 +2507,7 @@ public class PackageManagerTests extends AndroidTestCase {
}
@LargeTest
+ @FlakyTest(bugId = 283797480)
public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
// checkSignatures should be successful when both apps have been signed with the same
// rotated key since the initial signature comparison between the two apps should
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 4989f841a275..03231ecfb723 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1904,6 +1904,34 @@ public class BroadcastQueueTest {
}
@Test
+ public void testReplacePending_diffReceivers() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
+ final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5),
+ withPriority(receiverYellow, 0))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5))));
+
+ waitForIdle();
+
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index f44c1d18614d..4315254f68a9 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
@@ -36,6 +37,7 @@
<uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9166b3d75f60..2a0c745a0ffc 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -61,6 +61,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -80,6 +81,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
@@ -119,12 +121,14 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import android.Manifest;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -181,11 +185,14 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.WorkSource;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -351,6 +358,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private PermissionManager mPermissionManager;
@Mock
private DevicePolicyManagerInternal mDevicePolicyManager;
+ @Mock
+ private PowerManager mPowerManager;
+ private final ArrayList<WakeLock> mAcquiredWakeLocks = new ArrayList<>();
private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
new TestPostNotificationTrackerFactory();
@@ -431,8 +441,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private final List<PostNotificationTracker> mCreatedTrackers = new ArrayList<>();
@Override
- public PostNotificationTracker newTracker() {
- PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker();
+ public PostNotificationTracker newTracker(@Nullable WakeLock optionalWakeLock) {
+ PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker(
+ optionalWakeLock);
mCreatedTrackers.add(tracker);
return tracker;
}
@@ -563,6 +574,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
+ // Use the real PowerManager to back up the mock w.r.t. creating WakeLocks.
+ // This is because 1) we need a mock to verify() calls and tracking the created WakeLocks,
+ // but 2) PowerManager and WakeLock perform their own checks (e.g. correct arguments, don't
+ // call release twice, etc) and we want the test to fail if such misuse happens, too.
+ PowerManager realPowerManager = mContext.getSystemService(PowerManager.class);
+ when(mPowerManager.newWakeLock(anyInt(), anyString())).then(
+ (Answer<WakeLock>) invocation -> {
+ WakeLock wl = realPowerManager.newWakeLock(invocation.getArgument(0),
+ invocation.getArgument(1));
+ mAcquiredWakeLocks.add(wl);
+ return wl;
+ });
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
+
// apps allowed as convos
mService.setStringArrayResourceValue(PKG_O);
@@ -579,7 +604,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
- mPostNotificationTrackerFactory);
+ mPowerManager, mPostNotificationTrackerFactory);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -686,6 +711,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@After
+ public void assertAllWakeLocksReleased() {
+ for (WakeLock wakeLock : mAcquiredWakeLocks) {
+ assertThat(wakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @After
public void tearDown() throws Exception {
if (mFile != null) mFile.delete();
clearDeviceConfig();
@@ -1486,7 +1518,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -1507,7 +1539,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -1803,6 +1835,112 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception {
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_acquiresAndReleasesWakeLock", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_throws_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate not enqueued due to rejected inputs.
+ assertThrows(Exception.class,
+ () -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_throws_acquiresAndReleasesWakeLock", 0,
+ /* notification= */ null, 0));
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate not enqueued due to snoozing inputs.
+ when(mSnoozeHelper.getSnoozeContextForUnpostedNotification(anyInt(), any(), any()))
+ .thenReturn("zzzzzzz");
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_notPosted_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate enqueued but not posted due to missing small icon.
+ Notification notif = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .build();
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0,
+ notif, 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ // NLSes were not called.
+ verify(mListeners, never()).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_setsWakeLockWorkSource() throws Exception {
+ // Use a "full" mock for the PowerManager (instead of the one that delegates to the real
+ // service) so we can return a mocked WakeLock that we can verify() on.
+ reset(mPowerManager);
+ WakeLock wakeLock = mock(WakeLock.class);
+ when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(wakeLock);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ InOrder inOrder = inOrder(mPowerManager, wakeLock);
+ inOrder.verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ inOrder.verify(wakeLock).setWorkSource(eq(new WorkSource(mUid, PKG)));
+ inOrder.verify(wakeLock).acquire(anyLong());
+ inOrder.verify(wakeLock).release();
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void enqueueNotification_wakeLockFlagOff_noWakeLock() throws Exception {
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ verifyZeroInteractions(mPowerManager);
+ }
+
+ @Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
"testCancelNonexistentNotification", 0, 0);
@@ -4361,7 +4499,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4380,7 +4518,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(), update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4400,7 +4538,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(), update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4420,7 +4558,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(),
- update.getUid(), mPostNotificationTrackerFactory.newTracker());
+ update.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4434,13 +4572,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
r = generateNotificationRecord(mTestNotificationChannel, 1, null, false);
r.setCriticality(CriticalNotificationExtractor.CRITICAL);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
mService.addEnqueuedNotification(r);
runnable.run();
@@ -5090,7 +5228,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.new PostNotificationRunnable(original.getKey(),
original.getSbn().getPackageName(),
original.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -5114,7 +5252,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(),
update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -7536,7 +7674,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10234,7 +10372,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10251,7 +10389,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10268,7 +10406,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10361,7 +10499,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// normal blocked notifications - blocked
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10379,7 +10517,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10392,7 +10530,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats, never()).registerBlocked(any());
@@ -10406,7 +10544,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats, never()).registerBlocked(any());
@@ -10420,7 +10558,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10435,7 +10573,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10448,7 +10586,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 66c1e35754c5..81c573d8fb1e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,6 +48,7 @@ import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
@@ -169,6 +170,7 @@ public class RoleObserverTest extends UiServiceTestCase {
mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
mock(NotificationChannelLogger.class), new TestableFlagResolver(),
mock(PermissionManager.class),
+ mock(PowerManager.class),
new NotificationManagerService.PostNotificationTrackerFactory() {});
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 04e1d9c07a07..2a8f0ffc4d49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -59,10 +59,10 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
-import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.DisplayAddress;
@@ -518,7 +518,8 @@ public class DisplayRotationTests {
mBuilder.build();
configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(true);
thawRotation();
@@ -544,7 +545,8 @@ public class DisplayRotationTests {
@Test
public void testFreezeRotation_reverseRotationDirectionAroundZAxis_yes() throws Exception {
mBuilder.build();
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(true);
freezeRotation(Surface.ROTATION_90);
assertEquals(Surface.ROTATION_270, mTarget.getUserRotation());
@@ -553,7 +555,8 @@ public class DisplayRotationTests {
@Test
public void testFreezeRotation_reverseRotationDirectionAroundZAxis_no() throws Exception {
mBuilder.build();
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(false);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(false);
freezeRotation(Surface.ROTATION_90);
assertEquals(Surface.ROTATION_90, mTarget.getUserRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e74ad3364b63..082122e33175 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -406,7 +406,6 @@ public class SizeCompatTests extends WindowTestsBase {
clearInvocations(translucentActivity.mLetterboxUiController);
// We destroy the first opaque activity
- mActivity.setState(DESTROYED, "testing");
mActivity.removeImmediately();
// Check that updateInheritedLetterbox() is invoked again
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
index bedf0990a188..bfebb42ad244 100644
--- a/tests/InputMethodStressTest/AndroidTest.xml
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -19,6 +19,7 @@
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
<option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
</target_preparer>