1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.notification.interruption
18 
19 import android.app.ActivityManager
20 import android.app.Notification
21 import android.app.Notification.BubbleMetadata
22 import android.app.Notification.EXTRA_COLORIZED
23 import android.app.Notification.EXTRA_TEMPLATE
24 import android.app.Notification.FLAG_BUBBLE
25 import android.app.Notification.FLAG_CAN_COLORIZE
26 import android.app.Notification.FLAG_FOREGROUND_SERVICE
27 import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED
28 import android.app.Notification.FLAG_USER_INITIATED_JOB
29 import android.app.Notification.GROUP_ALERT_ALL
30 import android.app.Notification.GROUP_ALERT_CHILDREN
31 import android.app.Notification.GROUP_ALERT_SUMMARY
32 import android.app.Notification.VISIBILITY_PRIVATE
33 import android.app.NotificationChannel
34 import android.app.NotificationManager
35 import android.app.NotificationManager.IMPORTANCE_DEFAULT
36 import android.app.NotificationManager.IMPORTANCE_HIGH
37 import android.app.NotificationManager.IMPORTANCE_LOW
38 import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT
39 import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
40 import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK
41 import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
42 import android.app.PendingIntent
43 import android.app.PendingIntent.FLAG_MUTABLE
44 import android.content.Context
45 import android.content.Intent
46 import android.content.pm.PackageManager
47 import android.content.pm.UserInfo
48 import android.graphics.drawable.Icon
49 import android.hardware.display.FakeAmbientDisplayConfiguration
50 import android.os.Looper
51 import android.os.PowerManager
52 import android.platform.test.annotations.EnableFlags
53 import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
54 import android.provider.Settings.Global.HEADS_UP_OFF
55 import android.provider.Settings.Global.HEADS_UP_ON
56 import com.android.internal.logging.UiEventLogger.UiEventEnum
57 import com.android.internal.logging.testing.UiEventLoggerFake
58 import com.android.systemui.SysuiTestCase
59 import com.android.systemui.log.LogBuffer
60 import com.android.systemui.log.LogcatEchoTracker
61 import com.android.systemui.log.core.LogLevel
62 import com.android.systemui.res.R
63 import com.android.systemui.settings.FakeUserTracker
64 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
65 import com.android.systemui.statusbar.FakeStatusBarStateController
66 import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
67 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
68 import com.android.systemui.statusbar.StatusBarState.SHADE
69 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
70 import com.android.systemui.statusbar.notification.NotifPipelineFlags
71 import com.android.systemui.statusbar.notification.collection.NotificationEntry
72 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
73 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
74 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
75 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD
76 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA
77 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
78 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN
79 import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController
80 import com.android.systemui.util.FakeEventLog
81 import com.android.systemui.util.settings.FakeGlobalSettings
82 import com.android.systemui.util.settings.FakeSettings
83 import com.android.systemui.util.settings.SystemSettings
84 import com.android.systemui.util.time.FakeSystemClock
85 import com.android.systemui.utils.leaks.FakeBatteryController
86 import com.android.systemui.utils.leaks.FakeKeyguardStateController
87 import com.android.systemui.utils.leaks.LeakCheckedTest
88 import com.android.systemui.utils.os.FakeHandler
89 import com.android.wm.shell.bubbles.Bubbles
90 import junit.framework.Assert.assertFalse
91 import junit.framework.Assert.assertTrue
92 import kotlinx.coroutines.flow.MutableStateFlow
93 import org.junit.Assert.assertEquals
94 import org.junit.Before
95 import org.junit.Test
96 import org.mockito.kotlin.any
97 import org.mockito.kotlin.mock
98 import org.mockito.kotlin.whenever
99 
100 abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
101     private val fakeLogBuffer =
102         LogBuffer(
103             name = "FakeLog",
104             maxSize = 1,
105             logcatEchoTracker =
106                 object : LogcatEchoTracker {
107                     override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean =
108                         true
109 
110                     override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
111                 },
112             systrace = false,
113         )
114 
115     private val leakCheck = LeakCheckedTest.SysuiLeakCheck()
116 
117     protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context)
118     protected val batteryController = FakeBatteryController(leakCheck)
119     protected val deviceProvisionedController = FakeDeviceProvisionedController()
120     protected val eventLog = FakeEventLog()
121     protected val flags: NotifPipelineFlags = mock()
122     protected val globalSettings =
123         FakeGlobalSettings().also { it.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) }
124     protected val headsUpManager: HeadsUpManager = mock()
125     protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider =
126         mock()
127     protected val keyguardStateController = FakeKeyguardStateController(leakCheck)
128     protected val mainHandler = FakeHandler(Looper.getMainLooper())
129     protected val newLogger = VisualInterruptionDecisionLogger(fakeLogBuffer)
130     protected val oldLogger = NotificationInterruptLogger(fakeLogBuffer)
131     protected val powerManager: PowerManager = mock()
132     protected val statusBarStateController = FakeStatusBarStateController()
133     protected val systemClock = FakeSystemClock()
134     protected val uiEventLogger = UiEventLoggerFake()
135     protected val userTracker = FakeUserTracker()
136     protected val avalancheProvider: AvalancheProvider = mock()
137     protected val bubbles: Bubbles = mock()
138     lateinit var systemSettings: SystemSettings
139     protected val settingsInteractor: NotificationSettingsInteractor = mock()
140     protected val packageManager: PackageManager = mock()
141     protected val notificationManager: NotificationManager = mock()
142     protected val logger: VisualInterruptionDecisionLogger = mock()
143     protected abstract val provider: VisualInterruptionDecisionProvider
144 
145     private val neverSuppresses = object : NotificationInterruptSuppressor {}
146 
147     private val alwaysSuppressesInterruptions =
148         object : NotificationInterruptSuppressor {
149             override fun suppressInterruptions(entry: NotificationEntry?) = true
150         }
151 
152     private val alwaysSuppressesAwakeInterruptions =
153         object : NotificationInterruptSuppressor {
154             override fun suppressAwakeInterruptions(entry: NotificationEntry?) = true
155         }
156 
157     private val alwaysSuppressesAwakeHeadsUp =
158         object : NotificationInterruptSuppressor {
159             override fun suppressAwakeHeadsUp(entry: NotificationEntry?) = true
160         }
161 
162     @Before
163     fun setUp() {
164         val userId = ActivityManager.getCurrentUser()
165         val user = UserInfo(userId, "Current user", /* flags= */ 0)
166 
167         deviceProvisionedController.currentUser = userId
168         userTracker.set(listOf(user), /* currentUserIndex= */ 0)
169         systemSettings = FakeSettings()
170         whenever(bubbles.canShowBubbleNotification()).thenReturn(true)
171         whenever(settingsInteractor.isCooldownEnabled).thenReturn(MutableStateFlow(true))
172         provider.start()
173     }
174 
175     @Test
176     fun testShouldPeek() {
177         ensurePeekState()
178         assertShouldHeadsUp(buildPeekEntry())
179         assertNoEventsLogged()
180     }
181 
182     @Test
183     fun testShouldNotPeek_settingDisabled() {
184         ensurePeekState { hunSettingEnabled = false }
185         assertShouldNotHeadsUp(buildPeekEntry())
186         assertNoEventsLogged()
187     }
188 
189     @Test
190     fun testShouldNotPeek_packageSnoozed_withoutFsi() {
191         ensurePeekState { hunSnoozed = true }
192         assertShouldNotHeadsUp(buildPeekEntry())
193         assertNoEventsLogged()
194     }
195 
196     @Test
197     fun testShouldPeek_packageSnoozed_withFsi() {
198         val entry = buildFsiEntry()
199         forEachPeekableFsiState {
200             ensurePeekState { hunSnoozed = true }
201             assertShouldHeadsUp(entry)
202 
203             // The old code logs a UiEvent when a HUN snooze is bypassed because the notification
204             // has an FSI, but that doesn't fit into the new code's suppressor-based logic, so we're
205             // not reimplementing it.
206             if (provider !is NotificationInterruptStateProviderWrapper) {
207                 assertNoEventsLogged()
208             }
209         }
210     }
211 
212     @Test
213     fun testShouldNotPeek_alreadyBubbled() {
214         ensurePeekState { statusBarState = SHADE }
215         assertShouldNotHeadsUp(buildPeekEntry { isBubble = true })
216         assertNoEventsLogged()
217     }
218 
219     @Test
220     fun testShouldPeek_bubblesCannotShowNotification() {
221         whenever(bubbles.canShowBubbleNotification()).thenReturn(false)
222         ensurePeekState { statusBarState = SHADE }
223         assertShouldHeadsUp(buildPeekEntry { isBubble = true })
224         assertNoEventsLogged()
225     }
226 
227     @Test
228     fun testShouldPeek_isBubble_shadeLocked() {
229         ensurePeekState { statusBarState = SHADE_LOCKED }
230         assertShouldHeadsUp(buildPeekEntry { isBubble = true })
231         assertNoEventsLogged()
232     }
233 
234     @Test
235     fun testShouldPeek_isBubble_keyguard() {
236         ensurePeekState { statusBarState = KEYGUARD }
237         assertShouldHeadsUp(buildPeekEntry { isBubble = true })
238         assertNoEventsLogged()
239     }
240 
241     @Test
242     fun testShouldNotPeek_dnd() {
243         ensurePeekState()
244         assertShouldNotHeadsUp(buildPeekEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK })
245         assertNoEventsLogged()
246     }
247 
248     @Test
249     fun testShouldNotPeek_notImportant() {
250         ensurePeekState()
251         assertShouldNotHeadsUp(buildPeekEntry { importance = IMPORTANCE_DEFAULT })
252         assertNoEventsLogged()
253     }
254 
255     @Test
256     fun testShouldNotPeek_screenOff() {
257         ensurePeekState { isScreenOn = false }
258         assertShouldNotHeadsUp(buildPeekEntry())
259         assertNoEventsLogged()
260     }
261 
262     @Test
263     fun testShouldNotPeek_dreaming() {
264         ensurePeekState { isDreaming = true }
265         assertShouldNotHeadsUp(buildPeekEntry())
266         assertNoEventsLogged()
267     }
268 
269     @Test
270     fun testShouldNotPeek_oldWhen() {
271         ensurePeekState()
272         assertShouldNotHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) })
273     }
274 
275     @Test
276     fun testLogsHunOldWhen() {
277         assertNoEventsLogged()
278 
279         ensurePeekState()
280         val entry = buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }
281 
282         // The old code logs the "old when" UiEvent unconditionally, so don't expect that it hasn't.
283         if (provider !is NotificationInterruptStateProviderWrapper) {
284             provider.makeUnloggedHeadsUpDecision(entry)
285             assertNoEventsLogged()
286         }
287 
288         provider.makeAndLogHeadsUpDecision(entry)
289         assertUiEventLogged(HUN_SUPPRESSED_OLD_WHEN, entry.sbn.uid, entry.sbn.packageName)
290         assertNoSystemEventLogged()
291     }
292 
293     @Test
294     fun testShouldPeek_oldWhen_now() {
295         ensurePeekState()
296         assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(0) })
297         assertNoEventsLogged()
298     }
299 
300     @Test
301     fun testShouldPeek_oldWhen_notOldEnough() {
302         ensurePeekState()
303         assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) })
304         assertNoEventsLogged()
305     }
306 
307     @Test
308     fun testShouldPeek_oldWhen_zeroWhen() {
309         ensurePeekState()
310         assertShouldHeadsUp(buildPeekEntry { whenMs = 0L })
311         assertNoEventsLogged()
312     }
313 
314     @Test
315     fun testShouldPeek_oldWhen_negativeWhen() {
316         ensurePeekState()
317         assertShouldHeadsUp(buildPeekEntry { whenMs = -1L })
318         assertNoEventsLogged()
319     }
320 
321     @Test
322     fun testShouldPeek_oldWhen_fullScreenIntent() {
323         ensurePeekState()
324         assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) })
325         assertNoEventsLogged()
326     }
327 
328     @Test
329     fun testShouldPeek_oldWhen_foregroundService() {
330         ensurePeekState()
331         assertShouldHeadsUp(
332             buildPeekEntry {
333                 whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS)
334                 isForegroundService = true
335             }
336         )
337         assertNoEventsLogged()
338     }
339 
340     @Test
341     fun testShouldPeek_oldWhen_userInitiatedJob() {
342         ensurePeekState()
343         assertShouldHeadsUp(
344             buildPeekEntry {
345                 whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS)
346                 isUserInitiatedJob = true
347             }
348         )
349         assertNoEventsLogged()
350     }
351 
352     @Test
353     fun testShouldNotPeek_appSuspended() {
354         ensurePeekState()
355         assertShouldNotBubble(buildPeekEntry { packageSuspended = true })
356         assertNoEventsLogged()
357     }
358 
359     @Test
360     fun testShouldNotPeek_hiddenOnKeyguard() {
361         ensurePeekState({ keyguardShouldHideNotification = true })
362         assertShouldNotHeadsUp(buildPeekEntry())
363         assertNoEventsLogged()
364     }
365 
366     @Test
367     fun testShouldPeek_defaultLegacySuppressor() {
368         ensurePeekState()
369         withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) }
370         assertNoEventsLogged()
371     }
372 
373     @Test
374     fun testShouldNotPeek_legacySuppressInterruptions() {
375         ensurePeekState()
376         withLegacySuppressor(alwaysSuppressesInterruptions) {
377             assertShouldNotHeadsUp(buildPeekEntry())
378         }
379         assertNoEventsLogged()
380     }
381 
382     @Test
383     fun testShouldNotPeek_legacySuppressAwakeInterruptions() {
384         ensurePeekState()
385         withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
386             assertShouldNotHeadsUp(buildPeekEntry())
387         }
388         assertNoEventsLogged()
389     }
390 
391     @Test
392     fun testShouldNotPeek_legacySuppressAwakeHeadsUp() {
393         ensurePeekState()
394         withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
395             assertShouldNotHeadsUp(buildPeekEntry())
396         }
397         assertNoEventsLogged()
398     }
399 
400     @Test
401     fun testShouldPulse() {
402         ensurePulseState()
403         assertShouldHeadsUp(buildPulseEntry())
404         assertNoEventsLogged()
405     }
406 
407     @Test
408     fun testShouldNotPulse_disabled() {
409         ensurePulseState { pulseOnNotificationsEnabled = false }
410         assertShouldNotHeadsUp(buildPulseEntry())
411         assertNoEventsLogged()
412     }
413 
414     @Test
415     fun testShouldNotPulse_batterySaver() {
416         ensurePulseState { isAodPowerSave = true }
417         assertShouldNotHeadsUp(buildPulseEntry())
418         assertNoEventsLogged()
419     }
420 
421     @Test
422     fun testShouldNotPulse_effectSuppressed() {
423         ensurePulseState()
424         assertShouldNotHeadsUp(
425             buildPulseEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_AMBIENT }
426         )
427         assertNoEventsLogged()
428     }
429 
430     @Test
431     fun testShouldNotPulse_visibilityOverridePrivate() {
432         ensurePulseState()
433         assertShouldNotHeadsUp(buildPulseEntry { visibilityOverride = VISIBILITY_PRIVATE })
434         assertNoEventsLogged()
435     }
436 
437     @Test
438     fun testShouldNotPulse_importanceLow() {
439         ensurePulseState()
440         assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW })
441         assertNoEventsLogged()
442     }
443 
444     @Test
445     fun testShouldNotPulse_appSuspended() {
446         ensurePulseState()
447         assertShouldNotHeadsUp(buildPulseEntry { packageSuspended = true })
448         assertNoEventsLogged()
449     }
450 
451     @Test
452     fun testShouldNotPulse_hiddenOnKeyguard() {
453         ensurePulseState({ keyguardShouldHideNotification = true })
454         assertShouldNotHeadsUp(buildPulseEntry())
455         assertNoEventsLogged()
456     }
457 
458     @Test
459     fun testShouldPulse_defaultLegacySuppressor() {
460         ensurePulseState()
461         withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) }
462         assertNoEventsLogged()
463     }
464 
465     @Test
466     fun testShouldNotPulse_legacySuppressInterruptions() {
467         ensurePulseState()
468         withLegacySuppressor(alwaysSuppressesInterruptions) {
469             assertShouldNotHeadsUp(buildPulseEntry())
470         }
471         assertNoEventsLogged()
472     }
473 
474     @Test
475     fun testShouldPulse_legacySuppressAwakeInterruptions() {
476         ensurePulseState()
477         withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
478             assertShouldHeadsUp(buildPulseEntry())
479         }
480         assertNoEventsLogged()
481     }
482 
483     @Test
484     fun testShouldPulse_legacySuppressAwakeHeadsUp() {
485         ensurePulseState()
486         withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
487             assertShouldHeadsUp(buildPulseEntry())
488         }
489         assertNoEventsLogged()
490     }
491 
492     private fun withPeekAndPulseEntry(
493         extendEntry: EntryBuilder.() -> Unit,
494         block: (NotificationEntry) -> Unit,
495     ) {
496         ensurePeekState()
497         block(buildPeekEntry(extendEntry))
498 
499         ensurePulseState()
500         block(buildPulseEntry(extendEntry))
501     }
502 
503     @Test
504     fun testShouldNotHeadsUp_suppressiveGroupAlertBehavior() {
505         withPeekAndPulseEntry({
506             isGrouped = true
507             isGroupSummary = false
508             groupAlertBehavior = GROUP_ALERT_SUMMARY
509         }) {
510             assertShouldNotHeadsUp(it)
511             assertNoEventsLogged()
512         }
513     }
514 
515     @Test
516     fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notSuppressive() {
517         withPeekAndPulseEntry({
518             isGrouped = true
519             isGroupSummary = false
520             groupAlertBehavior = GROUP_ALERT_CHILDREN
521         }) {
522             assertShouldHeadsUp(it)
523             assertNoEventsLogged()
524         }
525     }
526 
527     @Test
528     fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notGrouped() {
529         withPeekAndPulseEntry({
530             isGrouped = false
531             isGroupSummary = false
532             groupAlertBehavior = GROUP_ALERT_SUMMARY
533         }) {
534             assertShouldHeadsUp(it)
535             assertNoEventsLogged()
536         }
537     }
538 
539     @Test
540     @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
541     fun testShouldNotHeadsUp_silentNotification() {
542         withPeekAndPulseEntry({
543             isGrouped = false
544             isGroupSummary = false
545             isSilent = true
546         }) {
547             assertShouldNotHeadsUp(it)
548             assertNoEventsLogged()
549         }
550     }
551 
552     @Test
553     @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
554     fun testShouldHeadsUp_silentNotificationFalse() {
555         withPeekAndPulseEntry({
556             isGrouped = false
557             isGroupSummary = false
558             isSilent = false
559         }) {
560             assertShouldHeadsUp(it)
561             assertNoEventsLogged()
562         }
563     }
564 
565     @Test
566     fun testShouldNotHeadsUp_justLaunchedFsi() {
567         withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) {
568             assertShouldNotHeadsUp(it)
569             assertNoEventsLogged()
570         }
571     }
572 
573     @Test
574     fun testShouldBubble_withIntentAndIcon() {
575         ensureBubbleState()
576         assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = false })
577         assertNoEventsLogged()
578     }
579 
580     @Test
581     fun testShouldBubble_withShortcut() {
582         ensureBubbleState()
583         assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = true })
584         assertNoEventsLogged()
585     }
586 
587     @Test
588     fun testShouldBubble_suppressiveGroupAlertBehavior() {
589         ensureBubbleState()
590         assertShouldBubble(
591             buildBubbleEntry {
592                 isGrouped = true
593                 isGroupSummary = false
594                 groupAlertBehavior = GROUP_ALERT_SUMMARY
595             }
596         )
597         assertNoEventsLogged()
598     }
599 
600     @Test
601     fun testShouldNotBubble_notABubble() {
602         ensureBubbleState()
603         assertShouldNotBubble(
604             buildBubbleEntry {
605                 isBubble = false
606                 hasBubbleMetadata = false
607             }
608         )
609         assertNoEventsLogged()
610     }
611 
612     @Test
613     fun testShouldNotBubble_missingBubbleMetadata() {
614         ensureBubbleState()
615         assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false })
616         assertNoEventsLogged()
617     }
618 
619     @Test
620     fun testShouldNotBubble_notAllowedToBubble() {
621         ensureBubbleState()
622         assertShouldNotBubble(buildBubbleEntry { canBubble = false })
623         assertNoEventsLogged()
624     }
625 
626     @Test
627     fun testShouldBubble_defaultLegacySuppressor() {
628         ensureBubbleState()
629         withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) }
630         assertNoEventsLogged()
631     }
632 
633     @Test
634     fun testShouldNotBubble_legacySuppressInterruptions() {
635         ensureBubbleState()
636         withLegacySuppressor(alwaysSuppressesInterruptions) {
637             assertShouldNotBubble(buildBubbleEntry())
638         }
639         assertNoEventsLogged()
640     }
641 
642     @Test
643     fun testShouldNotBubble_legacySuppressAwakeInterruptions() {
644         ensureBubbleState()
645         withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
646             assertShouldNotBubble(buildBubbleEntry())
647         }
648         assertNoEventsLogged()
649     }
650 
651     @Test
652     fun testShouldBubble_legacySuppressAwakeHeadsUp() {
653         ensureBubbleState()
654         withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
655             assertShouldBubble(buildBubbleEntry())
656         }
657         assertNoEventsLogged()
658     }
659 
660     @Test
661     fun testShouldNotBubble_appSuspended() {
662         ensureBubbleState()
663         assertShouldNotBubble(buildBubbleEntry { packageSuspended = true })
664         assertNoEventsLogged()
665     }
666 
667     @Test
668     fun testShouldNotBubble_hiddenOnKeyguard() {
669         ensureBubbleState({ keyguardShouldHideNotification = true })
670         assertShouldNotBubble(buildBubbleEntry())
671         assertNoEventsLogged()
672     }
673 
674     @Test
675     fun testShouldNotFsi_noFullScreenIntent() {
676         forEachFsiState {
677             assertShouldNotFsi(buildFsiEntry { hasFsi = false })
678             assertNoEventsLogged()
679         }
680     }
681 
682     @Test
683     fun testShouldNotFsi_showStickyHun() {
684         forEachFsiState {
685             assertShouldNotFsi(
686                 buildFsiEntry {
687                     hasFsi = false
688                     isStickyAndNotDemoted = true
689                 }
690             )
691             assertNoEventsLogged()
692         }
693     }
694 
695     @Test
696     fun testShouldNotFsi_onlyDnd() {
697         forEachFsiState {
698             assertShouldNotFsi(
699                 buildFsiEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT },
700                 expectWouldInterruptWithoutDnd = true,
701             )
702             assertNoEventsLogged()
703         }
704     }
705 
706     @Test
707     fun testShouldNotFsi_notImportantEnough() {
708         forEachFsiState {
709             assertShouldNotFsi(buildFsiEntry { importance = IMPORTANCE_DEFAULT })
710             assertNoEventsLogged()
711         }
712     }
713 
714     @Test
715     fun testShouldNotFsi_notOnlyDnd() {
716         forEachFsiState {
717             assertShouldNotFsi(
718                 buildFsiEntry {
719                     suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
720                     importance = IMPORTANCE_DEFAULT
721                 },
722                 expectWouldInterruptWithoutDnd = false,
723             )
724             assertNoEventsLogged()
725         }
726     }
727 
728     @Test
729     fun testShouldNotFsi_suppressiveGroupAlertBehavior() {
730         forEachFsiState {
731             assertShouldNotFsi(
732                 buildFsiEntry {
733                     isGrouped = true
734                     isGroupSummary = true
735                     groupAlertBehavior = GROUP_ALERT_CHILDREN
736                 }
737             )
738         }
739     }
740 
741     @Test
742     fun testLogsFsiSuppressiveGroupAlertBehavior() {
743         ensureNotInteractiveFsiState()
744         val entry = buildFsiEntry {
745             isGrouped = true
746             isGroupSummary = true
747             groupAlertBehavior = GROUP_ALERT_CHILDREN
748         }
749 
750         val decision = provider.makeUnloggedFullScreenIntentDecision(entry)
751         assertNoEventsLogged()
752 
753         provider.logFullScreenIntentDecision(decision)
754         assertUiEventLogged(
755             FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
756             entry.sbn.uid,
757             entry.sbn.packageName,
758         )
759         assertSystemEventLogged("231322873", entry.sbn.uid, "groupAlertBehavior")
760     }
761 
762     @Test
763     fun testShouldFsi_suppressiveGroupAlertBehavior_notGrouped() {
764         forEachFsiState {
765             assertShouldFsi(
766                 buildFsiEntry {
767                     isGrouped = false
768                     isGroupSummary = true
769                     groupAlertBehavior = GROUP_ALERT_CHILDREN
770                 }
771             )
772             assertNoEventsLogged()
773         }
774     }
775 
776     @Test
777     fun testShouldFsi_suppressiveGroupAlertBehavior_notSuppressive() {
778         forEachFsiState {
779             assertShouldFsi(
780                 buildFsiEntry {
781                     isGrouped = true
782                     isGroupSummary = true
783                     groupAlertBehavior = GROUP_ALERT_ALL
784                 }
785             )
786         }
787     }
788 
789     @Test
790     fun testShouldNotFsi_suppressiveBubbleMetadata() {
791         forEachFsiState {
792             assertShouldNotFsi(
793                 buildFsiEntry {
794                     hasBubbleMetadata = true
795                     bubbleSuppressesNotification = true
796                 }
797             )
798         }
799     }
800 
801     @Test
802     fun testLogsFsiSuppressiveBubbleMetadata() {
803         ensureNotInteractiveFsiState()
804         val entry = buildFsiEntry {
805             hasBubbleMetadata = true
806             bubbleSuppressesNotification = true
807         }
808 
809         val decision = provider.makeUnloggedFullScreenIntentDecision(entry)
810         assertNoEventsLogged()
811 
812         provider.logFullScreenIntentDecision(decision)
813         assertUiEventLogged(
814             FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA,
815             entry.sbn.uid,
816             entry.sbn.packageName,
817         )
818         assertSystemEventLogged("274759612", entry.sbn.uid, "bubbleMetadata")
819     }
820 
821     @Test
822     fun testShouldNotFsi_packageSuspended() {
823         forEachFsiState {
824             assertShouldNotFsi(buildFsiEntry { packageSuspended = true })
825             assertNoEventsLogged()
826         }
827     }
828 
829     @Test
830     fun testShouldFsi_notInteractive() {
831         ensureNotInteractiveFsiState()
832         assertShouldFsi(buildFsiEntry())
833         assertNoEventsLogged()
834     }
835 
836     @Test
837     fun testShouldFsi_dreaming() {
838         ensureDreamingFsiState()
839         assertShouldFsi(buildFsiEntry())
840         assertNoEventsLogged()
841     }
842 
843     @Test
844     fun testShouldFsi_keyguard() {
845         ensureKeyguardFsiState()
846         assertShouldFsi(buildFsiEntry())
847         assertNoEventsLogged()
848     }
849 
850     @Test
851     fun testShouldNotFsi_expectedToHun() {
852         forEachPeekableFsiState {
853             ensurePeekState()
854             assertShouldNotFsi(buildFsiEntry())
855             assertNoEventsLogged()
856         }
857     }
858 
859     @Test
860     fun testShouldNotFsi_expectedToHun_hunSnoozed() {
861         forEachPeekableFsiState {
862             ensurePeekState { hunSnoozed = true }
863             assertShouldNotFsi(buildFsiEntry())
864             assertNoEventsLogged()
865         }
866     }
867 
868     @Test
869     fun testShouldFsi_lockedShade() {
870         ensureLockedShadeFsiState()
871         assertShouldFsi(buildFsiEntry())
872         assertNoEventsLogged()
873     }
874 
875     @Test
876     fun testShouldFsi_keyguardOccluded() {
877         ensureKeyguardOccludedFsiState()
878         assertShouldFsi(buildFsiEntry())
879         assertNoEventsLogged()
880     }
881 
882     @Test
883     fun testShouldFsi_deviceNotProvisioned() {
884         ensureDeviceNotProvisionedFsiState()
885         assertShouldFsi(buildFsiEntry())
886         assertNoEventsLogged()
887     }
888 
889     @Test
890     fun testShouldFsi_userSetupIncomplete() {
891         ensureUserSetupIncompleteFsiState()
892         assertShouldFsi(buildFsiEntry())
893         assertNoEventsLogged()
894     }
895 
896     @Test
897     fun testShouldNotFsi_noHunOrKeyguard() {
898         ensureNoHunOrKeyguardFsiState()
899         assertShouldNotFsi(buildFsiEntry())
900     }
901 
902     @Test
903     fun testLogsFsiNoHunOrKeyguard() {
904         ensureNoHunOrKeyguardFsiState()
905         val entry = buildFsiEntry()
906 
907         val decision = provider.makeUnloggedFullScreenIntentDecision(entry)
908         assertNoEventsLogged()
909 
910         provider.logFullScreenIntentDecision(decision)
911         assertUiEventLogged(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, entry.sbn.uid, entry.sbn.packageName)
912         assertSystemEventLogged("231322873", entry.sbn.uid, "no hun or keyguard")
913     }
914 
915     @Test
916     fun testShouldFsi_defaultLegacySuppressor() {
917         forEachFsiState {
918             withLegacySuppressor(neverSuppresses) { assertShouldFsi(buildFsiEntry()) }
919             assertNoEventsLogged()
920         }
921     }
922 
923     @Test
924     fun testShouldFsi_suppressInterruptions() {
925         forEachFsiState {
926             withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldFsi(buildFsiEntry()) }
927             assertNoEventsLogged()
928         }
929     }
930 
931     @Test
932     fun testShouldFsi_suppressAwakeInterruptions() {
933         forEachFsiState {
934             withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
935                 assertShouldFsi(buildFsiEntry())
936             }
937             assertNoEventsLogged()
938         }
939     }
940 
941     @Test
942     fun testShouldFsi_suppressAwakeHeadsUp() {
943         forEachFsiState {
944             withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldFsi(buildFsiEntry()) }
945             assertNoEventsLogged()
946         }
947     }
948 
949     protected data class State(
950         var hunSettingEnabled: Boolean? = null,
951         var hunSnoozed: Boolean? = null,
952         var isAodPowerSave: Boolean? = null,
953         var isDozing: Boolean? = null,
954         var isDreaming: Boolean? = null,
955         var isInteractive: Boolean? = null,
956         var isScreenOn: Boolean? = null,
957         var keyguardShouldHideNotification: Boolean? = null,
958         var pulseOnNotificationsEnabled: Boolean? = null,
959         var statusBarState: Int? = null,
960         var keyguardIsShowing: Boolean = false,
961         var keyguardIsOccluded: Boolean = false,
962         var deviceProvisioned: Boolean = true,
963         var currentUserSetup: Boolean = true,
964     )
965 
966     protected fun setState(state: State): Unit =
967         state.run {
968             hunSettingEnabled?.let {
969                 val newSetting = if (it) HEADS_UP_ON else HEADS_UP_OFF
970                 globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, newSetting)
971             }
972 
973             hunSnoozed?.let { whenever(headsUpManager.isSnoozed(TEST_PACKAGE)).thenReturn(it) }
974 
975             isAodPowerSave?.let { batteryController.setIsAodPowerSave(it) }
976 
977             isDozing?.let { statusBarStateController.dozing = it }
978 
979             isDreaming?.let { statusBarStateController.dreaming = it }
980 
981             isInteractive?.let { whenever(powerManager.isInteractive).thenReturn(it) }
982 
983             isScreenOn?.let { whenever(powerManager.isScreenOn).thenReturn(it) }
984 
985             keyguardShouldHideNotification?.let {
986                 whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
987                     .thenReturn(it)
988             }
989 
990             pulseOnNotificationsEnabled?.let {
991                 ambientDisplayConfiguration.fakePulseOnNotificationEnabled = it
992             }
993 
994             statusBarState?.let { statusBarStateController.state = it }
995 
996             keyguardStateController.isOccluded = keyguardIsOccluded
997             keyguardStateController.isShowing = keyguardIsShowing
998 
999             deviceProvisionedController.deviceProvisioned = deviceProvisioned
1000             deviceProvisionedController.isCurrentUserSetup = currentUserSetup
1001         }
1002 
1003     protected fun ensureState(block: State.() -> Unit) =
1004         State()
1005             .apply {
1006                 keyguardShouldHideNotification = false
1007                 apply(block)
1008             }
1009             .run(this::setState)
1010 
1011     protected fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
1012         hunSettingEnabled = true
1013         hunSnoozed = false
1014         isDozing = false
1015         isDreaming = false
1016         isScreenOn = true
1017         run(block)
1018     }
1019 
1020     protected fun ensurePulseState(block: State.() -> Unit = {}) = ensureState {
1021         isAodPowerSave = false
1022         isDozing = true
1023         pulseOnNotificationsEnabled = true
1024         run(block)
1025     }
1026 
1027     protected fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block)
1028 
1029     protected fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState {
1030         isInteractive = false
1031         run(block)
1032     }
1033 
1034     protected fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState {
1035         isInteractive = true
1036         isDreaming = true
1037         run(block)
1038     }
1039 
1040     protected fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
1041         isInteractive = true
1042         isDreaming = false
1043         statusBarState = KEYGUARD
1044         run(block)
1045     }
1046 
1047     protected fun ensureLockedShadeFsiState(block: State.() -> Unit = {}) = ensureState {
1048         // It is assumed *but not checked in the code* that statusBarState is SHADE_LOCKED.
1049         isInteractive = true
1050         isDreaming = false
1051         statusBarState = SHADE
1052         hunSettingEnabled = false
1053         keyguardIsShowing = true
1054         keyguardIsOccluded = false
1055         run(block)
1056     }
1057 
1058     protected fun ensureKeyguardOccludedFsiState(block: State.() -> Unit = {}) = ensureState {
1059         isInteractive = true
1060         isDreaming = false
1061         statusBarState = SHADE
1062         hunSettingEnabled = false
1063         keyguardIsShowing = true
1064         keyguardIsOccluded = true
1065         run(block)
1066     }
1067 
1068     protected fun ensureDeviceNotProvisionedFsiState(block: State.() -> Unit = {}) = ensureState {
1069         isInteractive = true
1070         isDreaming = false
1071         statusBarState = SHADE
1072         hunSettingEnabled = false
1073         keyguardIsShowing = false
1074         deviceProvisioned = false
1075         currentUserSetup = true
1076         run(block)
1077     }
1078 
1079     protected fun ensureUserSetupIncompleteFsiState(block: State.() -> Unit = {}) = ensureState {
1080         isInteractive = true
1081         isDreaming = false
1082         statusBarState = SHADE
1083         hunSettingEnabled = false
1084         keyguardIsShowing = false
1085         deviceProvisioned = true
1086         currentUserSetup = false
1087         run(block)
1088     }
1089 
1090     protected fun ensureNoHunOrKeyguardFsiState(block: State.() -> Unit = {}) = ensureState {
1091         isInteractive = true
1092         isDreaming = false
1093         statusBarState = SHADE
1094         hunSettingEnabled = false
1095         keyguardIsShowing = false
1096         deviceProvisioned = true
1097         currentUserSetup = true
1098         run(block)
1099     }
1100 
1101     protected fun forEachFsiState(block: () -> Unit) {
1102         ensureNotInteractiveFsiState()
1103         block()
1104 
1105         ensureDreamingFsiState()
1106         block()
1107 
1108         ensureKeyguardFsiState()
1109         block()
1110 
1111         ensureLockedShadeFsiState()
1112         block()
1113 
1114         ensureKeyguardOccludedFsiState()
1115         block()
1116 
1117         ensureDeviceNotProvisionedFsiState()
1118         block()
1119     }
1120 
1121     private fun forEachPeekableFsiState(extendState: State.() -> Unit = {}, block: () -> Unit) {
1122         ensureLockedShadeFsiState(extendState)
1123         block()
1124 
1125         ensureKeyguardOccludedFsiState(extendState)
1126         block()
1127 
1128         ensureDeviceNotProvisionedFsiState(extendState)
1129         block()
1130     }
1131 
1132     protected fun withLegacySuppressor(
1133         suppressor: NotificationInterruptSuppressor,
1134         block: () -> Unit,
1135     ) {
1136         provider.addLegacySuppressor(suppressor)
1137         block()
1138         provider.removeLegacySuppressor(suppressor)
1139     }
1140 
1141     protected fun assertShouldHeadsUp(entry: NotificationEntry) =
1142         provider.makeAndLogHeadsUpDecision(entry).let {
1143             assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt)
1144         }
1145 
1146     protected fun assertShouldNotHeadsUp(entry: NotificationEntry) =
1147         provider.makeAndLogHeadsUpDecision(entry).let {
1148             assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt)
1149         }
1150 
1151     protected fun assertShouldBubble(entry: NotificationEntry) =
1152         provider.makeAndLogBubbleDecision(entry).let {
1153             assertTrue("unexpected suppressed bubble: ${it.logReason}", it.shouldInterrupt)
1154         }
1155 
1156     protected fun assertShouldNotBubble(entry: NotificationEntry) =
1157         provider.makeAndLogBubbleDecision(entry).let {
1158             assertFalse("unexpected unsuppressed bubble: ${it.logReason}", it.shouldInterrupt)
1159         }
1160 
1161     protected fun assertShouldFsi(entry: NotificationEntry) =
1162         provider.makeUnloggedFullScreenIntentDecision(entry).let {
1163             provider.logFullScreenIntentDecision(it)
1164             assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt)
1165         }
1166 
1167     protected fun assertShouldNotFsi(
1168         entry: NotificationEntry,
1169         expectWouldInterruptWithoutDnd: Boolean? = null,
1170     ) =
1171         provider.makeUnloggedFullScreenIntentDecision(entry).let {
1172             provider.logFullScreenIntentDecision(it)
1173             assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt)
1174             if (expectWouldInterruptWithoutDnd != null) {
1175                 assertEquals(
1176                     "unexpected wouldInterruptWithoutDnd for FSI: ${it.logReason}",
1177                     expectWouldInterruptWithoutDnd,
1178                     it.wouldInterruptWithoutDnd,
1179                 )
1180             }
1181         }
1182 
1183     protected class EntryBuilder(val context: Context) {
1184         // Set on BubbleMetadata:
1185         var bubbleIsShortcut = false
1186         var bubbleSuppressesNotification = false
1187 
1188         // Set on Notification.Builder:
1189         var whenMs: Long? = null
1190         var isGrouped = false
1191         var isGroupSummary = false
1192         var isCall = false
1193         var category: String? = null
1194         var groupAlertBehavior: Int? = null
1195         var hasBubbleMetadata = false
1196         var hasFsi = false
1197         var isSilent = false
1198 
1199         // Set on Notification:
1200         var isForegroundService = false
1201         var isUserInitiatedJob = false
1202         var isBubble = false
1203         var isStickyAndNotDemoted = false
1204         var isColorized = false
1205 
1206         // Set on NotificationEntryBuilder:
1207         var importance = IMPORTANCE_DEFAULT
1208         var canBubble: Boolean? = null
1209         var isImportantConversation = false
1210 
1211         // Set on NotificationEntry:
1212         var hasJustLaunchedFsi = false
1213 
1214         // Set on ModifiedRankingBuilder:
1215         var packageSuspended = false
1216         var visibilityOverride: Int? = null
1217         var suppressedVisualEffects: Int? = null
1218         var isConversation = false
1219 
1220         private fun buildBubbleMetadata(): BubbleMetadata {
1221             val builder =
1222                 if (bubbleIsShortcut) {
1223                     BubbleMetadata.Builder(context.packageName + ":test_shortcut_id")
1224                 } else {
1225                     BubbleMetadata.Builder(
1226                         PendingIntent.getActivity(
1227                             context,
1228                             /* requestCode = */ 0,
1229                             Intent().setPackage(context.packageName),
1230                             FLAG_MUTABLE,
1231                         ),
1232                         Icon.createWithResource(context.resources, R.drawable.android),
1233                     )
1234                 }
1235 
1236             if (bubbleSuppressesNotification) {
1237                 builder.setSuppressNotification(true)
1238             }
1239 
1240             return builder.build()
1241         }
1242 
1243         fun build() =
1244             Notification.Builder(context, TEST_CHANNEL_ID)
1245                 .also { nb ->
1246                     nb.setContentTitle(TEST_CONTENT_TITLE)
1247                     nb.setContentText(TEST_CONTENT_TEXT)
1248 
1249                     whenMs?.let { nb.setWhen(it) }
1250 
1251                     if (isGrouped) {
1252                         nb.setGroup(TEST_GROUP_KEY)
1253                     }
1254 
1255                     if (isGroupSummary) {
1256                         nb.setGroupSummary(true)
1257                     }
1258 
1259                     if (isCall) {
1260                         nb.extras.putString(EXTRA_TEMPLATE, Notification.CallStyle::class.java.name)
1261                     }
1262 
1263                     if (category != null) {
1264                         nb.setCategory(category)
1265                     }
1266                     groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) }
1267 
1268                     nb.setSilent(isSilent)
1269 
1270                     if (hasBubbleMetadata) {
1271                         nb.setBubbleMetadata(buildBubbleMetadata())
1272                     }
1273 
1274                     if (hasFsi) {
1275                         nb.setFullScreenIntent(mock(), /* highPriority= */ true)
1276                     }
1277                 }
1278                 .build()
1279                 .also { n ->
1280                     if (isForegroundService) {
1281                         n.flags = n.flags or FLAG_FOREGROUND_SERVICE
1282                     }
1283 
1284                     if (isUserInitiatedJob) {
1285                         n.flags = n.flags or FLAG_USER_INITIATED_JOB
1286                     }
1287 
1288                     if (isBubble) {
1289                         n.flags = n.flags or FLAG_BUBBLE
1290                     }
1291 
1292                     if (isStickyAndNotDemoted) {
1293                         n.flags = n.flags or FLAG_FSI_REQUESTED_BUT_DENIED
1294                     }
1295                     if (isColorized) {
1296                         n.extras.putBoolean(EXTRA_COLORIZED, true)
1297                         n.flags = n.flags or FLAG_CAN_COLORIZE
1298                     }
1299                 }
1300                 .let { NotificationEntryBuilder().setNotification(it) }
1301                 .also { neb ->
1302                     neb.setPkg(TEST_PACKAGE)
1303                     neb.setOpPkg(TEST_PACKAGE)
1304                     neb.setTag(TEST_TAG)
1305 
1306                     neb.setImportance(importance)
1307                     val channel =
1308                         NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
1309                     channel.isImportantConversation = isImportantConversation
1310                     neb.setChannel(channel)
1311 
1312                     canBubble?.let { neb.setCanBubble(it) }
1313                 }
1314                 .build()!!
1315                 .also { ne ->
1316                     if (hasJustLaunchedFsi) {
1317                         ne.notifyFullScreenIntentLaunched()
1318                     }
1319 
1320                     if (isStickyAndNotDemoted) {
1321                         assertFalse(ne.isDemoted)
1322                     }
1323 
1324                     modifyRanking(ne)
1325                         .also { mrb ->
1326                             if (packageSuspended) {
1327                                 mrb.setSuspended(true)
1328                             }
1329                             visibilityOverride?.let { mrb.setVisibilityOverride(it) }
1330                             suppressedVisualEffects?.let { mrb.setSuppressedVisualEffects(it) }
1331                             mrb.setIsConversation(isConversation)
1332                         }
1333                         .build()
1334                 }
1335     }
1336 
1337     protected fun buildEntry(block: EntryBuilder.() -> Unit) =
1338         EntryBuilder(context).also(block).build()
1339 
1340     protected fun buildPeekEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
1341         importance = IMPORTANCE_HIGH
1342         run(block)
1343     }
1344 
1345     protected fun buildPulseEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
1346         importance = IMPORTANCE_DEFAULT
1347         visibilityOverride = VISIBILITY_NO_OVERRIDE
1348         run(block)
1349     }
1350 
1351     protected fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
1352         isBubble = true
1353         canBubble = true
1354         hasBubbleMetadata = true
1355         run(block)
1356     }
1357 
1358     protected fun buildFsiEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry {
1359         importance = IMPORTANCE_HIGH
1360         hasFsi = true
1361         run(block)
1362     }
1363 
1364     private fun assertNoEventsLogged() {
1365         assertNoUiEventLogged()
1366         assertNoSystemEventLogged()
1367     }
1368 
1369     private fun assertNoUiEventLogged() {
1370         assertEquals(0, uiEventLogger.numLogs())
1371     }
1372 
1373     private fun assertUiEventLogged(uiEventId: UiEventEnum, uid: Int, packageName: String) {
1374         assertEquals(1, uiEventLogger.numLogs())
1375 
1376         val event = uiEventLogger.get(0)
1377         assertEquals(uiEventId.id, event.eventId)
1378         assertEquals(uid, event.uid)
1379         assertEquals(packageName, event.packageName)
1380     }
1381 
1382     private fun assertNoSystemEventLogged() {
1383         assertEquals(0, eventLog.events.size)
1384     }
1385 
1386     private fun assertSystemEventLogged(number: String, uid: Int, description: String) {
1387         assertEquals(1, eventLog.events.size)
1388 
1389         val event = eventLog.events[0]
1390         assertEquals(0x534e4554, event.tag)
1391 
1392         val value = event.value
1393         assertTrue(value is Array<*>)
1394 
1395         if (value is Array<*>) {
1396             assertEquals(3, value.size)
1397             assertEquals(number, value[0])
1398             assertEquals(uid, value[1])
1399             assertEquals(description, value[2])
1400         }
1401     }
1402 
1403     protected fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
1404 }
1405 
1406 private const val TEST_CONTENT_TITLE = "Test Content Title"
1407 private const val TEST_CONTENT_TEXT = "Test content text"
1408 private const val TEST_CHANNEL_ID = "test_channel"
1409 private const val TEST_CHANNEL_NAME = "Test Channel"
1410 private const val TEST_PACKAGE = "test_package"
1411 private const val TEST_TAG = "test_tag"
1412 private const val TEST_GROUP_KEY = "test_group_key"
1413