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