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

Commit 6ad767a0 authored by Matt Pietal's avatar Matt Pietal
Browse files

Support multiple smartspace views

Previously, a singleton view was reused in multiple places. However,
this does not work when multiple displays are in use, such as when
casting the device display to another screen. Supports sets of views
that are activated when they are attached to windows, and removed when
they are detached.

Fixes: 196175396
Test: atest LockscreenSmartspaceControllerTest
Change-Id: Ied1ecff9103d8539f075785409d2951a1be21ab2
parent a3391604
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -239,16 +239,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
        mView.setClockPlugin(null, mStatusBarStateController.getState());

        mSmartspaceController.disconnect();

        // TODO: This is an unfortunate necessity since smartspace plugin retains a single instance
        // of the smartspace view -- if we don't remove the view, it can't be reused by a later
        // instance of this class. In order to fix this, we need to modify the plugin so that
        // (a) we get a new view each time and (b) we can properly clean up an old view by making
        // it unregister itself as a plugin listener.
        if (mSmartspaceView != null) {
            mView.removeView(mSmartspaceView);
            mSmartspaceView = null;
        }
    }

    /**
+23 −24
Original line number Diff line number Diff line
@@ -74,15 +74,27 @@ class LockscreenSmartspaceController @Inject constructor(
) {
    private var session: SmartspaceSession? = null
    private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
    private lateinit var smartspaceView: SmartspaceView

    lateinit var view: View
        private set
    // Smartspace can be used on multiple displays, such as when the user casts their screen
    private var smartspaceViews = mutableSetOf<SmartspaceView>()

    private var showSensitiveContentForCurrentUser = false
    private var showSensitiveContentForManagedUser = false
    private var managedUserHandle: UserHandle? = null

    var stateChangeListener = object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(v: View) {
            smartspaceViews.add(v as SmartspaceView)

            updateTextColorFromWallpaper()
            statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
        }

        override fun onViewDetachedFromWindow(v: View) {
            smartspaceViews.remove(v as SmartspaceView)
        }
    }

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

@@ -90,17 +102,16 @@ class LockscreenSmartspaceController @Inject constructor(
    }

    /**
     * Constructs the smartspace view and connects it to the smartspace service. Subsequent calls
     * are idempotent until [disconnect] is called.
     * Constructs the smartspace view and connects it to the smartspace service.
     */
    fun buildAndConnectView(parent: ViewGroup): View {
    fun buildAndConnectView(parent: ViewGroup): View? {
        execution.assertIsMainThread()

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

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

        return view
@@ -110,14 +121,9 @@ class LockscreenSmartspaceController @Inject constructor(
        session?.requestSmartspaceUpdate()
    }

    private fun buildView(parent: ViewGroup) {
    private fun buildView(parent: ViewGroup): View? {
        if (plugin == null) {
            return
        }
        if (this::view.isInitialized) {
            // Due to some oddities with a singleton smartspace view, allow reparenting
            (view.getParent() as ViewGroup?)?.removeView(view)
            return
            return null
        }

        val ssView = plugin.getView(parent)
@@ -132,12 +138,7 @@ class LockscreenSmartspaceController @Inject constructor(
            }
        })
        ssView.setFalsingManager(falsingManager)

        this.smartspaceView = ssView
        this.view = ssView as View

        updateTextColorFromWallpaper()
        statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
        return (ssView as View).apply { addOnAttachStateChangeListener(stateChangeListener) }
    }

    private fun connectSession() {
@@ -165,8 +166,6 @@ class LockscreenSmartspaceController @Inject constructor(

    /**
     * Disconnects the smartspace view from the smartspace service and cleans up any resources.
     * Calling [buildAndConnectView] again will cause the same view to be reconnected to the
     * service.
     */
    fun disconnect() {
        execution.assertIsMainThread()
@@ -231,7 +230,7 @@ class LockscreenSmartspaceController @Inject constructor(
    private val statusBarStateListener = object : StatusBarStateController.StateListener {
        override fun onDozeAmountChanged(linear: Float, eased: Float) {
            execution.assertIsMainThread()
            smartspaceView.setDozeAmount(eased)
            smartspaceViews.forEach { it.setDozeAmount(eased) }
        }
    }

@@ -256,7 +255,7 @@ class LockscreenSmartspaceController @Inject constructor(

    private fun updateTextColorFromWallpaper() {
        val wallpaperTextColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
        smartspaceView.setPrimaryTextColor(wallpaperTextColor)
        smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }
    }

    private fun reloadSmartspace() {
+40 −47
Original line number Diff line number Diff line
@@ -51,7 +51,6 @@ import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
@@ -114,6 +113,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    private lateinit var configChangeListener: ConfigurationListener
    private lateinit var statusBarStateListener: StateListener

    private lateinit var smartspaceView: SmartspaceView

    private val clock = FakeSystemClock()
    private val executor = FakeExecutor(clock)
    private val execution = FakeExecution()
@@ -141,7 +142,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
                .thenReturn(fakePrivateLockscreenSettingUri)
        `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
        `when`(plugin.getView(any())).thenReturn(fakeSmartspaceView)
        `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
        `when`(userTracker.userProfiles).thenReturn(userList)
        `when`(statusBarStateController.dozeAmount).thenReturn(0.5f)

@@ -249,7 +250,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        configChangeListener.onThemeChanged()

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

    @Test
@@ -261,7 +262,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)

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

    @Test
@@ -393,40 +394,30 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
    }

    @Test
    fun testBuildViewIsIdempotent() {
        // GIVEN a connected session
        connectSession()
        clearInvocations(plugin)

        // WHEN we disconnect and then reconnect
        controller.disconnect()
        controller.buildAndConnectView(fakeParent)

        // THEN the view is not rebuilt
        verify(plugin, never()).getView(any())
        assertEquals(fakeSmartspaceView, controller.view)
    }

    @Test
    fun testDoubleConnectIsIgnored() {
    fun testMultipleViewsUseSameSession() {
        // GIVEN a connected session
        connectSession()
        clearInvocations(smartspaceManager)
        clearInvocations(plugin)

        // WHEN we're asked to connect a second time and add to a parent
        // WHEN we're asked to connect a second time and add to a parent. If the same view
        // was created the ViewGroup will throw an exception
        val view = controller.buildAndConnectView(fakeParent)
        fakeParent.addView(view)
        val smartspaceView2 = view as SmartspaceView

        // THEN the existing view and session are reused
        // THEN the existing session is reused and views are registered
        verify(smartspaceManager, never()).createSmartspaceSession(any())
        verify(plugin, never()).getView(any())
        assertEquals(fakeSmartspaceView, controller.view)
        verify(smartspaceView2).registerDataProvider(plugin)
    }

    private fun connectSession() {
        controller.buildAndConnectView(fakeParent)
        val view = controller.buildAndConnectView(fakeParent)
        smartspaceView = view as SmartspaceView

        controller.stateChangeListener.onViewAttachedToWindow(view)

        verify(smartspaceView).registerDataProvider(plugin)
        verify(smartspaceSession)
                .addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor))
        sessionListener = sessionListenerCaptor.value
@@ -450,11 +441,11 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        verify(smartspaceSession).requestSmartspaceUpdate()
        clearInvocations(smartspaceSession)

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

        fakeParent.addView(fakeSmartspaceView)
        fakeParent.addView(view)
    }

    private fun setActiveUser(userHandle: UserHandle) {
@@ -490,7 +481,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
        ).thenReturn(if (value) 1 else 0)
    }

    private val fakeSmartspaceView = spy(object : View(context), SmartspaceView {
    private fun createSmartspaceView(): SmartspaceView {
        return spy(object : View(context), SmartspaceView {
            override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
            }

@@ -516,6 +508,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
            }
        })
    }
}

private const val PRIVATE_LOCKSCREEN_SETTING =
        Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS