Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a045a061 authored by Xiaowen Lei's avatar Xiaowen Lei Committed by Automerger Merge Worker
Browse files

Merge "Add standalone weather view to lockscreen if decoupling isenabled."...

Merge "Add standalone weather view to lockscreen if decoupling isenabled." into tm-qpr-dev am: 7e9ff1ef

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21062126



Change-Id: Ifc4eb005555b1baefd6b19e74e40fe594d59e33f
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 9639e4c2 7e9ff1ef
Loading
Loading
Loading
Loading
+24 −3
Original line number Original line Diff line number Diff line
@@ -88,7 +88,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
    private final ClockRegistry.ClockChangeListener mClockChangedListener;
    private final ClockRegistry.ClockChangeListener mClockChangedListener;


    private ViewGroup mStatusArea;
    private ViewGroup mStatusArea;
    // If set will replace keyguard_slice_view

    // If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views.
    private View mWeatherView;
    private View mSmartspaceView;
    private View mSmartspaceView;


    private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
    private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -192,10 +194,17 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS


        if (mSmartspaceController.isEnabled()) {
        if (mSmartspaceController.isEnabled()) {
            View ksv = mView.findViewById(R.id.keyguard_slice_view);
            View ksv = mView.findViewById(R.id.keyguard_slice_view);
            int ksvIndex = mStatusArea.indexOfChild(ksv);
            int viewIndex = mStatusArea.indexOfChild(ksv);
            ksv.setVisibility(View.GONE);
            ksv.setVisibility(View.GONE);


            addSmartspaceView(ksvIndex);
            // TODO(b/261757708): add content observer for the Settings toggle and add/remove
            //  weather according to the Settings.
            if (mSmartspaceController.isDateWeatherDecoupled()) {
                addWeatherView(viewIndex);
                viewIndex += 1;
            }

            addSmartspaceView(viewIndex);
        }
        }


        mSecureSettings.registerContentObserverForUser(
        mSecureSettings.registerContentObserverForUser(
@@ -237,6 +246,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
        }
        }
    }
    }


    private void addWeatherView(int index) {
        mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                MATCH_PARENT, WRAP_CONTENT);
        mStatusArea.addView(mWeatherView, index, lp);
        int startPadding = getContext().getResources().getDimensionPixelSize(
                R.dimen.below_clock_padding_start);
        int endPadding = getContext().getResources().getDimensionPixelSize(
                R.dimen.below_clock_padding_end);
        mWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
    }

    private void addSmartspaceView(int index) {
    private void addSmartspaceView(int index) {
        mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
        mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+6 −0
Original line number Original line Diff line number Diff line
@@ -107,6 +107,8 @@ import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.Executor;


import javax.inject.Named;

import dagger.Binds;
import dagger.Binds;
import dagger.BindsOptionalOf;
import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Module;
@@ -214,6 +216,10 @@ public abstract class SystemUIModule {
    @BindsOptionalOf
    @BindsOptionalOf
    abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin();
    abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin();


    @BindsOptionalOf
    @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
    abstract BcSmartspaceDataPlugin optionalWeatherSmartspaceConfigPlugin();

    @BindsOptionalOf
    @BindsOptionalOf
    abstract Recents optionalRecents();
    abstract Recents optionalRecents();


+5 −12
Original line number Original line Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.smartspace.dagger
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.filters.LockscreenAndDreamTargetFilter
import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
import dagger.Binds
import dagger.Binds
import dagger.BindsOptionalOf
import dagger.BindsOptionalOf
@@ -34,11 +33,6 @@ abstract class SmartspaceModule {
         */
         */
        const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"
        const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"


        /**
         * The lockscreen smartspace target filter.
         */
        const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter"

        /**
        /**
         * The dream smartspace target filter.
         * The dream smartspace target filter.
         */
         */
@@ -48,6 +42,11 @@ abstract class SmartspaceModule {
         * The precondition for dream smartspace
         * The precondition for dream smartspace
         */
         */
        const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
        const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"

        /**
         * The BcSmartspaceDataPlugin for the standalone weather.
         */
        const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
    }
    }


    @BindsOptionalOf
    @BindsOptionalOf
@@ -58,12 +57,6 @@ abstract class SmartspaceModule {
    @Named(DREAM_SMARTSPACE_DATA_PLUGIN)
    @Named(DREAM_SMARTSPACE_DATA_PLUGIN)
    abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
    abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?


    @Binds
    @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
    abstract fun provideLockscreenSmartspaceTargetFilter(
        filter: LockscreenAndDreamTargetFilter?
    ): SmartspaceTargetFilter?

    @Binds
    @Binds
    @Named(DREAM_SMARTSPACE_PRECONDITION)
    @Named(DREAM_SMARTSPACE_PRECONDITION)
    abstract fun bindSmartspacePrecondition(
    abstract fun bindSmartspacePrecondition(
+62 −13
Original line number Original line Diff line number Diff line
@@ -52,6 +52,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.shared.regionsampling.UpdateColorCallback
import com.android.systemui.shared.regionsampling.UpdateColorCallback
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -60,6 +61,7 @@ import com.android.systemui.util.settings.SecureSettings
import java.util.Optional
import java.util.Optional
import java.util.concurrent.Executor
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Named


/** Controller for managing the smartspace view on the lockscreen */
/** Controller for managing the smartspace view on the lockscreen */
@SysUISingleton
@SysUISingleton
@@ -82,6 +84,8 @@ constructor(
        @Main private val uiExecutor: Executor,
        @Main private val uiExecutor: Executor,
        @Background private val bgExecutor: Executor,
        @Background private val bgExecutor: Executor,
        @Main private val handler: Handler,
        @Main private val handler: Handler,
        @Named(WEATHER_SMARTSPACE_DATA_PLUGIN)
        optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
        optionalPlugin: Optional<BcSmartspaceDataPlugin>,
        optionalPlugin: Optional<BcSmartspaceDataPlugin>,
        optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>,
        optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>,
) {
) {
@@ -90,6 +94,7 @@ constructor(
    }
    }


    private var session: SmartspaceSession? = null
    private var session: SmartspaceSession? = null
    private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
    private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
    private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
    private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
    private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)


@@ -131,6 +136,10 @@ constructor(


    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
        execution.assertIsMainThread()
        execution.assertIsMainThread()

        // The weather data plugin takes unfiltered targets and performs the filtering internally.
        weatherPlugin?.onTargetsAvailable(targets)

        val filteredTargets = targets.filter(::filterSmartspaceTarget)
        val filteredTargets = targets.filter(::filterSmartspaceTarget)
        plugin?.onTargetsAvailable(filteredTargets)
        plugin?.onTargetsAvailable(filteredTargets)
        if (!isContentUpdatedOnce) {
        if (!isContentUpdatedOnce) {
@@ -209,32 +218,58 @@ constructor(
        return plugin != null
        return plugin != null
    }
    }


    fun isDateWeatherDecoupled(): Boolean {
        execution.assertIsMainThread()

        return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) &&
                weatherPlugin != null
    }

    private fun updateBypassEnabled() {
    private fun updateBypassEnabled() {
        val bypassEnabled = bypassController.bypassEnabled
        val bypassEnabled = bypassController.bypassEnabled
        smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) }
        smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) }
    }
    }


    /**
    /**
     * Constructs the smartspace view and connects it to the smartspace service.
     * Constructs the weather view and connects it to the smartspace service.
     */
     */
    fun buildAndConnectView(parent: ViewGroup): View? {
    fun buildAndConnectWeatherView(parent: ViewGroup): View? {
        execution.assertIsMainThread()
        execution.assertIsMainThread()


        if (!isEnabled()) {
        if (!isEnabled()) {
            throw RuntimeException("Cannot build view when not enabled")
            throw RuntimeException("Cannot build view when not enabled")
        }
        }
        if (!isDateWeatherDecoupled()) {
            throw RuntimeException("Cannot build weather view when not decoupled")
        }


        val view = buildView(parent)
        val view = buildView(parent, weatherPlugin)
        connectSession()
        connectSession()


        return view
        return view
    }
    }


    fun requestSmartspaceUpdate() {
    /**
        session?.requestSmartspaceUpdate()
     * Constructs the smartspace view and connects it to the smartspace service.
     */
    fun buildAndConnectView(parent: ViewGroup): View? {
        execution.assertIsMainThread()

        if (!isEnabled()) {
            throw RuntimeException("Cannot build view when not enabled")
        }
        }


    private fun buildView(parent: ViewGroup): View? {
        val view = buildView(parent, plugin, configPlugin)
        connectSession()

        return view
    }

    private fun buildView(
            parent: ViewGroup,
            plugin: BcSmartspaceDataPlugin?,
            configPlugin: BcSmartspaceConfigPlugin? = null
    ): View? {
        if (plugin == null) {
        if (plugin == null) {
            return null
            return null
        }
        }
@@ -242,7 +277,7 @@ constructor(
        val ssView = plugin.getView(parent)
        val ssView = plugin.getView(parent)
        ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
        ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
        ssView.registerDataProvider(plugin)
        ssView.registerDataProvider(plugin)
        ssView.registerConfigProvider(configPlugin)
        configPlugin?.let { ssView.registerConfigProvider(it) }


        ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
        ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
            override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
            override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
@@ -273,7 +308,8 @@ constructor(
    }
    }


    private fun connectSession() {
    private fun connectSession() {
        if (plugin == null || session != null || smartspaceViews.isEmpty()) {
        if (weatherPlugin == null && plugin == null) return
        if (session != null || smartspaceViews.isEmpty()) {
            return
            return
        }
        }


@@ -310,14 +346,20 @@ constructor(
        statusBarStateController.addCallback(statusBarStateListener)
        statusBarStateController.addCallback(statusBarStateListener)
        bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
        bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)


        plugin.registerSmartspaceEventNotifier {
        weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
                e -> session?.notifySmartspaceEvent(e)
        plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
        }


        updateBypassEnabled()
        updateBypassEnabled()
        reloadSmartspace()
        reloadSmartspace()
    }
    }


    /**
     * Requests the smartspace session for an update.
     */
    fun requestSmartspaceUpdate() {
        session?.requestSmartspaceUpdate()
    }

    /**
    /**
     * Disconnects the smartspace view from the smartspace service and cleans up any resources.
     * Disconnects the smartspace view from the smartspace service and cleans up any resources.
     */
     */
@@ -341,9 +383,13 @@ constructor(
        bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
        bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
        session = null
        session = null


        weatherPlugin?.registerSmartspaceEventNotifier(null)
        weatherPlugin?.onTargetsAvailable(emptyList())

        plugin?.registerSmartspaceEventNotifier(null)
        plugin?.registerSmartspaceEventNotifier(null)
        plugin?.onTargetsAvailable(emptyList())
        plugin?.onTargetsAvailable(emptyList())
        Log.d(TAG, "Ending smartspace session for lockscreen")

        Log.d(TAG, "Ended smartspace session for lockscreen")
    }
    }


    fun addListener(listener: SmartspaceTargetListener) {
    fun addListener(listener: SmartspaceTargetListener) {
@@ -357,8 +403,11 @@ constructor(
    }
    }


    private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
    private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
        if (isDateWeatherDecoupled()) {
            return t.featureType != SmartspaceTarget.FEATURE_WEATHER
        }
        if (!showNotifications) {
        if (!showNotifications) {
            return t.getFeatureType() == SmartspaceTarget.FEATURE_WEATHER
            return t.featureType == SmartspaceTarget.FEATURE_WEATHER
        }
        }
        return when (t.userHandle) {
        return when (t.userHandle) {
            userTracker.userHandle -> {
            userTracker.userHandle -> {
+153 −20
Original line number Original line Diff line number Diff line
@@ -34,6 +34,7 @@ import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
@@ -111,6 +112,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    @Mock
    @Mock
    private lateinit var handler: Handler
    private lateinit var handler: Handler


    @Mock
    private lateinit var weatherPlugin: BcSmartspaceDataPlugin

    @Mock
    @Mock
    private lateinit var plugin: BcSmartspaceDataPlugin
    private lateinit var plugin: BcSmartspaceDataPlugin


@@ -151,6 +155,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        KeyguardBypassController.OnBypassStateChangedListener
        KeyguardBypassController.OnBypassStateChangedListener
    private lateinit var deviceProvisionedListener: DeviceProvisionedListener
    private lateinit var deviceProvisionedListener: DeviceProvisionedListener


    private lateinit var weatherSmartspaceView: SmartspaceView
    private lateinit var smartspaceView: SmartspaceView
    private lateinit var smartspaceView: SmartspaceView


    private val clock = FakeSystemClock()
    private val clock = FakeSystemClock()
@@ -176,16 +181,22 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    fun setUp() {
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        MockitoAnnotations.initMocks(this)


        // Todo(b/261760571): flip the flag value here when feature is launched, and update relevant
        //  tests.
        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)

        `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
        `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
                .thenReturn(fakePrivateLockscreenSettingUri)
                .thenReturn(fakePrivateLockscreenSettingUri)
        `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
        `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
                .thenReturn(fakeNotifOnLockscreenSettingUri)
                .thenReturn(fakeNotifOnLockscreenSettingUri)
        `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
        `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
        `when`(weatherPlugin.getView(any())).thenReturn(
                createWeatherSmartspaceView(), createWeatherSmartspaceView())
        `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
        `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
        `when`(userTracker.userProfiles).thenReturn(userList)
        `when`(userTracker.userProfiles).thenReturn(userList)
        `when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
        `when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
        `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
        `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)


        setActiveUser(userHandlePrimary)
        setActiveUser(userHandlePrimary)
        setAllowPrivateNotifications(userHandlePrimary, true)
        setAllowPrivateNotifications(userHandlePrimary, true)
@@ -210,6 +221,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
                executor,
                executor,
                bgExecutor,
                bgExecutor,
                handler,
                handler,
                Optional.of(weatherPlugin),
                Optional.of(plugin),
                Optional.of(plugin),
                Optional.of(configPlugin),
                Optional.of(configPlugin),
        )
        )
@@ -218,11 +230,22 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        deviceProvisionedListener = deviceProvisionedCaptor.value
        deviceProvisionedListener = deviceProvisionedCaptor.value
    }
    }


    @Test(expected = RuntimeException::class)
    fun testBuildAndConnectWeatherView_throwsIfDecouplingDisabled() {
        // GIVEN the feature flag is disabled
        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)

        // WHEN we try to build the view
        controller.buildAndConnectWeatherView(fakeParent)

        // THEN an exception is thrown
    }

    @Test
    @Test
    fun connectOnlyAfterDeviceIsProvisioned() {
    fun testBuildAndConnectView_connectsOnlyAfterDeviceIsProvisioned() {
        // GIVEN an unprovisioned device and an attempt to connect
        // GIVEN an unprovisioned device and an attempt to connect
        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
        `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
        `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)


        // WHEN a connection attempt is made and view is attached
        // WHEN a connection attempt is made and view is attached
        val view = controller.buildAndConnectView(fakeParent)
        val view = controller.buildAndConnectView(fakeParent)
@@ -232,8 +255,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        verify(smartspaceManager, never()).createSmartspaceSession(any())
        verify(smartspaceManager, never()).createSmartspaceSession(any())


        // WHEN it does become provisioned
        // WHEN it does become provisioned
        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
        `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
        `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
        deviceProvisionedListener.onUserSetupChanged()
        deviceProvisionedListener.onUserSetupChanged()


        // THEN the session is created
        // THEN the session is created
@@ -243,7 +266,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    fun testListenersAreRegistered() {
    fun testAddListener_registersListenersForPlugin() {
        // GIVEN a listener is added after a session is created
        // GIVEN a listener is added after a session is created
        connectSession()
        connectSession()


@@ -252,10 +275,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {


        // THEN the listener is registered to the underlying plugin
        // THEN the listener is registered to the underlying plugin
        verify(plugin).registerListener(controllerListener)
        verify(plugin).registerListener(controllerListener)
        // The listener is registered only for the plugin, not the weather plugin.
        verify(weatherPlugin, never()).registerListener(any())
    }
    }


    @Test
    @Test
    fun testEarlyRegisteredListenersAreAttachedAfterConnected() {
    fun testAddListener_earlyRegisteredListenersAreAttachedAfterConnected() {
        // GIVEN a listener that is registered before the session is created
        // GIVEN a listener that is registered before the session is created
        controller.addListener(controllerListener)
        controller.addListener(controllerListener)


@@ -264,10 +289,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {


        // THEN the listener is subsequently registered
        // THEN the listener is subsequently registered
        verify(plugin).registerListener(controllerListener)
        verify(plugin).registerListener(controllerListener)
        // The listener is registered only for the plugin, not the weather plugin.
        verify(weatherPlugin, never()).registerListener(any())
    }
    }


    @Test
    @Test
    fun testEmptyListIsEmittedAndNotifierRemovedAfterDisconnect() {
    fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
        // GIVEN a registered listener on an active session
        // GIVEN a registered listener on an active session
        connectSession()
        connectSession()
        clearInvocations(plugin)
        clearInvocations(plugin)
@@ -279,10 +306,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        // THEN the listener receives an empty list of targets and unregisters the notifier
        // THEN the listener receives an empty list of targets and unregisters the notifier
        verify(plugin).onTargetsAvailable(emptyList())
        verify(plugin).onTargetsAvailable(emptyList())
        verify(plugin).registerSmartspaceEventNotifier(null)
        verify(plugin).registerSmartspaceEventNotifier(null)
        verify(weatherPlugin).onTargetsAvailable(emptyList())
        verify(weatherPlugin).registerSmartspaceEventNotifier(null)
    }
    }


    @Test
    @Test
    fun testUserChangeReloadsSmartspace() {
    fun testUserChange_reloadsSmartspace() {
        // GIVEN a connected smartspace session
        // GIVEN a connected smartspace session
        connectSession()
        connectSession()


@@ -294,7 +323,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    fun testSettingsChangeReloadsSmartspace() {
    fun testSettingsChange_reloadsSmartspace() {
        // GIVEN a connected smartspace session
        // GIVEN a connected smartspace session
        connectSession()
        connectSession()


@@ -306,7 +335,21 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    fun testThemeChangeUpdatesTextColor() {
    fun testThemeChange_updatesTextColor() {
        // GIVEN a connected smartspace session
        connectSession()

        // WHEN the theme changes
        configChangeListener.onThemeChanged()

        // We update the new text color to match the wallpaper color
        verify(smartspaceView).setPrimaryTextColor(anyInt())
    }

    @Test
    fun testThemeChange_ifDecouplingEnabled_updatesTextColor() {
        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)

        // GIVEN a connected smartspace session
        // GIVEN a connected smartspace session
        connectSession()
        connectSession()


@@ -314,11 +357,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        configChangeListener.onThemeChanged()
        configChangeListener.onThemeChanged()


        // We update the new text color to match the wallpaper color
        // We update the new text color to match the wallpaper color
        verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
        verify(smartspaceView).setPrimaryTextColor(anyInt())
        verify(smartspaceView).setPrimaryTextColor(anyInt())
    }
    }


    @Test
    @Test
    fun testDozeAmountChangeUpdatesView() {
    fun testDozeAmountChange_updatesView() {
        // GIVEN a connected smartspace session
        // GIVEN a connected smartspace session
        connectSession()
        connectSession()


@@ -330,7 +374,22 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    fun testKeyguardBypassEnabledUpdatesView() {
    fun testDozeAmountChange_ifDecouplingEnabled_updatesViews() {
        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)

        // GIVEN a connected smartspace session
        connectSession()

        // WHEN the doze amount changes
        statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)

        // We pass that along to the view
        verify(weatherSmartspaceView).setDozeAmount(0.7f)
        verify(smartspaceView).setDozeAmount(0.7f)
    }

    @Test
    fun testKeyguardBypassEnabled_updatesView() {
        // GIVEN a connected smartspace session
        // GIVEN a connected smartspace session
        connectSession()
        connectSession()
        `when`(keyguardBypassController.bypassEnabled).thenReturn(true)
        `when`(keyguardBypassController.bypassEnabled).thenReturn(true)
@@ -424,6 +483,27 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        )))
        )))
    }
    }


    @Test
    fun testSessionListener_ifDecouplingEnabled_weatherTargetIsFilteredOut() {
        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
        connectSession()

        // WHEN we receive a list of targets
        val targets = listOf(
                makeTarget(1, userHandlePrimary, isSensitive = true),
                makeTarget(2, userHandlePrimary),
                makeTarget(3, userHandleManaged),
                makeTarget(4, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER)
        )

        sessionListener.onTargetsAvailable(targets)

        // THEN all non-sensitive content is still shown
        verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2])))
        // No filtering is applied for the weather plugin
        verify(weatherPlugin).onTargetsAvailable(eq(targets))
    }

    @Test
    @Test
    fun testSettingsAreReloaded() {
    fun testSettingsAreReloaded() {
        // GIVEN a connected session where the privacy settings later flip to false
        // GIVEN a connected session where the privacy settings later flip to false
@@ -514,6 +594,16 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        verify(smartspaceView2).registerConfigProvider(configPlugin)
        verify(smartspaceView2).registerConfigProvider(configPlugin)
    }
    }


    @Test
    fun testWeatherViewUsesSameSession() {
        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
        // GIVEN a connected session
        connectSession()

        // No checks is needed here, since connectSession() already checks internally that
        // createSmartspaceSession is invoked only once.
    }

    @Test
    @Test
    fun testViewGetInitializedWithBypassEnabledState() {
    fun testViewGetInitializedWithBypassEnabledState() {
        // GIVEN keyguard bypass is enabled.
        // GIVEN keyguard bypass is enabled.
@@ -531,8 +621,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
    fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
        // GIVEN an uninitalized smartspaceView
        // GIVEN an uninitalized smartspaceView
        // WHEN the device is provisioned
        // WHEN the device is provisioned
        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
        `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
        `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
        deviceProvisionedListener.onDeviceProvisionedChanged()
        deviceProvisionedListener.onDeviceProvisionedChanged()


        // THEN no calls to createSmartspaceSession should occur
        // THEN no calls to createSmartspaceSession should occur
@@ -542,9 +632,23 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    }
    }


    private fun connectSession() {
    private fun connectSession() {
        if (controller.isDateWeatherDecoupled()) {
            val weatherView = controller.buildAndConnectWeatherView(fakeParent)
            weatherSmartspaceView = weatherView as SmartspaceView
            fakeParent.addView(weatherView)
            controller.stateChangeListener.onViewAttachedToWindow(weatherView)

            verify(weatherSmartspaceView).setUiSurface(
                    BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
            verify(weatherSmartspaceView).registerDataProvider(weatherPlugin)

            verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
            verify(weatherSmartspaceView).setDozeAmount(0.5f)
        }

        val view = controller.buildAndConnectView(fakeParent)
        val view = controller.buildAndConnectView(fakeParent)
        smartspaceView = view as SmartspaceView
        smartspaceView = view as SmartspaceView

        fakeParent.addView(view)
        controller.stateChangeListener.onViewAttachedToWindow(view)
        controller.stateChangeListener.onViewAttachedToWindow(view)


        verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
        verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
@@ -554,6 +658,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
                .addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor))
                .addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor))
        sessionListener = sessionListenerCaptor.value
        sessionListener = sessionListenerCaptor.value


        verify(smartspaceManager).createSmartspaceSession(any())

        verify(userTracker).addCallback(capture(userTrackerCaptor), any())
        verify(userTracker).addCallback(capture(userTrackerCaptor), any())
        userListener = userTrackerCaptor.value
        userListener = userTrackerCaptor.value


@@ -578,9 +684,11 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {


        verify(smartspaceView).setPrimaryTextColor(anyInt())
        verify(smartspaceView).setPrimaryTextColor(anyInt())
        verify(smartspaceView).setDozeAmount(0.5f)
        verify(smartspaceView).setDozeAmount(0.5f)
        clearInvocations(view)


        fakeParent.addView(view)
        if (controller.isDateWeatherDecoupled()) {
            clearInvocations(weatherSmartspaceView)
        }
        clearInvocations(smartspaceView)
    }
    }


    private fun setActiveUser(userHandle: UserHandle) {
    private fun setActiveUser(userHandle: UserHandle) {
@@ -626,6 +734,31 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        ).thenReturn(if (value) 1 else 0)
        ).thenReturn(if (value) 1 else 0)
    }
    }


    // Separate function for the weather view, which doesn't implement all functions in interface.
    private fun createWeatherSmartspaceView(): SmartspaceView {
        return spy(object : View(context), SmartspaceView {
            override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
            }

            override fun setPrimaryTextColor(color: Int) {
            }

            override fun setIsDreaming(isDreaming: Boolean) {
            }

            override fun setUiSurface(uiSurface: String) {
            }

            override fun setDozeAmount(amount: Float) {
            }

            override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
            }

            override fun setFalsingManager(falsingManager: FalsingManager?) {
            }
        })
    }
    private fun createSmartspaceView(): SmartspaceView {
    private fun createSmartspaceView(): SmartspaceView {
        return spy(object : View(context), SmartspaceView {
        return spy(object : View(context), SmartspaceView {
            override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
            override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {