Merge changes Iff50fbf8,Ibfd590be,Ibc4484bb into main
* changes:
Add logging to LogBuffer
Refactor VisualInterruptionDecisionProviderImpl
Improve tests and add missing test cases
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 538be14..9ff416a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -43,9 +43,9 @@
class PeekDisabledSuppressor(
private val globalSettings: GlobalSettings,
private val headsUpManager: HeadsUpManager,
- private val logger: NotificationInterruptLogger,
+ private val logger: VisualInterruptionDecisionLogger,
@Main private val mainHandler: Handler,
-) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") {
+) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek disabled by global setting") {
private var isEnabled = false
override fun shouldSuppress(): Boolean = !isEnabled
@@ -87,16 +87,13 @@
class PulseDisabledSuppressor(
private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val userTracker: UserTracker,
-) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") {
+) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by user setting") {
override fun shouldSuppress(): Boolean =
!ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId)
}
class PulseBatterySaverSuppressor(private val batteryController: BatteryController) :
- VisualInterruptionCondition(
- types = setOf(PULSE),
- reason = "pulsing disabled by battery saver"
- ) {
+ VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by battery saver") {
override fun shouldSuppress() = batteryController.isAodPowerSave()
}
@@ -128,14 +125,14 @@
}
class PeekNotImportantSuppressor() :
- VisualInterruptionFilter(types = setOf(PEEK), reason = "not important") {
+ VisualInterruptionFilter(types = setOf(PEEK), reason = "importance < HIGH") {
override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH
}
class PeekDeviceNotInUseSuppressor(
private val powerManager: PowerManager,
private val statusBarStateController: StatusBarStateController
-) : VisualInterruptionCondition(types = setOf(PEEK), reason = "not in use") {
+) : VisualInterruptionCondition(types = setOf(PEEK), reason = "device not in use") {
override fun shouldSuppress() =
when {
!powerManager.isScreenOn || statusBarStateController.isDreaming -> true
@@ -144,7 +141,7 @@
}
class PeekOldWhenSuppressor(private val systemClock: SystemClock) :
- VisualInterruptionFilter(types = setOf(PEEK), reason = "old when") {
+ VisualInterruptionFilter(types = setOf(PEEK), reason = "has old `when`") {
private fun whenAge(entry: NotificationEntry) =
systemClock.currentTimeMillis() - entry.sbn.notification.`when`
@@ -165,21 +162,21 @@
}
class PulseEffectSuppressor() :
- VisualInterruptionFilter(types = setOf(PULSE), reason = "ambient effect suppressed") {
+ VisualInterruptionFilter(types = setOf(PULSE), reason = "suppressed by DND") {
override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressAmbient()
}
class PulseLockscreenVisibilityPrivateSuppressor() :
VisualInterruptionFilter(
types = setOf(PULSE),
- reason = "notification hidden on lock screen by override"
+ reason = "hidden by lockscreen visibility override"
) {
override fun shouldSuppress(entry: NotificationEntry) =
entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE
}
class PulseLowImportanceSuppressor() :
- VisualInterruptionFilter(types = setOf(PULSE), reason = "importance less than DEFAULT") {
+ VisualInterruptionFilter(types = setOf(PULSE), reason = "importance < DEFAULT") {
override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT
}
@@ -198,12 +195,12 @@
}
class BubbleNotAllowedSuppressor() :
- VisualInterruptionFilter(types = setOf(BUBBLE), reason = "not allowed") {
+ VisualInterruptionFilter(types = setOf(BUBBLE), reason = "cannot bubble") {
override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
}
class BubbleNoMetadataSuppressor() :
- VisualInterruptionFilter(types = setOf(BUBBLE), reason = "no bubble metadata") {
+ VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") {
private fun isValidMetadata(metadata: BubbleMetadata?) =
metadata != null && (metadata.intent != null || metadata.shortcutId != null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index 6af2543..b44a367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -50,19 +50,32 @@
val shouldFsi: Boolean
val wouldFsiWithoutDnd: Boolean
val logReason: String
+ val shouldLog: Boolean
+ val isWarning: Boolean
}
private enum class DecisionImpl(
override val shouldFsi: Boolean,
override val logReason: String,
override val wouldFsiWithoutDnd: Boolean = shouldFsi,
- val supersedesDnd: Boolean = false
+ val supersedesDnd: Boolean = false,
+ override val shouldLog: Boolean = true,
+ override val isWarning: Boolean = false
) : Decision {
- NO_FSI_NO_FULL_SCREEN_INTENT(false, "no full-screen intent", supersedesDnd = true),
+ NO_FSI_NO_FULL_SCREEN_INTENT(
+ false,
+ "no full-screen intent",
+ supersedesDnd = true,
+ shouldLog = false
+ ),
NO_FSI_SHOW_STICKY_HUN(false, "full-screen intents are disabled", supersedesDnd = true),
NO_FSI_NOT_IMPORTANT_ENOUGH(false, "not important enough"),
- NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false, "suppressive group alert behavior"),
- NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata"),
+ NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(
+ false,
+ "suppressive group alert behavior",
+ isWarning = true
+ ),
+ NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata", isWarning = true),
NO_FSI_PACKAGE_SUSPENDED(false, "package suspended"),
FSI_DEVICE_NOT_INTERACTIVE(true, "device is not interactive"),
FSI_DEVICE_DREAMING(true, "device is dreaming"),
@@ -71,7 +84,7 @@
FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"),
FSI_LOCKED_SHADE(true, "locked shade"),
FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"),
- NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard"),
+ NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard", isWarning = true),
NO_FSI_SUPPRESSED_BY_DND(false, "suppressed by DND", wouldFsiWithoutDnd = false),
NO_FSI_SUPPRESSED_ONLY_BY_DND(false, "suppressed only by DND", wouldFsiWithoutDnd = true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
new file mode 100644
index 0000000..1470b03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.statusbar.notification.interruption
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
+import com.android.systemui.log.dagger.NotificationInterruptLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class VisualInterruptionDecisionLogger
+@Inject
+constructor(@NotificationInterruptLog val buffer: LogBuffer) {
+ fun logHeadsUpFeatureChanged(isEnabled: Boolean) {
+ buffer.log(
+ TAG,
+ INFO,
+ { bool1 = isEnabled },
+ { "HUN feature is now ${if (bool1) "enabled" else "disabled"}" }
+ )
+ }
+
+ fun logWillDismissAll() {
+ buffer.log(TAG, INFO, {}, { "dismissing all HUNs since feature was disabled" })
+ }
+
+ fun logDecision(
+ type: String,
+ entry: NotificationEntry,
+ decision: VisualInterruptionDecisionProvider.Decision
+ ) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = type
+ bool1 = decision.shouldInterrupt
+ str2 = decision.logReason
+ str3 = entry.logKey
+ },
+ {
+ val outcome = if (bool1) "allowed" else "suppressed"
+ "$str1 $outcome: $str2 (key=$str3)"
+ }
+ )
+ }
+
+ fun logFullScreenIntentDecision(
+ entry: NotificationEntry,
+ decision: FullScreenIntentDecision,
+ warning: Boolean
+ ) {
+ buffer.log(
+ TAG,
+ if (warning) WARNING else DEBUG,
+ {
+ bool1 = decision.shouldInterrupt
+ bool2 = decision.wouldInterruptWithoutDnd
+ str1 = decision.logReason
+ str2 = entry.logKey
+ },
+ {
+ val outcome =
+ when {
+ bool1 -> "allowed"
+ bool2 -> "suppressed only by DND"
+ else -> "suppressed"
+ }
+ "FSI $outcome: $str1 (key=$str2)"
+ }
+ )
+ }
+}
+
+private const val TAG = "VisualInterruptionDecisionProvider"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 9640682..c0a1a32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -18,6 +18,7 @@
import android.hardware.display.AmbientDisplayConfiguration
import android.os.Handler
import android.os.PowerManager
+import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -46,7 +47,7 @@
private val headsUpManager: HeadsUpManager,
private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
keyguardStateController: KeyguardStateController,
- private val logger: NotificationInterruptLogger,
+ private val logger: VisualInterruptionDecisionLogger,
@Main private val mainHandler: Handler,
private val powerManager: PowerManager,
private val statusBarStateController: StatusBarStateController,
@@ -58,9 +59,32 @@
override val logReason: String
) : Decision
+ private data class LoggableDecision private constructor(val decision: DecisionImpl) {
+ companion object {
+ val unsuppressed =
+ LoggableDecision(DecisionImpl(shouldInterrupt = true, logReason = "not suppressed"))
+
+ fun suppressed(legacySuppressor: NotificationInterruptSuppressor, methodName: String) =
+ LoggableDecision(
+ DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${legacySuppressor.name}.$methodName"
+ )
+ )
+
+ fun suppressed(suppressor: VisualInterruptionSuppressor) =
+ LoggableDecision(
+ DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason)
+ )
+ }
+ }
+
private class FullScreenIntentDecisionImpl(
+ val entry: NotificationEntry,
private val fsiDecision: FullScreenIntentDecisionProvider.Decision
) : FullScreenIntentDecision {
+ var hasBeenLogged = false
+
override val shouldInterrupt
get() = fsiDecision.shouldFsi
@@ -69,6 +93,12 @@
override val logReason
get() = fsiDecision.logReason
+
+ val shouldLog
+ get() = fsiDecision.shouldLog
+
+ val isWarning
+ get() = fsiDecision.isWarning
}
private val fullScreenIntentDecisionProvider =
@@ -139,137 +169,113 @@
override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision {
check(started)
- return makeHeadsUpDecision(entry)
+
+ return if (statusBarStateController.isDozing) {
+ makeLoggablePulseDecision(entry)
+ } else {
+ makeLoggablePeekDecision(entry)
+ }
+ .decision
}
override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision {
check(started)
- return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) }
+
+ return if (statusBarStateController.isDozing) {
+ makeLoggablePulseDecision(entry).also { logDecision(PULSE, entry, it) }
+ } else {
+ makeLoggablePeekDecision(entry).also { logDecision(PEEK, entry, it) }
+ }
+ .decision
}
+ private fun makeLoggablePeekDecision(entry: NotificationEntry): LoggableDecision =
+ checkConditions(PEEK)
+ ?: checkFilters(PEEK, entry) ?: checkSuppressInterruptions(entry)
+ ?: checkSuppressAwakeInterruptions(entry) ?: checkSuppressAwakeHeadsUp(entry)
+ ?: LoggableDecision.unsuppressed
+
+ private fun makeLoggablePulseDecision(entry: NotificationEntry): LoggableDecision =
+ checkConditions(PULSE)
+ ?: checkFilters(PULSE, entry) ?: checkSuppressInterruptions(entry)
+ ?: LoggableDecision.unsuppressed
+
override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision {
check(started)
- return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) }
+
+ return makeLoggableBubbleDecision(entry).also { logDecision(BUBBLE, entry, it) }.decision
+ }
+
+ private fun makeLoggableBubbleDecision(entry: NotificationEntry): LoggableDecision =
+ checkConditions(BUBBLE)
+ ?: checkFilters(BUBBLE, entry) ?: checkSuppressInterruptions(entry)
+ ?: checkSuppressAwakeInterruptions(entry) ?: LoggableDecision.unsuppressed
+
+ private fun logDecision(
+ type: VisualInterruptionType,
+ entry: NotificationEntry,
+ loggable: LoggableDecision
+ ) {
+ logger.logDecision(type.name, entry, loggable.decision)
}
override fun makeUnloggedFullScreenIntentDecision(
entry: NotificationEntry
): FullScreenIntentDecision {
check(started)
- return makeFullScreenIntentDecision(entry)
+
+ val couldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt
+ val fsiDecision =
+ fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, couldHeadsUp)
+ return FullScreenIntentDecisionImpl(entry, fsiDecision)
}
override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
check(started)
- // Not yet implemented.
+
+ if (decision !is FullScreenIntentDecisionImpl) {
+ Log.wtf(TAG, "FSI decision $decision was not created by this class")
+ return
+ }
+
+ if (decision.hasBeenLogged) {
+ Log.wtf(TAG, "FSI decision $decision has already been logged")
+ return
+ }
+
+ decision.hasBeenLogged = true
+
+ if (!decision.shouldLog) {
+ return
+ }
+
+ logger.logFullScreenIntentDecision(decision.entry, decision, decision.isWarning)
}
- private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl {
- if (statusBarStateController.isDozing) {
- return makePulseDecision(entry)
- } else {
- return makePeekDecision(entry)
- }
- }
+ private fun checkSuppressInterruptions(entry: NotificationEntry) =
+ legacySuppressors
+ .firstOrNull { it.suppressInterruptions(entry) }
+ ?.let { LoggableDecision.suppressed(it, "suppressInterruptions") }
- private fun makePeekDecision(entry: NotificationEntry): DecisionImpl {
- checkConditions(PEEK)?.let {
- return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
- }
- checkFilters(PEEK, entry)?.let {
- return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
- }
- checkSuppressors(entry)?.let {
- return DecisionImpl(
- shouldInterrupt = false,
- logReason = "${it.name}.suppressInterruptions"
- )
- }
- checkAwakeSuppressors(entry)?.let {
- return DecisionImpl(
- shouldInterrupt = false,
- logReason = "${it.name}.suppressAwakeInterruptions"
- )
- }
- checkAwakeHeadsUpSuppressors(entry)?.let {
- return DecisionImpl(
- shouldInterrupt = false,
- logReason = "${it.name}.suppressAwakeHeadsUpInterruptions"
- )
- }
- return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
- }
+ private fun checkSuppressAwakeInterruptions(entry: NotificationEntry) =
+ legacySuppressors
+ .firstOrNull { it.suppressAwakeInterruptions(entry) }
+ ?.let { LoggableDecision.suppressed(it, "suppressAwakeInterruptions") }
- private fun makePulseDecision(entry: NotificationEntry): DecisionImpl {
- checkConditions(PULSE)?.let {
- return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
- }
- checkFilters(PULSE, entry)?.let {
- return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
- }
- checkSuppressors(entry)?.let {
- return DecisionImpl(
- shouldInterrupt = false,
- logReason = "${it.name}.suppressInterruptions"
- )
- }
- return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
- }
+ private fun checkSuppressAwakeHeadsUp(entry: NotificationEntry) =
+ legacySuppressors
+ .firstOrNull { it.suppressAwakeHeadsUp(entry) }
+ ?.let { LoggableDecision.suppressed(it, "suppressAwakeHeadsUp") }
- private fun makeBubbleDecision(entry: NotificationEntry): DecisionImpl {
- checkConditions(BUBBLE)?.let {
- return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
- }
- checkFilters(BUBBLE, entry)?.let {
- return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
- }
- checkSuppressors(entry)?.let {
- return DecisionImpl(
- shouldInterrupt = false,
- logReason = "${it.name}.suppressInterruptions"
- )
- }
- checkAwakeSuppressors(entry)?.let {
- return DecisionImpl(
- shouldInterrupt = false,
- logReason = "${it.name}.suppressAwakeInterruptions"
- )
- }
- return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
- }
+ private fun checkConditions(type: VisualInterruptionType) =
+ conditions
+ .firstOrNull { it.types.contains(type) && it.shouldSuppress() }
+ ?.let { LoggableDecision.suppressed(it) }
- private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) {
- // Not yet implemented.
- }
-
- private fun logBubbleDecision(entry: NotificationEntry, decision: DecisionImpl) {
- // Not yet implemented.
- }
-
- private fun makeFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision {
- val wouldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt
- val fsiDecision =
- fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, wouldHeadsUp)
- return FullScreenIntentDecisionImpl(fsiDecision)
- }
-
- private fun checkSuppressors(entry: NotificationEntry) =
- legacySuppressors.firstOrNull { it.suppressInterruptions(entry) }
-
- private fun checkAwakeSuppressors(entry: NotificationEntry) =
- legacySuppressors.firstOrNull { it.suppressAwakeInterruptions(entry) }
-
- private fun checkAwakeHeadsUpSuppressors(entry: NotificationEntry) =
- legacySuppressors.firstOrNull { it.suppressAwakeHeadsUp(entry) }
-
- private fun checkConditions(type: VisualInterruptionType): VisualInterruptionCondition? =
- conditions.firstOrNull { it.types.contains(type) && it.shouldSuppress() }
-
- private fun checkFilters(
- type: VisualInterruptionType,
- entry: NotificationEntry
- ): VisualInterruptionFilter? =
- filters.firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) }
+ private fun checkFilters(type: VisualInterruptionType, entry: NotificationEntry) =
+ filters
+ .firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) }
+ ?.let { LoggableDecision.suppressed(it) }
}
private const val TAG = "VisualInterruptionDecisionProviderImpl"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index 1d2055e..e1581ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -44,7 +44,7 @@
statusBarStateController,
keyguardStateController,
headsUpManager,
- logger,
+ oldLogger,
mainHandler,
flags,
keyguardNotificationVisibilityProvider,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index 722b170..1064475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -37,7 +37,7 @@
headsUpManager,
keyguardNotificationVisibilityProvider,
keyguardStateController,
- logger,
+ newLogger,
mainHandler,
powerManager,
statusBarStateController,
@@ -222,14 +222,14 @@
private class TestCondition(
types: Set<VisualInterruptionType>,
val onShouldSuppress: () -> Boolean
- ) : VisualInterruptionCondition(types = types, reason = "") {
+ ) : VisualInterruptionCondition(types = types, reason = "test condition") {
override fun shouldSuppress(): Boolean = onShouldSuppress()
}
private class TestFilter(
types: Set<VisualInterruptionType>,
val onShouldSuppress: (NotificationEntry) -> Boolean = { true }
- ) : VisualInterruptionFilter(types = types, reason = "") {
+ ) : VisualInterruptionFilter(types = types, reason = "test filter") {
override fun shouldSuppress(entry: NotificationEntry) = onShouldSuppress(entry)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 0f29836..5e81156 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -20,7 +20,9 @@
import android.app.Notification
import android.app.Notification.BubbleMetadata
import android.app.Notification.FLAG_BUBBLE
+import android.app.Notification.FLAG_FOREGROUND_SERVICE
import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED
+import android.app.Notification.FLAG_USER_INITIATED_JOB
import android.app.Notification.GROUP_ALERT_ALL
import android.app.Notification.GROUP_ALERT_CHILDREN
import android.app.Notification.GROUP_ALERT_SUMMARY
@@ -47,6 +49,9 @@
import android.provider.Settings.Global.HEADS_UP_ON
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.statusbar.FakeStatusBarStateController
@@ -76,19 +81,35 @@
import org.mockito.Mockito.`when` as whenever
abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
+ private val fakeLogBuffer =
+ LogBuffer(
+ name = "FakeLog",
+ maxSize = 1,
+ logcatEchoTracker =
+ object : LogcatEchoTracker {
+ override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean =
+ true
+
+ override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
+ },
+ systrace = false
+ )
+
private val leakCheck = LeakCheckedTest.SysuiLeakCheck()
protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context)
protected val batteryController = FakeBatteryController(leakCheck)
protected val deviceProvisionedController = FakeDeviceProvisionedController()
protected val flags: NotifPipelineFlags = mock()
- protected val globalSettings = FakeGlobalSettings()
+ protected val globalSettings =
+ FakeGlobalSettings().also { it.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) }
protected val headsUpManager: HeadsUpManager = mock()
protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider =
mock()
protected val keyguardStateController = FakeKeyguardStateController(leakCheck)
- protected val logger: NotificationInterruptLogger = mock()
protected val mainHandler = FakeHandler(Looper.getMainLooper())
+ protected val newLogger = VisualInterruptionDecisionLogger(fakeLogBuffer)
+ protected val oldLogger = NotificationInterruptLogger(fakeLogBuffer)
protected val powerManager: PowerManager = mock()
protected val statusBarStateController = FakeStatusBarStateController()
protected val systemClock = FakeSystemClock()
@@ -116,14 +137,9 @@
@Before
fun setUp() {
- globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON)
-
val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0)
userTracker.set(listOf(user), /* currentUserIndex = */ 0)
- whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
- .thenReturn(false)
-
provider.start()
}
@@ -203,24 +219,64 @@
}
@Test
- fun testShouldPeek_notQuiteOldEnoughWhen() {
+ fun testShouldPeek_oldWhen_now() {
+ ensurePeekState()
+ assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(0) })
+ }
+
+ @Test
+ fun testShouldPeek_oldWhen_notOldEnough() {
ensurePeekState()
assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) })
}
@Test
- fun testShouldPeek_zeroWhen() {
+ fun testShouldPeek_oldWhen_zeroWhen() {
ensurePeekState()
assertShouldHeadsUp(buildPeekEntry { whenMs = 0L })
}
@Test
- fun testShouldPeek_oldWhenButFsi() {
+ fun testShouldPeek_oldWhen_negativeWhen() {
+ ensurePeekState()
+ assertShouldHeadsUp(buildPeekEntry { whenMs = -1L })
+ }
+
+ @Test
+ fun testShouldPeek_oldWhen_fullScreenIntent() {
ensurePeekState()
assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) })
}
@Test
+ fun testShouldPeek_oldWhen_foregroundService() {
+ ensurePeekState()
+ assertShouldHeadsUp(
+ buildPeekEntry {
+ whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS)
+ isForegroundService = true
+ }
+ )
+ }
+
+ @Test
+ fun testShouldPeek_oldWhen_userInitiatedJob() {
+ ensurePeekState()
+ assertShouldHeadsUp(
+ buildPeekEntry {
+ whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS)
+ isUserInitiatedJob = true
+ }
+ )
+ }
+
+ @Test
+ fun testShouldNotPeek_hiddenOnKeyguard() {
+ ensurePeekState({ keyguardShouldHideNotification = true })
+ assertShouldNotHeadsUp(buildPeekEntry())
+ }
+
+ @Test
fun testShouldPeek_defaultLegacySuppressor() {
ensurePeekState()
withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) }
@@ -257,36 +313,6 @@
}
@Test
- fun testShouldPulse_defaultLegacySuppressor() {
- ensurePulseState()
- withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) }
- }
-
- @Test
- fun testShouldNotPulse_legacySuppressInterruptions() {
- ensurePulseState()
- withLegacySuppressor(alwaysSuppressesInterruptions) {
- assertShouldNotHeadsUp(buildPulseEntry())
- }
- }
-
- @Test
- fun testShouldPulse_legacySuppressAwakeInterruptions() {
- ensurePulseState()
- withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
- assertShouldHeadsUp(buildPulseEntry())
- }
- }
-
- @Test
- fun testShouldPulse_legacySuppressAwakeHeadsUp() {
- ensurePulseState()
- withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
- assertShouldHeadsUp(buildPulseEntry())
- }
- }
-
- @Test
fun testShouldNotPulse_disabled() {
ensurePulseState { pulseOnNotificationsEnabled = false }
assertShouldNotHeadsUp(buildPulseEntry())
@@ -318,6 +344,42 @@
assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW })
}
+ @Test
+ fun testShouldNotPulse_hiddenOnKeyguard() {
+ ensurePulseState({ keyguardShouldHideNotification = true })
+ assertShouldNotHeadsUp(buildPulseEntry())
+ }
+
+ @Test
+ fun testShouldPulse_defaultLegacySuppressor() {
+ ensurePulseState()
+ withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) }
+ }
+
+ @Test
+ fun testShouldNotPulse_legacySuppressInterruptions() {
+ ensurePulseState()
+ withLegacySuppressor(alwaysSuppressesInterruptions) {
+ assertShouldNotHeadsUp(buildPulseEntry())
+ }
+ }
+
+ @Test
+ fun testShouldPulse_legacySuppressAwakeInterruptions() {
+ ensurePulseState()
+ withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
+ assertShouldHeadsUp(buildPulseEntry())
+ }
+ }
+
+ @Test
+ fun testShouldPulse_legacySuppressAwakeHeadsUp() {
+ ensurePulseState()
+ withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
+ assertShouldHeadsUp(buildPulseEntry())
+ }
+ }
+
private fun withPeekAndPulseEntry(
extendEntry: EntryBuilder.() -> Unit,
block: (NotificationEntry) -> Unit
@@ -330,73 +392,7 @@
}
@Test
- fun testShouldHeadsUp_groupedSummaryNotif_groupAlertAll() {
- withPeekAndPulseEntry({
- isGrouped = true
- isGroupSummary = true
- groupAlertBehavior = GROUP_ALERT_ALL
- }) {
- assertShouldHeadsUp(it)
- }
- }
-
- @Test
- fun testShouldHeadsUp_groupedSummaryNotif_groupAlertSummary() {
- withPeekAndPulseEntry({
- isGrouped = true
- isGroupSummary = true
- groupAlertBehavior = GROUP_ALERT_SUMMARY
- }) {
- assertShouldHeadsUp(it)
- }
- }
-
- @Test
- fun testShouldNotHeadsUp_groupedSummaryNotif_groupAlertChildren() {
- withPeekAndPulseEntry({
- isGrouped = true
- isGroupSummary = true
- groupAlertBehavior = GROUP_ALERT_CHILDREN
- }) {
- assertShouldNotHeadsUp(it)
- }
- }
-
- @Test
- fun testShouldHeadsUp_ungroupedSummaryNotif_groupAlertChildren() {
- withPeekAndPulseEntry({
- isGrouped = false
- isGroupSummary = true
- groupAlertBehavior = GROUP_ALERT_CHILDREN
- }) {
- assertShouldHeadsUp(it)
- }
- }
-
- @Test
- fun testShouldHeadsUp_groupedChildNotif_groupAlertAll() {
- withPeekAndPulseEntry({
- isGrouped = true
- isGroupSummary = false
- groupAlertBehavior = GROUP_ALERT_ALL
- }) {
- assertShouldHeadsUp(it)
- }
- }
-
- @Test
- fun testShouldHeadsUp_groupedChildNotif_groupAlertChildren() {
- withPeekAndPulseEntry({
- isGrouped = true
- isGroupSummary = false
- groupAlertBehavior = GROUP_ALERT_CHILDREN
- }) {
- assertShouldHeadsUp(it)
- }
- }
-
- @Test
- fun testShouldNotHeadsUp_groupedChildNotif_groupAlertSummary() {
+ fun testShouldNotHeadsUp_suppressiveGroupAlertBehavior() {
withPeekAndPulseEntry({
isGrouped = true
isGroupSummary = false
@@ -407,7 +403,18 @@
}
@Test
- fun testShouldHeadsUp_ungroupedChildNotif_groupAlertSummary() {
+ fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notSuppressive() {
+ withPeekAndPulseEntry({
+ isGrouped = true
+ isGroupSummary = false
+ groupAlertBehavior = GROUP_ALERT_CHILDREN
+ }) {
+ assertShouldHeadsUp(it)
+ }
+ }
+
+ @Test
+ fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notGrouped() {
withPeekAndPulseEntry({
isGrouped = false
isGroupSummary = false
@@ -435,18 +442,41 @@
}
@Test
- fun testShouldNotBubble_notAllowed() {
+ fun testShouldBubble_suppressiveGroupAlertBehavior() {
ensureBubbleState()
- assertShouldNotBubble(buildBubbleEntry { canBubble = false })
+ assertShouldBubble(
+ buildBubbleEntry {
+ isGrouped = true
+ isGroupSummary = false
+ groupAlertBehavior = GROUP_ALERT_SUMMARY
+ }
+ )
}
@Test
- fun testShouldNotBubble_noBubbleMetadata() {
+ fun testShouldNotBubble_notABubble() {
+ ensureBubbleState()
+ assertShouldNotBubble(
+ buildBubbleEntry {
+ isBubble = false
+ hasBubbleMetadata = false
+ }
+ )
+ }
+
+ @Test
+ fun testShouldNotBubble_missingBubbleMetadata() {
ensureBubbleState()
assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false })
}
@Test
+ fun testShouldNotBubble_notAllowedToBubble() {
+ ensureBubbleState()
+ assertShouldNotBubble(buildBubbleEntry { canBubble = false })
+ }
+
+ @Test
fun testShouldBubble_defaultLegacySuppressor() {
ensureBubbleState()
withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) }
@@ -477,13 +507,7 @@
}
@Test
- fun testShouldNotAlert_hiddenOnKeyguard() {
- ensurePeekState({ keyguardShouldHideNotification = true })
- assertShouldNotHeadsUp(buildPeekEntry())
-
- ensurePulseState({ keyguardShouldHideNotification = true })
- assertShouldNotHeadsUp(buildPulseEntry())
-
+ fun testShouldNotBubble_hiddenOnKeyguard() {
ensureBubbleState({ keyguardShouldHideNotification = true })
assertShouldNotBubble(buildBubbleEntry())
}
@@ -855,12 +879,12 @@
}
protected fun assertShouldHeadsUp(entry: NotificationEntry) =
- provider.makeUnloggedHeadsUpDecision(entry).let {
+ provider.makeAndLogHeadsUpDecision(entry).let {
assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt)
}
protected fun assertShouldNotHeadsUp(entry: NotificationEntry) =
- provider.makeUnloggedHeadsUpDecision(entry).let {
+ provider.makeAndLogHeadsUpDecision(entry).let {
assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt)
}
@@ -876,6 +900,7 @@
protected fun assertShouldFsi(entry: NotificationEntry) =
provider.makeUnloggedFullScreenIntentDecision(entry).let {
+ provider.logFullScreenIntentDecision(it)
assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt)
}
@@ -884,10 +909,11 @@
expectWouldInterruptWithoutDnd: Boolean? = null
) =
provider.makeUnloggedFullScreenIntentDecision(entry).let {
+ provider.logFullScreenIntentDecision(it)
assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt)
if (expectWouldInterruptWithoutDnd != null) {
assertEquals(
- "unexpected unsuppressed-without-DND FSI: ${it.logReason}",
+ "unexpected wouldInterruptWithoutDnd for FSI: ${it.logReason}",
expectWouldInterruptWithoutDnd,
it.wouldInterruptWithoutDnd
)
@@ -895,22 +921,35 @@
}
protected class EntryBuilder(val context: Context) {
- var importance = IMPORTANCE_DEFAULT
- var suppressedVisualEffects: Int? = null
- var whenMs: Long? = null
- var visibilityOverride: Int? = null
- var hasFsi = false
- var canBubble: Boolean? = null
- var isBubble = false
- var hasBubbleMetadata = false
+ // Set on BubbleMetadata:
var bubbleIsShortcut = false
- var bubbleSuppressesNotification: Boolean? = null
+ var bubbleSuppressesNotification = false
+
+ // Set on Notification.Builder:
+ var whenMs: Long? = null
var isGrouped = false
- var isGroupSummary: Boolean? = null
+ var isGroupSummary = false
var groupAlertBehavior: Int? = null
- var hasJustLaunchedFsi = false
+ var hasBubbleMetadata = false
+ var hasFsi = false
+
+ // Set on Notification:
+ var isForegroundService = false
+ var isUserInitiatedJob = false
+ var isBubble = false
var isStickyAndNotDemoted = false
- var packageSuspended: Boolean? = null
+
+ // Set on NotificationEntryBuilder:
+ var importance = IMPORTANCE_DEFAULT
+ var canBubble: Boolean? = null
+
+ // Set on NotificationEntry:
+ var hasJustLaunchedFsi = false
+
+ // Set on ModifiedRankingBuilder:
+ var packageSuspended = false
+ var visibilityOverride: Int? = null
+ var suppressedVisualEffects: Int? = null
private fun buildBubbleMetadata(): BubbleMetadata {
val builder =
@@ -928,71 +967,87 @@
)
}
- bubbleSuppressesNotification?.let { builder.setSuppressNotification(it) }
+ if (bubbleSuppressesNotification) {
+ builder.setSuppressNotification(true)
+ }
return builder.build()
}
fun build() =
Notification.Builder(context, TEST_CHANNEL_ID)
- .apply {
- setContentTitle(TEST_CONTENT_TITLE)
- setContentText(TEST_CONTENT_TEXT)
+ .also { nb ->
+ nb.setContentTitle(TEST_CONTENT_TITLE)
+ nb.setContentText(TEST_CONTENT_TEXT)
- if (hasFsi) {
- setFullScreenIntent(mock(), /* highPriority = */ true)
- }
-
- whenMs?.let { setWhen(it) }
-
- if (hasBubbleMetadata) {
- setBubbleMetadata(buildBubbleMetadata())
- }
+ whenMs?.let { nb.setWhen(it) }
if (isGrouped) {
- setGroup(TEST_GROUP_KEY)
+ nb.setGroup(TEST_GROUP_KEY)
}
- isGroupSummary?.let { setGroupSummary(it) }
+ if (isGroupSummary) {
+ nb.setGroupSummary(true)
+ }
- groupAlertBehavior?.let { setGroupAlertBehavior(it) }
+ groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) }
+
+ if (hasBubbleMetadata) {
+ nb.setBubbleMetadata(buildBubbleMetadata())
+ }
+
+ if (hasFsi) {
+ nb.setFullScreenIntent(mock(), /* highPriority = */ true)
+ }
}
.build()
- .apply {
+ .also { n ->
+ if (isForegroundService) {
+ n.flags = n.flags or FLAG_FOREGROUND_SERVICE
+ }
+
+ if (isUserInitiatedJob) {
+ n.flags = n.flags or FLAG_USER_INITIATED_JOB
+ }
+
if (isBubble) {
- flags = flags or FLAG_BUBBLE
+ n.flags = n.flags or FLAG_BUBBLE
}
if (isStickyAndNotDemoted) {
- flags = flags or FLAG_FSI_REQUESTED_BUT_DENIED
+ n.flags = n.flags or FLAG_FSI_REQUESTED_BUT_DENIED
}
}
.let { NotificationEntryBuilder().setNotification(it) }
- .apply {
- setPkg(TEST_PACKAGE)
- setOpPkg(TEST_PACKAGE)
- setTag(TEST_TAG)
+ .also { neb ->
+ neb.setPkg(TEST_PACKAGE)
+ neb.setOpPkg(TEST_PACKAGE)
+ neb.setTag(TEST_TAG)
- setImportance(importance)
- setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance))
+ neb.setImportance(importance)
+ neb.setChannel(
+ NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+ )
- canBubble?.let { setCanBubble(it) }
+ canBubble?.let { neb.setCanBubble(it) }
}
.build()!!
- .also {
+ .also { ne ->
if (hasJustLaunchedFsi) {
- it.notifyFullScreenIntentLaunched()
+ ne.notifyFullScreenIntentLaunched()
}
if (isStickyAndNotDemoted) {
- assertFalse(it.isDemoted)
+ assertFalse(ne.isDemoted)
}
- modifyRanking(it)
- .apply {
- suppressedVisualEffects?.let { setSuppressedVisualEffects(it) }
- visibilityOverride?.let { setVisibilityOverride(it) }
- packageSuspended?.let { setSuspended(it) }
+ modifyRanking(ne)
+ .also { mrb ->
+ if (packageSuspended) {
+ mrb.setSuspended(true)
+ }
+ visibilityOverride?.let { mrb.setVisibilityOverride(it) }
+ suppressedVisualEffects?.let { mrb.setSuppressedVisualEffects(it) }
}
.build()
}
@@ -1013,6 +1068,7 @@
}
protected fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
+ isBubble = true
canBubble = true
hasBubbleMetadata = true
run(block)