Merge "Connect weather complication view with DreamSmartspaceController" into tm-qpr-dev
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user