Loading packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +1 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ public interface RegisteredComplicationsModule { int DREAM_MEDIA_COMPLICATION_WEIGHT = 0; int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4; int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3; int DREAM_WEATHER_COMPLICATION_WEIGHT = 0; /** * Provides layout parameters for the clock time complication. Loading packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt +68 −12 Original line number Diff line number Diff line Loading @@ -30,14 +30,15 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM import com.android.systemui.smartspace.SmartspacePrecondition import com.android.systemui.smartspace.SmartspaceTargetFilter import com.android.systemui.smartspace.dagger.SmartspaceModule import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER import com.android.systemui.smartspace.dagger.SmartspaceViewComponent import com.android.systemui.util.concurrency.Execution import java.lang.RuntimeException import java.util.Optional import java.util.concurrent.Executor import javax.inject.Inject Loading @@ -56,13 +57,16 @@ class DreamSmartspaceController @Inject constructor( @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition, @Named(DREAM_SMARTSPACE_TARGET_FILTER) private val optionalTargetFilter: Optional<SmartspaceTargetFilter>, @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin> @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>, @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN) optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>, ) { companion object { private const val TAG = "DreamSmartspaceCtrlr" } private var session: SmartspaceSession? = null private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null) private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null) Loading Loading @@ -116,31 +120,54 @@ class DreamSmartspaceController @Inject constructor( private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> execution.assertIsMainThread() // The weather data plugin takes unfiltered targets and performs the filtering internally. weatherPlugin?.onTargetsAvailable(targets) onTargetsAvailableUnfiltered(targets) val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true } plugin?.onTargetsAvailable(filteredTargets) } /** * Constructs the weather view with custom layout and connects it to the weather plugin. */ fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? { return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView) } /** * Constructs the smartspace view and connects it to the smartspace service. */ fun buildAndConnectView(parent: ViewGroup): View? { return buildAndConnectViewWithPlugin(parent, plugin, null) } private fun buildAndConnectViewWithPlugin( parent: ViewGroup, smartspaceDataPlugin: BcSmartspaceDataPlugin?, customView: View? ): View? { execution.assertIsMainThread() if (!precondition.conditionsMet()) { throw RuntimeException("Cannot build view when not enabled") } val view = buildView(parent) val view = buildView(parent, smartspaceDataPlugin, customView) connectSession() return view } private fun buildView(parent: ViewGroup): View? { return if (plugin != null) { var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener) private fun buildView( parent: ViewGroup, smartspaceDataPlugin: BcSmartspaceDataPlugin?, customView: View? ): View? { return if (smartspaceDataPlugin != null) { val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin, stateChangeListener, customView) .getView() if (view !is View) { return null Loading @@ -157,7 +184,10 @@ class DreamSmartspaceController @Inject constructor( } private fun connectSession() { if (plugin == null || session != null || !hasActiveSessionListeners()) { if (plugin == null && weatherPlugin == null) { return } if (session != null || !hasActiveSessionListeners()) { return } Loading @@ -166,13 +196,14 @@ class DreamSmartspaceController @Inject constructor( } val newSession = smartspaceManager.createSmartspaceSession( SmartspaceConfig.Builder(context, "dream").build() SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build() ) Log.d(TAG, "Starting smartspace session for dream") newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) this.session = newSession plugin.registerSmartspaceEventNotifier { weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } Loading @@ -199,22 +230,47 @@ class DreamSmartspaceController @Inject constructor( session = null weatherPlugin?.registerSmartspaceEventNotifier(null) weatherPlugin?.onTargetsAvailable(emptyList()) plugin?.registerSmartspaceEventNotifier(null) plugin?.onTargetsAvailable(emptyList()) Log.d(TAG, "Ending smartspace session for dream") } fun addListener(listener: SmartspaceTargetListener) { addAndRegisterListener(listener, plugin) } fun removeListener(listener: SmartspaceTargetListener) { removeAndUnregisterListener(listener, plugin) } fun addListenerForWeatherPlugin(listener: SmartspaceTargetListener) { addAndRegisterListener(listener, weatherPlugin) } fun removeListenerForWeatherPlugin(listener: SmartspaceTargetListener) { removeAndUnregisterListener(listener, weatherPlugin) } private fun addAndRegisterListener( listener: SmartspaceTargetListener, smartspaceDataPlugin: BcSmartspaceDataPlugin? ) { execution.assertIsMainThread() plugin?.registerListener(listener) smartspaceDataPlugin?.registerListener(listener) listeners.add(listener) connectSession() } fun removeListener(listener: SmartspaceTargetListener) { private fun removeAndUnregisterListener( listener: SmartspaceTargetListener, smartspaceDataPlugin: BcSmartspaceDataPlugin? ) { execution.assertIsMainThread() plugin?.unregisterListener(listener) smartspaceDataPlugin?.unregisterListener(listener) listeners.remove(listener) disconnect() } Loading packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt +6 −2 Original line number Diff line number Diff line Loading @@ -37,7 +37,8 @@ interface SmartspaceViewComponent { fun create( @BindsInstance parent: ViewGroup, @BindsInstance @Named(PLUGIN) plugin: BcSmartspaceDataPlugin, @BindsInstance onAttachListener: View.OnAttachStateChangeListener @BindsInstance onAttachListener: View.OnAttachStateChangeListener, @BindsInstance viewWithCustomLayout: View? = null ): SmartspaceViewComponent } Loading @@ -53,10 +54,13 @@ interface SmartspaceViewComponent { falsingManager: FalsingManager, parent: ViewGroup, @Named(PLUGIN) plugin: BcSmartspaceDataPlugin, viewWithCustomLayout: View?, onAttachListener: View.OnAttachStateChangeListener ): BcSmartspaceDataPlugin.SmartspaceView { val ssView = plugin.getView(parent) val ssView = viewWithCustomLayout as? BcSmartspaceDataPlugin.SmartspaceView ?: plugin.getView(parent) // Currently, this is only used to provide SmartspaceView on Dream surface. ssView.setUiSurface(UI_SURFACE_DREAM) ssView.registerDataProvider(plugin) Loading packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt +93 −5 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dreams.smartspace.DreamSmartspaceController Loading @@ -46,6 +47,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.Mockito.anyInt import org.mockito.MockitoAnnotations import org.mockito.Spy Loading @@ -68,12 +70,21 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { @Mock private lateinit var viewComponent: SmartspaceViewComponent @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent @Spy private var weatherSmartspaceView: SmartspaceView = TestView(context) @Mock private lateinit var targetFilter: SmartspaceTargetFilter @Mock private lateinit var plugin: BcSmartspaceDataPlugin @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin @Mock private lateinit var precondition: SmartspacePrecondition Loading @@ -88,6 +99,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { private lateinit var controller: DreamSmartspaceController // TODO(b/272811280): Remove usage of real view private val fakeParent = FrameLayout(context) /** * A class which implements SmartspaceView and extends View. This is mocked to provide the right * object inheritance and interface implementation used in DreamSmartspaceController Loading Loading @@ -121,13 +135,17 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) `when`(viewComponentFactory.create(any(), eq(plugin), any())) `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null))) .thenReturn(viewComponent) `when`(viewComponent.getView()).thenReturn(smartspaceView) `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any())) .thenReturn(weatherViewComponent) `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView) `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session) controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor, viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin)) viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin), Optional.of(weatherPlugin)) } /** Loading Loading @@ -168,11 +186,11 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { `when`(precondition.conditionsMet()).thenReturn(true) controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java)) var stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> { verify(viewComponentFactory).create(any(), eq(plugin), capture()) val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> { verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null)) } var mockView = Mockito.mock(TestView::class.java) val mockView = Mockito.mock(TestView::class.java) `when`(precondition.conditionsMet()).thenReturn(true) stateChangeListener.onViewAttachedToWindow(mockView) Loading @@ -183,4 +201,74 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { verify(session).close() } /** * Ensures session is created when weather smartspace view is created and attached. */ @Test fun testConnectOnWeatherViewCreate() { `when`(precondition.conditionsMet()).thenReturn(true) val customView = Mockito.mock(TestView::class.java) val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView) val weatherSmartspaceView = weatherView as SmartspaceView fakeParent.addView(weatherView) // Then weather view is created with custom view and the default weatherPlugin.getView // should not be called verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView)) verify(weatherPlugin, Mockito.never()).getView(fakeParent) // And then session is created controller.stateChangeListener.onViewAttachedToWindow(weatherView) verify(smartspaceManager).createSmartspaceSession(any()) verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) verify(weatherSmartspaceView).setDozeAmount(0f) } /** * Ensures weather plugin registers target listener when it is added from the controller. */ @Test fun testAddListenerInController_registersListenerForWeatherPlugin() { val customView = Mockito.mock(TestView::class.java) `when`(precondition.conditionsMet()).thenReturn(true) // Given a session is created val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView) controller.stateChangeListener.onViewAttachedToWindow(weatherView) verify(smartspaceManager).createSmartspaceSession(any()) // When a listener is added controller.addListenerForWeatherPlugin(listener) // Then the listener is registered to the weather plugin only verify(weatherPlugin).registerListener(listener) verify(plugin, Mockito.never()).registerListener(any()) } /** * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace * view is detached. */ @Test fun testDisconnect_emitsEmptyListAndRemovesNotifier() { `when`(precondition.conditionsMet()).thenReturn(true) // Given a session is created val customView = Mockito.mock(TestView::class.java) val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView) controller.stateChangeListener.onViewAttachedToWindow(weatherView) verify(smartspaceManager).createSmartspaceSession(any()) // When view is detached controller.stateChangeListener.onViewDetachedFromWindow(weatherView) // Then the session is closed verify(session).close() // And the listener receives an empty list of targets and unregisters the notifier verify(weatherPlugin).onTargetsAvailable(emptyList()) verify(weatherPlugin).registerSmartspaceEventNotifier(null) } } Loading
packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +1 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ public interface RegisteredComplicationsModule { int DREAM_MEDIA_COMPLICATION_WEIGHT = 0; int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4; int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3; int DREAM_WEATHER_COMPLICATION_WEIGHT = 0; /** * Provides layout parameters for the clock time complication. Loading
packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt +68 −12 Original line number Diff line number Diff line Loading @@ -30,14 +30,15 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM import com.android.systemui.smartspace.SmartspacePrecondition import com.android.systemui.smartspace.SmartspaceTargetFilter import com.android.systemui.smartspace.dagger.SmartspaceModule import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER import com.android.systemui.smartspace.dagger.SmartspaceViewComponent import com.android.systemui.util.concurrency.Execution import java.lang.RuntimeException import java.util.Optional import java.util.concurrent.Executor import javax.inject.Inject Loading @@ -56,13 +57,16 @@ class DreamSmartspaceController @Inject constructor( @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition, @Named(DREAM_SMARTSPACE_TARGET_FILTER) private val optionalTargetFilter: Optional<SmartspaceTargetFilter>, @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin> @Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>, @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN) optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>, ) { companion object { private const val TAG = "DreamSmartspaceCtrlr" } private var session: SmartspaceSession? = null private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null) private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null) Loading Loading @@ -116,31 +120,54 @@ class DreamSmartspaceController @Inject constructor( private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> execution.assertIsMainThread() // The weather data plugin takes unfiltered targets and performs the filtering internally. weatherPlugin?.onTargetsAvailable(targets) onTargetsAvailableUnfiltered(targets) val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true } plugin?.onTargetsAvailable(filteredTargets) } /** * Constructs the weather view with custom layout and connects it to the weather plugin. */ fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? { return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView) } /** * Constructs the smartspace view and connects it to the smartspace service. */ fun buildAndConnectView(parent: ViewGroup): View? { return buildAndConnectViewWithPlugin(parent, plugin, null) } private fun buildAndConnectViewWithPlugin( parent: ViewGroup, smartspaceDataPlugin: BcSmartspaceDataPlugin?, customView: View? ): View? { execution.assertIsMainThread() if (!precondition.conditionsMet()) { throw RuntimeException("Cannot build view when not enabled") } val view = buildView(parent) val view = buildView(parent, smartspaceDataPlugin, customView) connectSession() return view } private fun buildView(parent: ViewGroup): View? { return if (plugin != null) { var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener) private fun buildView( parent: ViewGroup, smartspaceDataPlugin: BcSmartspaceDataPlugin?, customView: View? ): View? { return if (smartspaceDataPlugin != null) { val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin, stateChangeListener, customView) .getView() if (view !is View) { return null Loading @@ -157,7 +184,10 @@ class DreamSmartspaceController @Inject constructor( } private fun connectSession() { if (plugin == null || session != null || !hasActiveSessionListeners()) { if (plugin == null && weatherPlugin == null) { return } if (session != null || !hasActiveSessionListeners()) { return } Loading @@ -166,13 +196,14 @@ class DreamSmartspaceController @Inject constructor( } val newSession = smartspaceManager.createSmartspaceSession( SmartspaceConfig.Builder(context, "dream").build() SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build() ) Log.d(TAG, "Starting smartspace session for dream") newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) this.session = newSession plugin.registerSmartspaceEventNotifier { weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } Loading @@ -199,22 +230,47 @@ class DreamSmartspaceController @Inject constructor( session = null weatherPlugin?.registerSmartspaceEventNotifier(null) weatherPlugin?.onTargetsAvailable(emptyList()) plugin?.registerSmartspaceEventNotifier(null) plugin?.onTargetsAvailable(emptyList()) Log.d(TAG, "Ending smartspace session for dream") } fun addListener(listener: SmartspaceTargetListener) { addAndRegisterListener(listener, plugin) } fun removeListener(listener: SmartspaceTargetListener) { removeAndUnregisterListener(listener, plugin) } fun addListenerForWeatherPlugin(listener: SmartspaceTargetListener) { addAndRegisterListener(listener, weatherPlugin) } fun removeListenerForWeatherPlugin(listener: SmartspaceTargetListener) { removeAndUnregisterListener(listener, weatherPlugin) } private fun addAndRegisterListener( listener: SmartspaceTargetListener, smartspaceDataPlugin: BcSmartspaceDataPlugin? ) { execution.assertIsMainThread() plugin?.registerListener(listener) smartspaceDataPlugin?.registerListener(listener) listeners.add(listener) connectSession() } fun removeListener(listener: SmartspaceTargetListener) { private fun removeAndUnregisterListener( listener: SmartspaceTargetListener, smartspaceDataPlugin: BcSmartspaceDataPlugin? ) { execution.assertIsMainThread() plugin?.unregisterListener(listener) smartspaceDataPlugin?.unregisterListener(listener) listeners.remove(listener) disconnect() } Loading
packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt +6 −2 Original line number Diff line number Diff line Loading @@ -37,7 +37,8 @@ interface SmartspaceViewComponent { fun create( @BindsInstance parent: ViewGroup, @BindsInstance @Named(PLUGIN) plugin: BcSmartspaceDataPlugin, @BindsInstance onAttachListener: View.OnAttachStateChangeListener @BindsInstance onAttachListener: View.OnAttachStateChangeListener, @BindsInstance viewWithCustomLayout: View? = null ): SmartspaceViewComponent } Loading @@ -53,10 +54,13 @@ interface SmartspaceViewComponent { falsingManager: FalsingManager, parent: ViewGroup, @Named(PLUGIN) plugin: BcSmartspaceDataPlugin, viewWithCustomLayout: View?, onAttachListener: View.OnAttachStateChangeListener ): BcSmartspaceDataPlugin.SmartspaceView { val ssView = plugin.getView(parent) val ssView = viewWithCustomLayout as? BcSmartspaceDataPlugin.SmartspaceView ?: plugin.getView(parent) // Currently, this is only used to provide SmartspaceView on Dream surface. ssView.setUiSurface(UI_SURFACE_DREAM) ssView.registerDataProvider(plugin) Loading
packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt +93 −5 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dreams.smartspace.DreamSmartspaceController Loading @@ -46,6 +47,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.Mockito.anyInt import org.mockito.MockitoAnnotations import org.mockito.Spy Loading @@ -68,12 +70,21 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { @Mock private lateinit var viewComponent: SmartspaceViewComponent @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent @Spy private var weatherSmartspaceView: SmartspaceView = TestView(context) @Mock private lateinit var targetFilter: SmartspaceTargetFilter @Mock private lateinit var plugin: BcSmartspaceDataPlugin @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin @Mock private lateinit var precondition: SmartspacePrecondition Loading @@ -88,6 +99,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { private lateinit var controller: DreamSmartspaceController // TODO(b/272811280): Remove usage of real view private val fakeParent = FrameLayout(context) /** * A class which implements SmartspaceView and extends View. This is mocked to provide the right * object inheritance and interface implementation used in DreamSmartspaceController Loading Loading @@ -121,13 +135,17 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) `when`(viewComponentFactory.create(any(), eq(plugin), any())) `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null))) .thenReturn(viewComponent) `when`(viewComponent.getView()).thenReturn(smartspaceView) `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any())) .thenReturn(weatherViewComponent) `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView) `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session) controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor, viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin)) viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin), Optional.of(weatherPlugin)) } /** Loading Loading @@ -168,11 +186,11 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { `when`(precondition.conditionsMet()).thenReturn(true) controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java)) var stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> { verify(viewComponentFactory).create(any(), eq(plugin), capture()) val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> { verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null)) } var mockView = Mockito.mock(TestView::class.java) val mockView = Mockito.mock(TestView::class.java) `when`(precondition.conditionsMet()).thenReturn(true) stateChangeListener.onViewAttachedToWindow(mockView) Loading @@ -183,4 +201,74 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { verify(session).close() } /** * Ensures session is created when weather smartspace view is created and attached. */ @Test fun testConnectOnWeatherViewCreate() { `when`(precondition.conditionsMet()).thenReturn(true) val customView = Mockito.mock(TestView::class.java) val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView) val weatherSmartspaceView = weatherView as SmartspaceView fakeParent.addView(weatherView) // Then weather view is created with custom view and the default weatherPlugin.getView // should not be called verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView)) verify(weatherPlugin, Mockito.never()).getView(fakeParent) // And then session is created controller.stateChangeListener.onViewAttachedToWindow(weatherView) verify(smartspaceManager).createSmartspaceSession(any()) verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) verify(weatherSmartspaceView).setDozeAmount(0f) } /** * Ensures weather plugin registers target listener when it is added from the controller. */ @Test fun testAddListenerInController_registersListenerForWeatherPlugin() { val customView = Mockito.mock(TestView::class.java) `when`(precondition.conditionsMet()).thenReturn(true) // Given a session is created val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView) controller.stateChangeListener.onViewAttachedToWindow(weatherView) verify(smartspaceManager).createSmartspaceSession(any()) // When a listener is added controller.addListenerForWeatherPlugin(listener) // Then the listener is registered to the weather plugin only verify(weatherPlugin).registerListener(listener) verify(plugin, Mockito.never()).registerListener(any()) } /** * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace * view is detached. */ @Test fun testDisconnect_emitsEmptyListAndRemovesNotifier() { `when`(precondition.conditionsMet()).thenReturn(true) // Given a session is created val customView = Mockito.mock(TestView::class.java) val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView) controller.stateChangeListener.onViewAttachedToWindow(weatherView) verify(smartspaceManager).createSmartspaceSession(any()) // When view is detached controller.stateChangeListener.onViewDetachedFromWindow(weatherView) // Then the session is closed verify(session).close() // And the listener receives an empty list of targets and unregisters the notifier verify(weatherPlugin).onTargetsAvailable(emptyList()) verify(weatherPlugin).registerSmartspaceEventNotifier(null) } }