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