1 /*
2  * 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.Manifest.permission
20 import android.app.Notification.CATEGORY_ALARM
21 import android.app.Notification.CATEGORY_CAR_EMERGENCY
22 import android.app.Notification.CATEGORY_CAR_WARNING
23 import android.app.Notification.CATEGORY_EVENT
24 import android.app.Notification.CATEGORY_REMINDER
25 import android.app.NotificationManager
26 import android.content.pm.PackageManager.PERMISSION_DENIED
27 import android.content.pm.PackageManager.PERMISSION_GRANTED
28 import android.platform.test.annotations.EnableFlags
29 import androidx.test.ext.junit.runners.AndroidJUnit4
30 import androidx.test.filters.SmallTest
31 import com.android.systemui.statusbar.notification.collection.NotificationEntry
32 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
33 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
34 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 import org.mockito.ArgumentMatchers.any
38 import org.mockito.ArgumentMatchers.anyInt
39 import org.mockito.Mockito.anyString
40 import org.mockito.Mockito.times
41 import org.mockito.Mockito.verify
42 import org.mockito.Mockito.`when`
43 import org.mockito.kotlin.whenever
44 import java.util.Optional
45 
46 @SmallTest
47 @RunWith(AndroidJUnit4::class)
48 @EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
49 class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
<lambda>null50     override val provider by lazy {
51         VisualInterruptionDecisionProviderImpl(
52             ambientDisplayConfiguration,
53             batteryController,
54             deviceProvisionedController,
55             eventLog,
56             globalSettings,
57             headsUpManager,
58             keyguardNotificationVisibilityProvider,
59             keyguardStateController,
60             newLogger,
61             mainHandler,
62             powerManager,
63             statusBarStateController,
64             systemClock,
65             uiEventLogger,
66             userTracker,
67             avalancheProvider,
68             systemSettings,
69             packageManager,
70             Optional.of(bubbles),
71             context,
72             notificationManager,
73             settingsInteractor
74         )
75     }
76 
77     @Test
testNothingCondition_suppressesNothingnull78     fun testNothingCondition_suppressesNothing() {
79         withCondition(TestCondition(types = emptySet()) { true }) {
80             assertPeekNotSuppressed()
81             assertPulseNotSuppressed()
82             assertBubbleNotSuppressed()
83             assertFsiNotSuppressed()
84         }
85     }
86 
87     @Test
testNothingFilter_suppressesNothingnull88     fun testNothingFilter_suppressesNothing() {
89         withFilter(TestFilter(types = emptySet()) { true }) {
90             assertPeekNotSuppressed()
91             assertPulseNotSuppressed()
92             assertBubbleNotSuppressed()
93             assertFsiNotSuppressed()
94         }
95     }
96 
97     // Avalanche tests are in VisualInterruptionDecisionProviderImplTest
98     // instead of VisualInterruptionDecisionProviderTestBase
99     // because avalanche code is based on the suppression refactor.
100 
getAvalancheSuppressornull101     private fun getAvalancheSuppressor() : AvalancheSuppressor {
102         return AvalancheSuppressor(
103             avalancheProvider, systemClock, settingsInteractor, packageManager,
104             uiEventLogger, context, notificationManager, logger, systemSettings
105         )
106     }
107 
108     @Test
testAvalancheFilter_suppress_hasNotSeenEdu_showEduHunnull109     fun testAvalancheFilter_suppress_hasNotSeenEdu_showEduHun() {
110         setAllowedEmergencyPkg(false)
111         whenever(avalancheProvider.timeoutMs).thenReturn(20)
112         whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
113 
114         val avalancheSuppressor = getAvalancheSuppressor()
115         avalancheSuppressor.hasSeenEdu = false
116 
117         withFilter(avalancheSuppressor) {
118             ensurePeekState()
119             assertShouldNotHeadsUp(
120                 buildEntry {
121                     importance = NotificationManager.IMPORTANCE_HIGH
122                     whenMs = whenAgo(5)
123                 }
124             )
125         }
126         verify(notificationManager, times(1)).notify(anyInt(), any())
127     }
128 
129     @Test
testAvalancheFilter_suppress_hasSeenEduHun_doNotShowEduHunnull130     fun testAvalancheFilter_suppress_hasSeenEduHun_doNotShowEduHun() {
131         setAllowedEmergencyPkg(false)
132         whenever(avalancheProvider.timeoutMs).thenReturn(20)
133         whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
134 
135         val avalancheSuppressor = getAvalancheSuppressor()
136         avalancheSuppressor.hasSeenEdu = true
137 
138         withFilter(avalancheSuppressor) {
139             ensurePeekState()
140             assertShouldNotHeadsUp(
141                 buildEntry {
142                     importance = NotificationManager.IMPORTANCE_HIGH
143                     whenMs = whenAgo(5)
144                 }
145             )
146         }
147         verify(notificationManager, times(0)).notify(anyInt(), any())
148     }
149 
150     @Test
testAvalancheFilter_duringAvalanche_allowConversationFromAfterEventnull151     fun testAvalancheFilter_duringAvalanche_allowConversationFromAfterEvent() {
152         avalancheProvider.startTime = whenAgo(10)
153 
154         withFilter(
155             getAvalancheSuppressor()
156         ) {
157             ensurePeekState()
158             assertShouldHeadsUp(
159                 buildEntry {
160                     importance = NotificationManager.IMPORTANCE_HIGH
161                     isConversation = true
162                     isImportantConversation = false
163                     whenMs = whenAgo(5)
164                 }
165             )
166         }
167     }
168 
169     @Test
testAvalancheFilter_duringAvalanche_suppressConversationFromBeforeEventnull170     fun testAvalancheFilter_duringAvalanche_suppressConversationFromBeforeEvent() {
171         avalancheProvider.startTime = whenAgo(10)
172 
173         withFilter(
174             getAvalancheSuppressor()
175         ) {
176             ensurePeekState()
177             assertShouldNotHeadsUp(
178                 buildEntry {
179                     importance = NotificationManager.IMPORTANCE_DEFAULT
180                     isConversation = true
181                     isImportantConversation = false
182                     whenMs = whenAgo(15)
183                 }
184             )
185         }
186     }
187 
188     @Test
testAvalancheFilter_duringAvalanche_allowHighPriorityConversationnull189     fun testAvalancheFilter_duringAvalanche_allowHighPriorityConversation() {
190         avalancheProvider.startTime = whenAgo(10)
191 
192         withFilter(
193             getAvalancheSuppressor()
194         ) {
195             ensurePeekState()
196             assertShouldHeadsUp(
197                 buildEntry {
198                     importance = NotificationManager.IMPORTANCE_HIGH
199                     isImportantConversation = true
200                 }
201             )
202         }
203     }
204 
205     @Test
testAvalancheFilter_duringAvalanche_allowCallnull206     fun testAvalancheFilter_duringAvalanche_allowCall() {
207         avalancheProvider.startTime = whenAgo(10)
208 
209         withFilter(
210             getAvalancheSuppressor()
211         ) {
212             ensurePeekState()
213             assertShouldHeadsUp(
214                 buildEntry {
215                     importance = NotificationManager.IMPORTANCE_HIGH
216                     isCall = true
217                 }
218             )
219         }
220     }
221 
222     @Test
testAvalancheFilter_duringAvalanche_allowCategoryRemindernull223     fun testAvalancheFilter_duringAvalanche_allowCategoryReminder() {
224         avalancheProvider.startTime = whenAgo(10)
225 
226         withFilter(
227             getAvalancheSuppressor()
228         ) {
229             ensurePeekState()
230             assertShouldHeadsUp(
231                 buildEntry {
232                     importance = NotificationManager.IMPORTANCE_HIGH
233                     category = CATEGORY_REMINDER
234                 }
235             )
236         }
237     }
238 
239     @Test
testAvalancheFilter_duringAvalanche_allowCategoryEventnull240     fun testAvalancheFilter_duringAvalanche_allowCategoryEvent() {
241         avalancheProvider.startTime = whenAgo(10)
242 
243         withFilter(
244             getAvalancheSuppressor()
245         ) {
246             ensurePeekState()
247             assertShouldHeadsUp(
248                 buildEntry {
249                     importance = NotificationManager.IMPORTANCE_HIGH
250                     category = CATEGORY_EVENT
251                 }
252             )
253         }
254     }
255 
256     @Test
testAvalancheFilter_duringAvalanche_allowCategoryAlarmnull257     fun testAvalancheFilter_duringAvalanche_allowCategoryAlarm() {
258         avalancheProvider.startTime = whenAgo(10)
259 
260         withFilter(
261             getAvalancheSuppressor()
262         ) {
263             ensurePeekState()
264             assertShouldHeadsUp(
265                 buildEntry {
266                     importance = NotificationManager.IMPORTANCE_HIGH
267                     category = CATEGORY_ALARM
268                 }
269             )
270         }
271     }
272 
273     @Test
testAvalancheFilter_duringAvalanche_allowCategoryCarEmergencynull274     fun testAvalancheFilter_duringAvalanche_allowCategoryCarEmergency() {
275         avalancheProvider.startTime = whenAgo(10)
276 
277         withFilter(
278             getAvalancheSuppressor()
279         ) {
280             ensurePeekState()
281             assertShouldHeadsUp(
282                 buildEntry {
283                     importance = NotificationManager.IMPORTANCE_HIGH
284                     category = CATEGORY_CAR_EMERGENCY
285 
286                 }
287             )
288         }
289     }
290 
291     @Test
testAvalancheFilter_duringAvalanche_allowCategoryCarWarningnull292     fun testAvalancheFilter_duringAvalanche_allowCategoryCarWarning() {
293         avalancheProvider.startTime = whenAgo(10)
294 
295         withFilter(
296             getAvalancheSuppressor()
297         ) {
298             ensurePeekState()
299             assertShouldHeadsUp(
300                 buildEntry {
301                     importance = NotificationManager.IMPORTANCE_HIGH
302                     category = CATEGORY_CAR_WARNING
303                 }
304             )
305         }
306     }
307 
308     @Test
testAvalancheFilter_duringAvalanche_allowFsinull309     fun testAvalancheFilter_duringAvalanche_allowFsi() {
310         avalancheProvider.startTime = whenAgo(10)
311 
312         withFilter(
313             getAvalancheSuppressor()
314         ) {
315             assertFsiNotSuppressed()
316         }
317     }
318 
319     @Test
testAvalancheFilter_duringAvalanche_allowColorizednull320     fun testAvalancheFilter_duringAvalanche_allowColorized() {
321         avalancheProvider.startTime = whenAgo(10)
322 
323         withFilter(
324             getAvalancheSuppressor()
325         ) {
326             ensurePeekState()
327             assertShouldHeadsUp(
328                 buildEntry {
329                     importance = NotificationManager.IMPORTANCE_HIGH
330                     isColorized = true
331                 }
332             )
333         }
334     }
335 
setAllowedEmergencyPkgnull336     private fun setAllowedEmergencyPkg(allow: Boolean) {
337         `when`(
338             packageManager.checkPermission(
339                 org.mockito.Mockito.eq(permission.RECEIVE_EMERGENCY_BROADCAST),
340                 anyString()
341             )
342         ).thenReturn(if (allow) PERMISSION_GRANTED else PERMISSION_DENIED)
343     }
344 
345     @Test
testAvalancheFilter_duringAvalanche_allowEmergencynull346     fun testAvalancheFilter_duringAvalanche_allowEmergency() {
347         avalancheProvider.startTime = whenAgo(10)
348 
349         setAllowedEmergencyPkg(true)
350 
351         withFilter(
352             getAvalancheSuppressor()
353         ) {
354             ensurePeekState()
355             assertShouldHeadsUp(
356                 buildEntry {
357                     importance = NotificationManager.IMPORTANCE_HIGH
358                 }
359             )
360         }
361     }
362 
363 
364     @Test
testPeekCondition_suppressesOnlyPeeknull365     fun testPeekCondition_suppressesOnlyPeek() {
366         withCondition(TestCondition(types = setOf(PEEK)) { true }) {
367             assertPeekSuppressed()
368             assertPulseNotSuppressed()
369             assertBubbleNotSuppressed()
370             assertFsiNotSuppressed()
371         }
372     }
373 
374     @Test
testPeekFilter_suppressesOnlyPeeknull375     fun testPeekFilter_suppressesOnlyPeek() {
376         withFilter(TestFilter(types = setOf(PEEK)) { true }) {
377             assertPeekSuppressed()
378             assertPulseNotSuppressed()
379             assertBubbleNotSuppressed()
380             assertFsiNotSuppressed()
381         }
382     }
383 
384     @Test
testPulseCondition_suppressesOnlyPulsenull385     fun testPulseCondition_suppressesOnlyPulse() {
386         withCondition(TestCondition(types = setOf(PULSE)) { true }) {
387             assertPeekNotSuppressed()
388             assertPulseSuppressed()
389             assertBubbleNotSuppressed()
390             assertFsiNotSuppressed()
391         }
392     }
393 
394     @Test
testPulseFilter_suppressesOnlyPulsenull395     fun testPulseFilter_suppressesOnlyPulse() {
396         withFilter(TestFilter(types = setOf(PULSE)) { true }) {
397             assertPeekNotSuppressed()
398             assertPulseSuppressed()
399             assertBubbleNotSuppressed()
400             assertFsiNotSuppressed()
401         }
402     }
403 
404     @Test
testBubbleCondition_suppressesOnlyBubblenull405     fun testBubbleCondition_suppressesOnlyBubble() {
406         withCondition(TestCondition(types = setOf(BUBBLE)) { true }) {
407             assertPeekNotSuppressed()
408             assertPulseNotSuppressed()
409             assertBubbleSuppressed()
410             assertFsiNotSuppressed()
411         }
412     }
413 
414     @Test
testBubbleFilter_suppressesOnlyBubblenull415     fun testBubbleFilter_suppressesOnlyBubble() {
416         withFilter(TestFilter(types = setOf(BUBBLE)) { true }) {
417             assertPeekNotSuppressed()
418             assertPulseNotSuppressed()
419             assertBubbleSuppressed()
420             assertFsiNotSuppressed()
421         }
422     }
423 
424     @Test
testCondition_differentStatenull425     fun testCondition_differentState() {
426         ensurePeekState()
427         val entry = buildPeekEntry()
428 
429         var stateShouldSuppress = false
430         withCondition(TestCondition(types = setOf(PEEK)) { stateShouldSuppress }) {
431             assertShouldHeadsUp(entry)
432 
433             stateShouldSuppress = true
434             assertShouldNotHeadsUp(entry)
435 
436             stateShouldSuppress = false
437             assertShouldHeadsUp(entry)
438         }
439     }
440 
441     @Test
testFilter_differentStatenull442     fun testFilter_differentState() {
443         ensurePeekState()
444         val entry = buildPeekEntry()
445 
446         var stateShouldSuppress = false
447         withFilter(TestFilter(types = setOf(PEEK)) { stateShouldSuppress }) {
448             assertShouldHeadsUp(entry)
449 
450             stateShouldSuppress = true
451             assertShouldNotHeadsUp(entry)
452 
453             stateShouldSuppress = false
454             assertShouldHeadsUp(entry)
455         }
456     }
457 
458     @Test
testFilter_differentNotifnull459     fun testFilter_differentNotif() {
460         ensurePeekState()
461 
462         val suppressedEntry = buildPeekEntry()
463         val unsuppressedEntry = buildPeekEntry()
464 
465         withFilter(TestFilter(types = setOf(PEEK)) { it == suppressedEntry }) {
466             assertShouldNotHeadsUp(suppressedEntry)
467             assertShouldHeadsUp(unsuppressedEntry)
468         }
469     }
470 
assertPeekSuppressednull471     private fun assertPeekSuppressed() {
472         ensurePeekState()
473         assertShouldNotHeadsUp(buildPeekEntry())
474     }
475 
assertPeekNotSuppressednull476     private fun assertPeekNotSuppressed() {
477         ensurePeekState()
478         assertShouldHeadsUp(buildPeekEntry())
479     }
480 
assertPulseSuppressednull481     private fun assertPulseSuppressed() {
482         ensurePulseState()
483         assertShouldNotHeadsUp(buildPulseEntry())
484     }
485 
assertPulseNotSuppressednull486     private fun assertPulseNotSuppressed() {
487         ensurePulseState()
488         assertShouldHeadsUp(buildPulseEntry())
489     }
490 
assertBubbleSuppressednull491     private fun assertBubbleSuppressed() {
492         ensureBubbleState()
493         assertShouldNotBubble(buildBubbleEntry())
494     }
495 
assertBubbleNotSuppressednull496     private fun assertBubbleNotSuppressed() {
497         ensureBubbleState()
498         assertShouldBubble(buildBubbleEntry())
499     }
500 
assertFsiNotSuppressednull501     private fun assertFsiNotSuppressed() {
502         forEachFsiState { assertShouldFsi(buildFsiEntry()) }
503     }
504 
withConditionnull505     private fun withCondition(condition: VisualInterruptionCondition, block: () -> Unit) {
506         provider.addCondition(condition)
507         block()
508         provider.removeCondition(condition)
509     }
510 
withFilternull511     private fun withFilter(filter: VisualInterruptionFilter, block: () -> Unit) {
512         provider.addFilter(filter)
513         block()
514         provider.removeFilter(filter)
515     }
516 
517     private class TestCondition(
518         types: Set<VisualInterruptionType>,
519         val onShouldSuppress: () -> Boolean
520     ) : VisualInterruptionCondition(types = types, reason = "test condition") {
shouldSuppressnull521         override fun shouldSuppress(): Boolean = onShouldSuppress()
522     }
523 
524     private class TestFilter(
525         types: Set<VisualInterruptionType>,
526         val onShouldSuppress: (NotificationEntry) -> Boolean = { true }
527     ) : VisualInterruptionFilter(types = types, reason = "test filter") {
shouldSuppressnull528         override fun shouldSuppress(entry: NotificationEntry) = onShouldSuppress(entry)
529     }
530 }
531