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