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

Commit 0252762d authored by Xiaowen Lei's avatar Xiaowen Lei Committed by Android (Google) Code Review
Browse files

Merge "[HSUM] For Dream, use SmartspaceManager in the current foreground user context." into main

parents 2980e562 51176ed5
Loading
Loading
Loading
Loading
+59 −64
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.mockito.any
@@ -46,66 +47,51 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
import org.mockito.MockitoAnnotations
import org.mockito.Spy

@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class DreamSmartspaceControllerTest : SysuiTestCase() {
    @Mock
    private lateinit var smartspaceManager: SmartspaceManager
    @Mock private lateinit var userTracker: UserTracker

    @Mock
    private lateinit var execution: Execution
    @Mock private lateinit var userContextPrimary: Context

    @Mock
    private lateinit var uiExecutor: Executor
    @Mock private lateinit var smartspaceManager: SmartspaceManager

    @Mock
    private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
    @Mock private lateinit var execution: Execution

    @Mock
    private lateinit var viewComponent: SmartspaceViewComponent
    @Mock private lateinit var uiExecutor: Executor

    @Mock
    private lateinit var weatherViewComponent: SmartspaceViewComponent
    @Mock private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory

    private val weatherSmartspaceView: SmartspaceView by lazy {
        Mockito.spy(TestView(context))
    }
    @Mock private lateinit var viewComponent: SmartspaceViewComponent

    @Mock
    private lateinit var targetFilter: SmartspaceTargetFilter
    @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent

    @Mock
    private lateinit var plugin: BcSmartspaceDataPlugin
    private val weatherSmartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }

    @Mock
    private lateinit var weatherPlugin: BcSmartspaceDataPlugin
    @Mock private lateinit var targetFilter: SmartspaceTargetFilter

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

    private val smartspaceView: SmartspaceView by lazy {
        Mockito.spy(TestView(context))
    }
    @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin

    @Mock private lateinit var precondition: SmartspacePrecondition

    @Mock
    private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
    private val smartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }

    @Mock
    private lateinit var session: SmartspaceSession
    @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener

    @Mock private lateinit var session: SmartspaceSession

    private lateinit var controller: DreamSmartspaceController

    // TODO(b/272811280): Remove usage of real view
    private val fakeParent by lazy {
        FrameLayout(context)
    }
    private val fakeParent by lazy { FrameLayout(context) }

    /**
     * A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -134,9 +120,13 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {

        override fun setMediaTarget(target: SmartspaceTarget?) {}

        override fun getSelectedPage(): Int { return 0; }
        override fun getSelectedPage(): Int {
            return 0
        }

        override fun getCurrentCardTopPadding(): Int { return 0; }
        override fun getCurrentCardTopPadding(): Int {
            return 0
        }
    }

    @Before
@@ -150,14 +140,24 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
        `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),
        Optional.of(weatherPlugin))
        `when`(userTracker.userContext).thenReturn(userContextPrimary)
        `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
            .thenReturn(smartspaceManager)

        controller =
            DreamSmartspaceController(
                userTracker,
                execution,
                uiExecutor,
                viewComponentFactory,
                precondition,
                Optional.of(targetFilter),
                Optional.of(plugin),
                Optional.of(weatherPlugin),
            )
    }

    /**
     * Ensures smartspace session begins on a listener only flow.
     */
    /** Ensures smartspace session begins on a listener only flow. */
    @Test
    fun testConnectOnListen() {
        `when`(precondition.conditionsMet()).thenReturn(true)
@@ -165,7 +165,8 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {

        verify(smartspaceManager).createSmartspaceSession(any())

        var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
        var targetListener =
            withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
                verify(session).addOnTargetsAvailableListener(any(), capture())
            }

@@ -174,9 +175,8 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
        var target = Mockito.mock(SmartspaceTarget::class.java)
        targetListener.onTargetsAvailable(listOf(target))

        var targets = withArgCaptor<List<SmartspaceTarget>> {
            verify(plugin).onTargetsAvailable(capture())
        }
        var targets =
            withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) }

        assertThat(targets.contains(target)).isTrue()

@@ -185,15 +185,14 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
        verify(session).close()
    }

    /**
     * Ensures session begins when a view is attached.
     */
    /** Ensures session begins when a view is attached. */
    @Test
    fun testConnectOnViewCreate() {
        `when`(precondition.conditionsMet()).thenReturn(true)
        controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))

        val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
        val stateChangeListener =
            withArgCaptor<View.OnAttachStateChangeListener> {
                verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
            }

@@ -209,9 +208,7 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
        verify(session).close()
    }

    /**
     * Ensures session is created when weather smartspace view is created and attached.
     */
    /** Ensures session is created when weather smartspace view is created and attached. */
    @Test
    fun testConnectOnWeatherViewCreate() {
        `when`(precondition.conditionsMet()).thenReturn(true)
@@ -223,8 +220,8 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {

        // 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(viewComponentFactory)
            .create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView))
        verify(weatherPlugin, Mockito.never()).getView(fakeParent)

        // And then session is created
@@ -234,9 +231,7 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
        verify(weatherSmartspaceView).setDozeAmount(0f)
    }

    /**
     * Ensures weather plugin registers target listener when it is added from the controller.
     */
    /** Ensures weather plugin registers target listener when it is added from the controller. */
    @Test
    fun testAddListenerInController_registersListenerForWeatherPlugin() {
        val customView = Mockito.mock(TestView::class.java)
+67 −63
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.graphics.Color
import android.util.Log
import android.view.View
@@ -31,6 +30,7 @@ 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.settings.UserTracker
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
@@ -44,13 +44,12 @@ import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Named

/**
 * Controller for managing the smartspace view on the dream
 */
/** Controller for managing the smartspace view on the dream */
@SysUISingleton
class DreamSmartspaceController @Inject constructor(
    private val context: Context,
    private val smartspaceManager: SmartspaceManager?,
class DreamSmartspaceController
@Inject
constructor(
    private val userTracker: UserTracker,
    private val execution: Execution,
    @Main private val uiExecutor: Executor,
    private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
@@ -65,6 +64,7 @@ class DreamSmartspaceController @Inject constructor(
        private const val TAG = "DreamSmartspaceCtrlr"
    }

    private var userSmartspaceManager: SmartspaceManager? = null
    private var session: SmartspaceSession? = null
    private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
    private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -78,7 +78,8 @@ class DreamSmartspaceController @Inject constructor(
    // Smartspace can be used on multiple displays, such as when the user casts their screen
    private var smartspaceViews = mutableSetOf<SmartspaceView>()

    var preconditionListener = object : SmartspacePrecondition.Listener {
    var preconditionListener =
        object : SmartspacePrecondition.Listener {
            override fun onCriteriaChanged() {
                reloadSmartspace()
            }
@@ -88,7 +89,8 @@ class DreamSmartspaceController @Inject constructor(
        precondition.addListener(preconditionListener)
    }

    var filterListener = object : SmartspaceTargetFilter.Listener {
    var filterListener =
        object : SmartspaceTargetFilter.Listener {
            override fun onCriteriaChanged() {
                reloadSmartspace()
            }
@@ -98,7 +100,8 @@ class DreamSmartspaceController @Inject constructor(
        targetFilter?.addListener(filterListener)
    }

    var stateChangeListener = object : View.OnAttachStateChangeListener {
    var stateChangeListener =
        object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(v: View) {
                val view = v as SmartspaceView
                // Until there is dream color matching
@@ -117,27 +120,26 @@ class DreamSmartspaceController @Inject constructor(
            }
        }

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

        // The weather data plugin takes unfiltered targets and performs the filtering internally.
            // 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 }
            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.
     */
    /** 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.
     */
    /** Constructs the smartspace view and connects it to the smartspace service. */
    fun buildAndConnectView(parent: ViewGroup): View? {
        return buildAndConnectViewWithPlugin(parent, plugin, null)
    }
@@ -145,7 +147,7 @@ class DreamSmartspaceController @Inject constructor(
    private fun buildAndConnectViewWithPlugin(
        parent: ViewGroup,
        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
        customView: View?
        customView: View?,
    ): View? {
        execution.assertIsMainThread()

@@ -163,11 +165,12 @@ class DreamSmartspaceController @Inject constructor(
    private fun buildView(
        parent: ViewGroup,
        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
        customView: View?
        customView: View?,
    ): View? {
        return if (smartspaceDataPlugin != null) {
            val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
                stateChangeListener, customView)
            val view =
                smartspaceViewComponentFactory
                    .create(parent, smartspaceDataPlugin, stateChangeListener, customView)
                    .getView()
            if (view !is View) {
                return null
@@ -179,12 +182,17 @@ class DreamSmartspaceController @Inject constructor(
    }

    private fun hasActiveSessionListeners(): Boolean {
        return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() ||
        return smartspaceViews.isNotEmpty() ||
            listeners.isNotEmpty() ||
            unfilteredListeners.isNotEmpty()
    }

    private fun connectSession() {
        if (smartspaceManager == null) {
        if (userSmartspaceManager == null) {
            userSmartspaceManager =
                userTracker.userContext.getSystemService(SmartspaceManager::class.java)
        }
        if (userSmartspaceManager == null) {
            return
        }
        if (plugin == null && weatherPlugin == null) {
@@ -198,25 +206,21 @@ class DreamSmartspaceController @Inject constructor(
            return
        }

        val newSession = smartspaceManager.createSmartspaceSession(
            SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
        val newSession =
            userSmartspaceManager?.createSmartspaceSession(
                SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_DREAM).build()
            )
        Log.d(TAG, "Starting smartspace session for dream")
        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
        newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
        this.session = newSession

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

        reloadSmartspace()
    }

    /**
     * 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. */
    private fun disconnect() {
        if (hasActiveSessionListeners()) return

@@ -259,7 +263,7 @@ class DreamSmartspaceController @Inject constructor(

    private fun addAndRegisterListener(
        listener: SmartspaceTargetListener,
        smartspaceDataPlugin: BcSmartspaceDataPlugin?
        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
    ) {
        execution.assertIsMainThread()
        smartspaceDataPlugin?.registerListener(listener)
@@ -270,7 +274,7 @@ class DreamSmartspaceController @Inject constructor(

    private fun removeAndUnregisterListener(
        listener: SmartspaceTargetListener,
        smartspaceDataPlugin: BcSmartspaceDataPlugin?
        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
    ) {
        execution.assertIsMainThread()
        smartspaceDataPlugin?.unregisterListener(listener)