1 /* <lambda>null2 * Copyright (C) 2021 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.lockscreen 18 19 import android.app.smartspace.SmartspaceAction 20 import android.app.smartspace.SmartspaceManager 21 import android.app.smartspace.SmartspaceSession 22 import android.app.smartspace.SmartspaceSession.OnTargetsAvailableListener 23 import android.app.smartspace.SmartspaceTarget 24 import android.content.ComponentName 25 import android.content.ContentResolver 26 import android.content.Context 27 import android.content.pm.UserInfo 28 import android.database.ContentObserver 29 import android.graphics.drawable.Drawable 30 import android.net.Uri 31 import android.os.Bundle 32 import android.os.Handler 33 import android.os.UserHandle 34 import android.platform.test.annotations.DisableFlags 35 import android.provider.Settings 36 import android.testing.TestableLooper.RunWithLooper 37 import android.view.View 38 import android.widget.FrameLayout 39 import androidx.test.ext.junit.runners.AndroidJUnit4 40 import androidx.test.filters.SmallTest 41 import com.android.keyguard.KeyguardUpdateMonitor 42 import com.android.systemui.SysuiTestCase 43 import com.android.systemui.dump.DumpManager 44 import com.android.systemui.flags.FeatureFlags 45 import com.android.systemui.keyguard.WakefulnessLifecycle 46 import com.android.systemui.plugins.ActivityStarter 47 import com.android.systemui.plugins.BcSmartspaceConfigPlugin 48 import com.android.systemui.plugins.BcSmartspaceDataPlugin 49 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener 50 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView 51 import com.android.systemui.plugins.FalsingManager 52 import com.android.systemui.plugins.clocks.WeatherData 53 import com.android.systemui.plugins.statusbar.StatusBarStateController 54 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener 55 import com.android.systemui.settings.UserTracker 56 import com.android.systemui.smartspace.ui.viewmodel.SmartspaceViewModel 57 import com.android.systemui.smartspace.viewmodel.smartspaceViewModelFactory 58 import com.android.systemui.statusbar.phone.KeyguardBypassController 59 import com.android.systemui.statusbar.policy.ConfigurationController 60 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener 61 import com.android.systemui.statusbar.policy.DeviceProvisionedController 62 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener 63 import com.android.systemui.testKosmos 64 import com.android.systemui.util.concurrency.FakeExecution 65 import com.android.systemui.util.concurrency.FakeExecutor 66 import com.android.systemui.util.mockito.any 67 import com.android.systemui.util.mockito.argThat 68 import com.android.systemui.util.mockito.capture 69 import com.android.systemui.util.mockito.eq 70 import com.android.systemui.util.settings.SecureSettings 71 import com.android.systemui.util.time.FakeSystemClock 72 import org.junit.Before 73 import org.junit.Test 74 import org.junit.runner.RunWith 75 import org.mockito.ArgumentCaptor 76 import org.mockito.Captor 77 import org.mockito.Mock 78 import org.mockito.Mockito.`when` 79 import org.mockito.Mockito.anyInt 80 import org.mockito.Mockito.clearInvocations 81 import org.mockito.Mockito.mock 82 import org.mockito.Mockito.never 83 import org.mockito.Mockito.spy 84 import org.mockito.Mockito.times 85 import org.mockito.Mockito.verify 86 import org.mockito.MockitoAnnotations 87 import java.util.Optional 88 import java.util.concurrent.Executor 89 90 @SmallTest 91 @RunWithLooper(setAsMainLooper = true) 92 @RunWith(AndroidJUnit4::class) 93 class LockscreenSmartspaceControllerTest : SysuiTestCase() { 94 companion object { 95 const val SMARTSPACE_TIME_TOO_EARLY = 1000L 96 const val SMARTSPACE_TIME_JUST_RIGHT = 4000L 97 const val SMARTSPACE_TIME_TOO_LATE = 9000L 98 const val SMARTSPACE_CREATION_TIME = 1234L 99 const val SMARTSPACE_EXPIRY_TIME = 5678L 100 } 101 @Mock 102 private lateinit var featureFlags: FeatureFlags 103 @Mock 104 private lateinit var smartspaceManager: SmartspaceManager 105 @Mock 106 private lateinit var smartspaceSession: SmartspaceSession 107 @Mock 108 private lateinit var activityStarter: ActivityStarter 109 @Mock 110 private lateinit var falsingManager: FalsingManager 111 112 @Mock 113 private lateinit var secureSettings: SecureSettings 114 115 @Mock 116 private lateinit var userTracker: UserTracker 117 118 @Mock 119 private lateinit var contentResolver: ContentResolver 120 121 @Mock 122 private lateinit var configurationController: ConfigurationController 123 124 @Mock 125 private lateinit var statusBarStateController: StatusBarStateController 126 127 @Mock 128 private lateinit var keyguardBypassController: KeyguardBypassController 129 130 @Mock 131 private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor 132 133 @Mock 134 private lateinit var deviceProvisionedController: DeviceProvisionedController 135 136 @Mock 137 private lateinit var bgExecutor: Executor 138 139 @Mock 140 private lateinit var handler: Handler 141 142 @Mock 143 private lateinit var bgHandler: Handler 144 145 @Mock 146 private lateinit var datePlugin: BcSmartspaceDataPlugin 147 148 @Mock 149 private lateinit var weatherPlugin: BcSmartspaceDataPlugin 150 151 @Mock 152 private lateinit var plugin: BcSmartspaceDataPlugin 153 154 @Mock 155 private lateinit var configPlugin: BcSmartspaceConfigPlugin 156 157 @Mock 158 private lateinit var dumpManager: DumpManager 159 160 @Mock 161 private lateinit var controllerListener: SmartspaceTargetListener 162 163 @Captor 164 private lateinit var sessionListenerCaptor: ArgumentCaptor<OnTargetsAvailableListener> 165 166 @Captor 167 private lateinit var userTrackerCaptor: ArgumentCaptor<UserTracker.Callback> 168 169 @Captor 170 private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> 171 172 @Captor 173 private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener> 174 175 @Captor 176 private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener> 177 178 @Captor 179 private lateinit var bypassStateChangedListenerCaptor: 180 ArgumentCaptor<KeyguardBypassController.OnBypassStateChangedListener> 181 182 @Captor 183 private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener> 184 185 private lateinit var sessionListener: OnTargetsAvailableListener 186 private lateinit var userListener: UserTracker.Callback 187 private lateinit var settingsObserver: ContentObserver 188 private lateinit var configChangeListener: ConfigurationListener 189 private lateinit var statusBarStateListener: StateListener 190 private lateinit var bypassStateChangeListener: 191 KeyguardBypassController.OnBypassStateChangedListener 192 private lateinit var deviceProvisionedListener: DeviceProvisionedListener 193 194 private lateinit var dateSmartspaceView: SmartspaceView 195 private lateinit var weatherSmartspaceView: SmartspaceView 196 private lateinit var smartspaceView: SmartspaceView 197 private lateinit var wakefulnessLifecycle: WakefulnessLifecycle 198 private lateinit var smartspaceViewModelFactory: SmartspaceViewModel.Factory 199 200 private val clock = FakeSystemClock() 201 private val executor = FakeExecutor(clock) 202 private val execution = FakeExecution() 203 private val fakeParent = FrameLayout(context) 204 private val fakePrivateLockscreenSettingUri = Uri.Builder().appendPath("test").build() 205 private val fakeNotifOnLockscreenSettingUri = Uri.Builder().appendPath("notif").build() 206 207 private val userHandlePrimary: UserHandle = UserHandle(0) 208 private val userHandleManaged: UserHandle = UserHandle(2) 209 private val userHandleSecondary: UserHandle = UserHandle(3) 210 211 @Mock private lateinit var userContextPrimary: Context 212 @Mock private lateinit var userContextSecondary: Context 213 214 private val userList = listOf( 215 mockUserInfo(userHandlePrimary, isManagedProfile = false), 216 mockUserInfo(userHandleManaged, isManagedProfile = true), 217 mockUserInfo(userHandleSecondary, isManagedProfile = false) 218 ) 219 220 private lateinit var controller: LockscreenSmartspaceController 221 222 @Before 223 fun setUp() { 224 MockitoAnnotations.initMocks(this) 225 226 `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING)) 227 .thenReturn(fakePrivateLockscreenSettingUri) 228 `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING)) 229 .thenReturn(fakeNotifOnLockscreenSettingUri) 230 `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession) 231 `when`(datePlugin.getView(any())).thenReturn( 232 createDateSmartspaceView(), createDateSmartspaceView()) 233 `when`(weatherPlugin.getView(any())).thenReturn( 234 createWeatherSmartspaceView(), createWeatherSmartspaceView()) 235 `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView()) 236 `when`(userTracker.userProfiles).thenReturn(userList) 237 `when`(statusBarStateController.dozeAmount).thenReturn(0.5f) 238 `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) 239 `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) 240 241 `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java)).thenReturn( 242 smartspaceManager 243 ) 244 245 setActiveUser(userHandlePrimary, userContextPrimary) 246 setAllowPrivateNotifications(userHandlePrimary, true) 247 setAllowPrivateNotifications(userHandleManaged, true) 248 setAllowPrivateNotifications(userHandleSecondary, true) 249 setShowNotifications(userHandlePrimary, true) 250 251 // Use the real wakefulness lifecycle instead of a mock 252 wakefulnessLifecycle = WakefulnessLifecycle( 253 context, 254 /* wallpaper= */ null, 255 clock, 256 dumpManager 257 ) 258 smartspaceViewModelFactory = testKosmos().smartspaceViewModelFactory 259 260 controller = LockscreenSmartspaceController( 261 context, 262 featureFlags, 263 activityStarter, 264 falsingManager, 265 clock, 266 secureSettings, 267 userTracker, 268 contentResolver, 269 configurationController, 270 statusBarStateController, 271 deviceProvisionedController, 272 keyguardBypassController, 273 keyguardUpdateMonitor, 274 wakefulnessLifecycle, 275 smartspaceViewModelFactory, 276 dumpManager, 277 execution, 278 executor, 279 bgExecutor, 280 handler, 281 bgHandler, 282 Optional.of(datePlugin), 283 Optional.of(weatherPlugin), 284 Optional.of(plugin), 285 Optional.of(configPlugin), 286 ) 287 288 verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor)) 289 deviceProvisionedListener = deviceProvisionedCaptor.value 290 } 291 292 @Test 293 fun testBuildAndConnectView_connectsOnlyAfterDeviceIsProvisioned() { 294 // GIVEN an unprovisioned device and an attempt to connect 295 `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) 296 `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false) 297 298 // WHEN a connection attempt is made and view is attached 299 val view = controller.buildAndConnectView(fakeParent)!! 300 controller.stateChangeListener.onViewAttachedToWindow(view) 301 302 // THEN no session is created 303 verify(smartspaceManager, never()).createSmartspaceSession(any()) 304 305 // WHEN it does become provisioned 306 `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) 307 `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) 308 deviceProvisionedListener.onUserSetupChanged() 309 310 // THEN the session is created 311 verify(smartspaceManager).createSmartspaceSession(any()) 312 // THEN an event notifier is registered 313 verify(plugin).registerSmartspaceEventNotifier(any()) 314 } 315 316 @Test 317 fun testAddListener_registersListenersForPlugin() { 318 // GIVEN a listener is added after a session is created 319 connectSession() 320 321 // WHEN a listener is registered 322 controller.addListener(controllerListener) 323 324 // THEN the listener is registered to the underlying plugin 325 verify(plugin).registerListener(controllerListener) 326 // The listener is registered only for the plugin, not the date, or weather plugin. 327 verify(datePlugin, never()).registerListener(any()) 328 verify(weatherPlugin, never()).registerListener(any()) 329 } 330 331 @Test 332 fun testAddListener_earlyRegisteredListenersAreAttachedAfterConnected() { 333 // GIVEN a listener that is registered before the session is created 334 controller.addListener(controllerListener) 335 336 // WHEN the session is created 337 connectSession() 338 339 // THEN the listener is subsequently registered 340 verify(plugin).registerListener(controllerListener) 341 // The listener is registered only for the plugin, not the date, or the weather plugin. 342 verify(datePlugin, never()).registerListener(any()) 343 verify(weatherPlugin, never()).registerListener(any()) 344 } 345 346 @Test 347 fun testDisconnect_emitsEmptyListAndRemovesNotifier() { 348 // GIVEN a registered listener on an active session 349 connectSession() 350 clearInvocations(plugin) 351 352 // WHEN the session is closed 353 controller.stateChangeListener.onViewDetachedFromWindow(dateSmartspaceView as View) 354 controller.stateChangeListener.onViewDetachedFromWindow(weatherSmartspaceView as View) 355 controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View) 356 controller.disconnect() 357 358 // THEN the listener receives an empty list of targets and unregisters the notifier 359 verify(plugin).onTargetsAvailable(emptyList()) 360 verify(plugin).registerSmartspaceEventNotifier(null) 361 verify(weatherPlugin).onTargetsAvailable(emptyList()) 362 verify(weatherPlugin).registerSmartspaceEventNotifier(null) 363 verify(datePlugin).registerSmartspaceEventNotifier(null) 364 } 365 366 @Test 367 fun testUserChange_reloadsSmartspace() { 368 // GIVEN a connected smartspace session 369 connectSession() 370 371 // WHEN the active user changes 372 userListener.onUserChanged(-1, context) 373 374 // THEN we request a new smartspace update 375 verify(smartspaceSession).requestSmartspaceUpdate() 376 } 377 378 @Test 379 fun testSettingsChange_reloadsSmartspace() { 380 // GIVEN a connected smartspace session 381 connectSession() 382 383 // WHEN the lockscreen privacy setting changes 384 settingsObserver.onChange(true, null) 385 386 // THEN we request a new smartspace update 387 verify(smartspaceSession).requestSmartspaceUpdate() 388 } 389 390 @Test 391 fun testThemeChange_updatesTextColor() { 392 // GIVEN a connected smartspace session 393 connectSession() 394 395 // WHEN the theme changes 396 configChangeListener.onThemeChanged() 397 398 // We update the new text color to match the wallpaper color 399 verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) 400 verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) 401 verify(smartspaceView).setPrimaryTextColor(anyInt()) 402 } 403 404 @Test 405 fun testDozeAmountChange_updatesView() { 406 // GIVEN a connected smartspace session 407 connectSession() 408 409 // WHEN the doze amount changes 410 statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f) 411 412 // We pass that along to the view 413 verify(dateSmartspaceView).setDozeAmount(0.7f) 414 verify(weatherSmartspaceView).setDozeAmount(0.7f) 415 verify(smartspaceView).setDozeAmount(0.7f) 416 } 417 418 @Test 419 fun testKeyguardBypassEnabled_updatesView() { 420 // GIVEN a connected smartspace session 421 connectSession() 422 `when`(keyguardBypassController.bypassEnabled).thenReturn(true) 423 424 // WHEN the doze amount changes 425 bypassStateChangeListener.onBypassStateChanged(true) 426 427 // We pass that along to the view 428 verify(smartspaceView).setKeyguardBypassEnabled(true) 429 } 430 431 @Test 432 fun testSensitiveTargetsAreNotFilteredIfAllowed() { 433 // GIVEN the active and managed users allow sensitive content 434 connectSession() 435 436 // WHEN we receive a list of targets 437 val targets = listOf( 438 makeTarget(1, userHandlePrimary, isSensitive = true), 439 makeTarget(2, userHandleManaged, isSensitive = true), 440 makeTarget(3, userHandlePrimary, isSensitive = true) 441 ) 442 sessionListener.onTargetsAvailable(targets) 443 444 // THEN all sensitive content is still shown 445 verify(plugin).onTargetsAvailable(eq(targets)) 446 } 447 448 @Test 449 fun testNonSensitiveTargetsAreNeverFiltered() { 450 // GIVEN the active user doesn't allow sensitive lockscreen content 451 setAllowPrivateNotifications(userHandlePrimary, false) 452 connectSession() 453 454 // WHEN we receive a list of targets 455 val targets = listOf( 456 makeTarget(1, userHandlePrimary), 457 makeTarget(2, userHandlePrimary), 458 makeTarget(3, userHandlePrimary) 459 ) 460 sessionListener.onTargetsAvailable(targets) 461 462 // THEN all non-sensitive content is still shown 463 verify(plugin).onTargetsAvailable(eq(targets)) 464 } 465 466 @Test 467 fun testAllTargetsAreFilteredInclWeatherWhenNotificationsAreDisabled() { 468 // GIVEN the active user doesn't allow any notifications on lockscreen 469 setShowNotifications(userHandlePrimary, false) 470 connectSession() 471 472 // WHEN we receive a list of targets 473 val targets = listOf( 474 makeTarget(1, userHandlePrimary, isSensitive = true), 475 makeTarget(2, userHandlePrimary), 476 makeTarget(3, userHandleManaged), 477 makeTarget(4, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) 478 ) 479 480 sessionListener.onTargetsAvailable(targets) 481 482 // THEN all non-sensitive content is still shown 483 verify(plugin).onTargetsAvailable(emptyList()) 484 } 485 486 @Test 487 fun testSensitiveTargetsAreFilteredOutForAppropriateUsers() { 488 // GIVEN the active and managed users don't allow sensitive lockscreen content 489 setAllowPrivateNotifications(userHandlePrimary, false) 490 setAllowPrivateNotifications(userHandleManaged, false) 491 connectSession() 492 493 // WHEN we receive a list of targets 494 val targets = listOf( 495 makeTarget(0, userHandlePrimary), 496 makeTarget(1, userHandlePrimary, isSensitive = true), 497 makeTarget(2, userHandleManaged, isSensitive = true), 498 makeTarget(3, userHandleManaged), 499 makeTarget(4, userHandlePrimary, isSensitive = true), 500 makeTarget(5, userHandlePrimary), 501 makeTarget(6, userHandleSecondary, isSensitive = true) 502 ) 503 sessionListener.onTargetsAvailable(targets) 504 505 // THEN only non-sensitive content from those accounts is shown 506 verify(plugin).onTargetsAvailable(eq(listOf( 507 targets[0], 508 targets[3], 509 targets[5] 510 ))) 511 } 512 513 @Test 514 fun testSessionListener_weatherTargetIsFilteredOut() { 515 connectSession() 516 517 // WHEN we receive a list of targets 518 val targets = listOf( 519 makeTarget(1, userHandlePrimary, isSensitive = true), 520 makeTarget(2, userHandlePrimary), 521 makeTarget(3, userHandleManaged), 522 makeTarget(4, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) 523 ) 524 525 sessionListener.onTargetsAvailable(targets) 526 527 // THEN all non-sensitive content is still shown 528 verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2]))) 529 // No filtering is applied for the weather plugin 530 verify(weatherPlugin).onTargetsAvailable(eq(targets)) 531 // No targets needed for the date plugin 532 verify(datePlugin, never()).onTargetsAvailable(any()) 533 } 534 535 @Test 536 fun testSessionListener_ifWeatherExtraMissing_thenWeatherDataNotSent() { 537 connectSession() 538 clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) 539 // WHEN we receive a list of targets 540 val targets = listOf( 541 makeTarget(1, userHandlePrimary, isSensitive = true), 542 makeTarget(2, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) 543 544 ) 545 sessionListener.onTargetsAvailable(targets) 546 verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) 547 } 548 549 @Test 550 fun testSessionListener_ifWeatherExtraIsMissingValues_thenWeatherDataNotSent() { 551 connectSession() 552 553 clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) 554 // WHEN we receive a list of targets 555 val targets = listOf( 556 makeTarget(1, userHandlePrimary, isSensitive = true), 557 makeWeatherTargetWithExtras( 558 id = 2, 559 userHandle = userHandlePrimary, 560 description = null, 561 state = WeatherData.WeatherStateIcon.SUNNY.id, 562 temperature = "32", 563 useCelsius = null) 564 565 ) 566 567 sessionListener.onTargetsAvailable(targets) 568 569 verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) 570 } 571 572 @Test 573 fun testSessionListener_ifTooEarly_thenWeatherDataNotSent() { 574 connectSession() 575 576 clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_EARLY) 577 // WHEN we receive a list of targets 578 val targets = listOf( 579 makeWeatherTargetWithExtras( 580 id = 1, 581 userHandle = userHandleManaged, 582 description = "Sunny", 583 state = WeatherData.WeatherStateIcon.SUNNY.id, 584 temperature = "32", 585 useCelsius = false) 586 ) 587 sessionListener.onTargetsAvailable(targets) 588 verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) 589 } 590 591 @Test 592 fun testSessionListener_ifOnTime_thenWeatherDataSent() { 593 connectSession() 594 595 clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) 596 // WHEN we receive a list of targets 597 val targets = listOf( 598 makeWeatherTargetWithExtras( 599 id = 1, 600 userHandle = userHandleManaged, 601 description = "Snow Showers", 602 state = WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW.id, 603 temperature = "-1", 604 useCelsius = false) 605 ) 606 sessionListener.onTargetsAvailable(targets) 607 verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> 608 w.description == "Snow Showers" && 609 w.state == WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW && 610 w.temperature == -1 && !w.useCelsius 611 }) 612 } 613 614 @Test 615 fun testSessionListener_ifTooLate_thenWeatherDataNotSent() { 616 connectSession() 617 618 clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_LATE) 619 // WHEN we receive a list of targets 620 val targets = listOf( 621 makeWeatherTargetWithExtras( 622 id = 1, 623 userHandle = userHandleManaged, 624 description = "Sunny", 625 state = WeatherData.WeatherStateIcon.SUNNY.id, 626 temperature = "72", 627 useCelsius = false) 628 ) 629 sessionListener.onTargetsAvailable(targets) 630 verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) 631 } 632 633 @Test 634 fun testSessionListener_onlyFirstWeatherDataSent() { 635 connectSession() 636 637 clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) 638 // WHEN we receive a list of targets 639 val targets = listOf( 640 makeWeatherTargetWithExtras( 641 id = 1, 642 userHandle = userHandleManaged, 643 description = "Sunny", 644 state = WeatherData.WeatherStateIcon.SUNNY.id, 645 temperature = "72", 646 useCelsius = false), 647 makeWeatherTargetWithExtras( 648 id = 2, 649 userHandle = userHandleManaged, 650 description = "Showers", 651 state = WeatherData.WeatherStateIcon.SHOWERS_RAIN.id, 652 temperature = "62", 653 useCelsius = true) 654 ) 655 sessionListener.onTargetsAvailable(targets) 656 verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> 657 w.description == "Sunny" && 658 w.state == WeatherData.WeatherStateIcon.SUNNY && 659 w.temperature == 72 && !w.useCelsius 660 }) 661 } 662 663 @Test 664 fun testSessionListener_weatherDataUpdates() { 665 connectSession() 666 667 clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) 668 // WHEN we receive a list of targets 669 val targets = listOf( 670 makeTarget(1, userHandlePrimary, isSensitive = true), 671 makeTarget(2, userHandlePrimary), 672 makeTarget(3, userHandleManaged), 673 makeWeatherTargetWithExtras( 674 id = 4, 675 userHandle = userHandlePrimary, 676 description = "Flurries", 677 state = WeatherData.WeatherStateIcon.FLURRIES.id, 678 temperature = "0", 679 useCelsius = true) 680 ) 681 682 sessionListener.onTargetsAvailable(targets) 683 684 verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> 685 w.description == "Flurries" && 686 w.state == WeatherData.WeatherStateIcon.FLURRIES && 687 w.temperature == 0 && w.useCelsius 688 }) 689 } 690 691 @Test 692 fun testSettingsAreReloaded() { 693 // GIVEN a connected session where the privacy settings later flip to false 694 connectSession() 695 setAllowPrivateNotifications(userHandlePrimary, false) 696 setAllowPrivateNotifications(userHandleManaged, false) 697 settingsObserver.onChange(true, fakePrivateLockscreenSettingUri) 698 699 // WHEN we receive a new list of targets 700 val targets = listOf( 701 makeTarget(1, userHandlePrimary, isSensitive = true), 702 makeTarget(2, userHandleManaged, isSensitive = true), 703 makeTarget(4, userHandlePrimary, isSensitive = true) 704 ) 705 sessionListener.onTargetsAvailable(targets) 706 707 // THEN we filter based on the new settings values 708 verify(plugin).onTargetsAvailable(emptyList()) 709 } 710 711 @Test 712 fun testRecognizeSwitchToSecondaryUser() { 713 // GIVEN an inactive secondary user that doesn't allow sensitive content 714 setAllowPrivateNotifications(userHandleSecondary, false) 715 setShowNotifications(userHandleSecondary, true) 716 connectSession() 717 718 // WHEN the secondary user becomes the active user 719 // Note: it doesn't switch to the SmartspaceManager for userContextSecondary 720 setActiveUser(userHandleSecondary, userContextSecondary) 721 userListener.onUserChanged(userHandleSecondary.identifier, context) 722 723 // WHEN we receive a new list of targets 724 val targets = listOf( 725 makeTarget(0, userHandlePrimary), 726 makeTarget(1, userHandleSecondary), 727 makeTarget(2, userHandleSecondary, isSensitive = true), 728 makeTarget(3, userHandleManaged), 729 makeTarget(4, userHandleSecondary), 730 makeTarget(5, userHandleManaged), 731 makeTarget(6, userHandlePrimary) 732 ) 733 sessionListener.onTargetsAvailable(targets) 734 735 // THEN only non-sensitive content from the secondary user is shown 736 verify(plugin).onTargetsAvailable(listOf( 737 targets[1], 738 targets[4] 739 )) 740 } 741 742 @Test 743 fun testUnregisterListenersOnCleanup() { 744 // GIVEN a connected session 745 connectSession() 746 747 // WHEN we are told to cleanup 748 controller.stateChangeListener.onViewDetachedFromWindow(dateSmartspaceView as View) 749 controller.stateChangeListener.onViewDetachedFromWindow(weatherSmartspaceView as View) 750 controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View) 751 controller.disconnect() 752 753 // THEN we disconnect from the session and unregister any listeners 754 verify(smartspaceSession).removeOnTargetsAvailableListener(sessionListener) 755 verify(smartspaceSession).close() 756 verify(userTracker).removeCallback(userListener) 757 verify(contentResolver).unregisterContentObserver(settingsObserver) 758 verify(configurationController).removeCallback(configChangeListener) 759 verify(statusBarStateController).removeCallback(statusBarStateListener) 760 verify(keyguardBypassController) 761 .unregisterOnBypassStateChangedListener(bypassStateChangeListener) 762 } 763 764 @Test 765 fun testMultipleViewsUseSameSession() { 766 // GIVEN a connected session 767 connectSession() 768 clearInvocations(smartspaceManager) 769 clearInvocations(plugin) 770 771 // WHEN we're asked to connect a second time and add to a parent. If the same view 772 // was created the ViewGroup will throw an exception 773 val view = controller.buildAndConnectView(fakeParent) 774 fakeParent.addView(view) 775 val smartspaceView2 = view as SmartspaceView 776 777 // THEN the existing session is reused and views are registered 778 verify(smartspaceManager, never()).createSmartspaceSession(any()) 779 verify(smartspaceView2).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) 780 verify(smartspaceView2).setBgHandler(bgHandler) 781 verify(smartspaceView2).setTimeChangedDelegate(any()) 782 verify(smartspaceView2).registerDataProvider(plugin) 783 verify(smartspaceView2).registerConfigProvider(configPlugin) 784 } 785 786 @Test 787 fun testViewGetInitializedWithBypassEnabledState() { 788 // GIVEN keyguard bypass is enabled. 789 `when`(keyguardBypassController.bypassEnabled).thenReturn(true) 790 791 // WHEN the view is being built 792 val view = controller.buildAndConnectView(fakeParent) 793 smartspaceView = view as SmartspaceView 794 795 // THEN the view is initialized with the keyguard bypass enabled state. 796 verify(smartspaceView).setKeyguardBypassEnabled(true) 797 } 798 799 @Test 800 @RunWithLooper(setAsMainLooper = false) 801 fun testConnectAttemptBeforeInitializationShouldNotCreateSession() { 802 // GIVEN an uninitalized smartspaceView 803 // WHEN the device is provisioned 804 `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) 805 `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) 806 deviceProvisionedListener.onDeviceProvisionedChanged() 807 808 // THEN no calls to createSmartspaceSession should occur 809 verify(smartspaceManager, never()).createSmartspaceSession(any()) 810 // THEN no listeners should be registered 811 verify(configurationController, never()).addCallback(any()) 812 } 813 814 @Test 815 @DisableFlags(com.android.systemui.Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL) 816 fun testWakefulnessLifecycleDispatch_wake_setsSmartspaceScreenOnTrue() { 817 // Connect session 818 connectSession() 819 820 // Add mock views 821 val mockSmartspaceView = mock(SmartspaceView::class.java) 822 controller.smartspaceViews.add(mockSmartspaceView) 823 824 // Initiate wakefulness change 825 wakefulnessLifecycle.dispatchStartedWakingUp(0) 826 827 // Verify smartspace views receive screen on 828 verify(mockSmartspaceView).setScreenOn(true) 829 } 830 831 @Test 832 @DisableFlags(com.android.systemui.Flags.FLAG_SMARTSPACE_LOCKSCREEN_VIEWMODEL) 833 fun testWakefulnessLifecycleDispatch_sleep_setsSmartspaceScreenOnFalse() { 834 // Connect session 835 connectSession() 836 837 // Add mock views 838 val mockSmartspaceView = mock(SmartspaceView::class.java) 839 controller.smartspaceViews.add(mockSmartspaceView) 840 841 // Initiate wakefulness change 842 wakefulnessLifecycle.dispatchFinishedGoingToSleep() 843 844 // Verify smartspace views receive screen on 845 verify(mockSmartspaceView).setScreenOn(false) 846 } 847 848 private fun connectSession() { 849 val dateView = controller.buildAndConnectDateView(fakeParent) 850 dateSmartspaceView = dateView as SmartspaceView 851 fakeParent.addView(dateView) 852 controller.stateChangeListener.onViewAttachedToWindow(dateView) 853 854 verify(dateSmartspaceView).setUiSurface( 855 BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) 856 verify(dateSmartspaceView).setTimeChangedDelegate(any()) 857 verify(dateSmartspaceView).setBgHandler(bgHandler) 858 verify(dateSmartspaceView).registerDataProvider(datePlugin) 859 860 verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) 861 verify(dateSmartspaceView).setDozeAmount(0.5f) 862 863 val weatherView = controller.buildAndConnectWeatherView(fakeParent) 864 weatherSmartspaceView = weatherView as SmartspaceView 865 fakeParent.addView(weatherView) 866 controller.stateChangeListener.onViewAttachedToWindow(weatherView) 867 868 verify(weatherSmartspaceView).setUiSurface( 869 BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) 870 verify(weatherSmartspaceView).setTimeChangedDelegate(any()) 871 verify(weatherSmartspaceView).setBgHandler(bgHandler) 872 verify(weatherSmartspaceView).registerDataProvider(weatherPlugin) 873 874 verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) 875 verify(weatherSmartspaceView).setDozeAmount(0.5f) 876 877 val view = controller.buildAndConnectView(fakeParent) 878 smartspaceView = view as SmartspaceView 879 fakeParent.addView(view) 880 controller.stateChangeListener.onViewAttachedToWindow(view) 881 882 verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) 883 verify(smartspaceView).setTimeChangedDelegate(any()) 884 verify(smartspaceView).setBgHandler(bgHandler) 885 verify(smartspaceView).registerDataProvider(plugin) 886 verify(smartspaceView).registerConfigProvider(configPlugin) 887 verify(smartspaceSession) 888 .addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor)) 889 sessionListener = sessionListenerCaptor.value 890 891 verify(smartspaceManager).createSmartspaceSession(any()) 892 893 verify(userTracker).addCallback(capture(userTrackerCaptor), any()) 894 userListener = userTrackerCaptor.value 895 896 verify(contentResolver).registerContentObserver( 897 eq(fakePrivateLockscreenSettingUri), 898 eq(true), 899 capture(settingsObserverCaptor), 900 eq(UserHandle.USER_ALL)) 901 settingsObserver = settingsObserverCaptor.value 902 903 verify(configurationController).addCallback(configChangeListenerCaptor.capture()) 904 configChangeListener = configChangeListenerCaptor.value 905 906 verify(statusBarStateController).addCallback(statusBarStateListenerCaptor.capture()) 907 statusBarStateListener = statusBarStateListenerCaptor.value 908 verify(keyguardBypassController) 909 .registerOnBypassStateChangedListener(capture(bypassStateChangedListenerCaptor)) 910 bypassStateChangeListener = bypassStateChangedListenerCaptor.value 911 912 verify(smartspaceSession).requestSmartspaceUpdate() 913 clearInvocations(smartspaceSession) 914 915 verify(smartspaceView).setPrimaryTextColor(anyInt()) 916 verify(smartspaceView).setDozeAmount(0.5f) 917 918 clearInvocations(dateSmartspaceView) 919 clearInvocations(weatherSmartspaceView) 920 clearInvocations(smartspaceView) 921 } 922 923 private fun setActiveUser(userHandle: UserHandle, userContext: Context) { 924 `when`(userTracker.userId).thenReturn(userHandle.identifier) 925 `when`(userTracker.userHandle).thenReturn(userHandle) 926 `when`(userTracker.userContext).thenReturn(userContext) 927 } 928 929 private fun mockUserInfo(userHandle: UserHandle, isManagedProfile: Boolean): UserInfo { 930 val userInfo = mock(UserInfo::class.java) 931 `when`(userInfo.userHandle).thenReturn(userHandle) 932 `when`(userInfo.isManagedProfile).thenReturn(isManagedProfile) 933 return userInfo 934 } 935 936 private fun makeTarget( 937 id: Int, 938 userHandle: UserHandle, 939 isSensitive: Boolean = false, 940 featureType: Int = 0 941 ): SmartspaceTarget { 942 return SmartspaceTarget.Builder( 943 "target$id", 944 ComponentName("testpackage", "testclass$id"), 945 userHandle) 946 .setSensitive(isSensitive) 947 .setFeatureType(featureType) 948 .build() 949 } 950 951 private fun makeWeatherTargetWithExtras( 952 id: Int, 953 userHandle: UserHandle, 954 description: String?, 955 state: Int?, 956 temperature: String?, 957 useCelsius: Boolean? 958 ): SmartspaceTarget { 959 val mockWeatherBundle = mock(Bundle::class.java).apply { 960 `when`(getString(WeatherData.DESCRIPTION_KEY)).thenReturn(description) 961 if (state != null) 962 `when`(getInt(eq(WeatherData.STATE_KEY), any())).thenReturn(state) 963 `when`(getString(WeatherData.TEMPERATURE_KEY)).thenReturn(temperature) 964 `when`(containsKey(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius != null) 965 if (useCelsius != null) 966 `when`(getBoolean(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius) 967 } 968 969 val mockBaseAction = mock(SmartspaceAction::class.java) 970 `when`(mockBaseAction.extras).thenReturn(mockWeatherBundle) 971 return SmartspaceTarget.Builder( 972 "targetWithWeatherExtras$id", 973 ComponentName("testpackage", "testclass$id"), 974 userHandle) 975 .setSensitive(false) 976 .setFeatureType(SmartspaceTarget.FEATURE_WEATHER) 977 .setBaseAction(mockBaseAction) 978 .setExpiryTimeMillis(SMARTSPACE_EXPIRY_TIME) 979 .setCreationTimeMillis(SMARTSPACE_CREATION_TIME) 980 .build() 981 } 982 983 private fun setAllowPrivateNotifications(user: UserHandle, value: Boolean) { 984 `when`(secureSettings.getIntForUser( 985 eq(PRIVATE_LOCKSCREEN_SETTING), 986 anyInt(), 987 eq(user.identifier)) 988 ).thenReturn(if (value) 1 else 0) 989 } 990 991 private fun setShowNotifications(user: UserHandle, value: Boolean) { 992 `when`(secureSettings.getIntForUser( 993 eq(NOTIF_ON_LOCKSCREEN_SETTING), 994 anyInt(), 995 eq(user.identifier)) 996 ).thenReturn(if (value) 1 else 0) 997 } 998 999 // Separate function for the date view, which implements a specific subset of all functions. 1000 private fun createDateSmartspaceView(): SmartspaceView { 1001 return spy(object : View(context), SmartspaceView { 1002 override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) { 1003 } 1004 1005 override fun setPrimaryTextColor(color: Int) { 1006 } 1007 1008 override fun setUiSurface(uiSurface: String) { 1009 } 1010 1011 override fun setBgHandler(bgHandler: Handler?) { 1012 } 1013 1014 override fun setTimeChangedDelegate( 1015 delegate: BcSmartspaceDataPlugin.TimeChangedDelegate? 1016 ) {} 1017 1018 override fun setDozeAmount(amount: Float) { 1019 } 1020 1021 override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) { 1022 } 1023 1024 override fun setFalsingManager(falsingManager: FalsingManager?) { 1025 } 1026 1027 override fun setDnd(image: Drawable?, description: String?) { 1028 } 1029 1030 override fun setNextAlarm(image: Drawable?, description: String?) { 1031 } 1032 }) 1033 } 1034 // Separate function for the weather view, which implements a specific subset of all functions. 1035 private fun createWeatherSmartspaceView(): SmartspaceView { 1036 return spy(object : View(context), SmartspaceView { 1037 override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) { 1038 } 1039 1040 override fun setPrimaryTextColor(color: Int) { 1041 } 1042 1043 override fun setUiSurface(uiSurface: String) { 1044 } 1045 1046 override fun setBgHandler(bgHandler: Handler?) { 1047 } 1048 1049 override fun setTimeChangedDelegate( 1050 delegate: BcSmartspaceDataPlugin.TimeChangedDelegate? 1051 ) {} 1052 1053 override fun setDozeAmount(amount: Float) { 1054 } 1055 1056 override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) { 1057 } 1058 1059 override fun setFalsingManager(falsingManager: FalsingManager?) { 1060 } 1061 }) 1062 } 1063 private fun createSmartspaceView(): SmartspaceView { 1064 return spy(object : View(context), SmartspaceView { 1065 override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) { 1066 } 1067 1068 override fun registerConfigProvider(plugin: BcSmartspaceConfigPlugin?) { 1069 } 1070 1071 override fun setPrimaryTextColor(color: Int) { 1072 } 1073 1074 override fun setUiSurface(uiSurface: String) { 1075 } 1076 1077 override fun setBgHandler(bgHandler: Handler?) { 1078 } 1079 1080 override fun setTimeChangedDelegate( 1081 delegate: BcSmartspaceDataPlugin.TimeChangedDelegate? 1082 ) {} 1083 1084 override fun setDozeAmount(amount: Float) { 1085 } 1086 1087 override fun setKeyguardBypassEnabled(enabled: Boolean) { 1088 } 1089 1090 override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) { 1091 } 1092 1093 override fun setFalsingManager(falsingManager: FalsingManager?) { 1094 } 1095 1096 override fun setDnd(image: Drawable?, description: String?) { 1097 } 1098 1099 override fun setNextAlarm(image: Drawable?, description: String?) { 1100 } 1101 1102 override fun setMediaTarget(target: SmartspaceTarget?) { 1103 } 1104 1105 override fun getSelectedPage(): Int { 1106 return -1 1107 } 1108 1109 override fun getCurrentCardTopPadding(): Int { 1110 return 0 1111 } 1112 1113 override fun setHorizontalPaddings(horizontalPadding: Int) { 1114 } 1115 }) 1116 } 1117 } 1118 1119 private const val PRIVATE_LOCKSCREEN_SETTING = 1120 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS 1121 private const val NOTIF_ON_LOCKSCREEN_SETTING = 1122 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS 1123