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

Commit 7d5f42f9 authored by Daniel Akinola's avatar Daniel Akinola Committed by Android (Google) Code Review
Browse files

Merge "Show sticky keys indicator on all displays" into main

parents fd2c5bec 22886981
Loading
Loading
Loading
Loading
+49 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.stickykeys.ui

import android.app.Dialog
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,12 +25,11 @@ import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository
import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.keyboard.stickykeys.shared.model.Locked
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
import com.android.systemui.settings.displayTracker
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -38,6 +38,12 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -46,12 +52,17 @@ class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() {
    private lateinit var coordinator: StickyKeysIndicatorCoordinator
    private val testScope = TestScope(StandardTestDispatcher())
    private val stickyKeysRepository = FakeStickyKeysRepository()
    private val displayTracker = testKosmos().displayTracker
    private val dialog = mock<Dialog>()
    private val dialogExternalDisplay = mock<Dialog>()
    private val display = mock<Display> { on { displayId } doReturn (Display.DEFAULT_DISPLAY) }
    private val externalDisplay = mock<Display> { on { displayId } doReturn (1) }

    @Before
    fun setup() {
        val dialogFactory = mock<StickyKeyDialogFactory>()
        whenever(dialogFactory.create(any())).thenReturn(dialog)
        whenever(dialogFactory.create(eq(display), any())).thenReturn(dialog)
        whenever(dialogFactory.create(eq(externalDisplay), any())).thenReturn(dialogExternalDisplay)
        val keyboardRepository = testKosmos().keyboardRepository
        val viewModel =
            StickyKeysIndicatorViewModel(
@@ -65,27 +76,35 @@ class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() {
                dialogFactory,
                viewModel,
                mock<StickyKeysLogger>(),
                displayTracker,
            )
        coordinator.startListening()
        keyboardRepository.setIsAnyKeyboardConnected(true)
    }

    @Test
    fun dialogIsShownWhenStickyKeysAreEmitted() {
    fun dialogsAreShownWhenStickyKeysAreEmitted() {
        testScope.run {
            displayTracker.allDisplays = arrayOf(display, externalDisplay)

            verifyNoMoreInteractions(dialog)
            verifyNoMoreInteractions(dialogExternalDisplay)

            stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
            runCurrent()

            verify(dialog).show()
            verify(dialogExternalDisplay).show()
        }
    }

    @Test
    fun dialogDisappearsWhenStickyKeysAreEmpty() {
    fun dialogsDisappearWhenStickyKeysAreEmpty() {
        testScope.run {
            displayTracker.allDisplays = arrayOf(display, externalDisplay)

            verifyNoMoreInteractions(dialog)
            verifyNoMoreInteractions(dialogExternalDisplay)

            stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
            runCurrent()
@@ -93,6 +112,30 @@ class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() {
            runCurrent()

            verify(dialog).dismiss()
            verify(dialogExternalDisplay).dismiss()
        }
    }

    @Test
    fun dialogIsAddedToNewExternalDisplayWhenStickyKeysAreEmitted() {
        testScope.run {
            displayTracker.allDisplays = arrayOf(display)

            verifyNoMoreInteractions(dialog)
            verifyNoMoreInteractions(dialogExternalDisplay)

            stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
            runCurrent()

            verify(dialog).show()
            verify(dialogExternalDisplay, never()).show()

            displayTracker.allDisplays = arrayOf(display, externalDisplay)

            stickyKeysRepository.setStickyKeys(linkedMapOf(ALT to Locked(true)))
            runCurrent()

            verify(dialogExternalDisplay).show()
        }
    }
}
+17 −13
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.keyboard.stickykeys.ui

import android.app.Dialog
import android.content.Context
import android.view.Display
import android.view.Gravity
import android.view.Window
import android.view.WindowInsets
@@ -35,18 +36,21 @@ import com.android.systemui.res.R
import javax.inject.Inject

@SysUISingleton
class StickyKeyDialogFactory
@Inject
constructor(
    @Application val context: Context,
) {
class StickyKeyDialogFactory @Inject constructor(@Application val context: Context) {

    fun create(viewModel: StickyKeysIndicatorViewModel): Dialog {
        return createStickyKeyIndicator(viewModel)
    fun create(display: Display, viewModel: StickyKeysIndicatorViewModel): Dialog {
        return createStickyKeyIndicator(display, viewModel)
    }

    private fun createStickyKeyIndicator(viewModel: StickyKeysIndicatorViewModel): Dialog {
        return ComponentDialog(context, R.style.Theme_SystemUI_Dialog_StickyKeys).apply {
    private fun createStickyKeyIndicator(
        display: Display,
        viewModel: StickyKeysIndicatorViewModel,
    ): Dialog {
        return ComponentDialog(
                context = context.createDisplayContext(display),
                themeResId = R.style.Theme_SystemUI_Dialog_StickyKeys,
            )
            .apply {
                // because we're requesting window feature it must be called before setting content
                window?.setStickyKeyWindowAttributes()
                setContentView(createStickyKeyIndicatorView(context, viewModel))
+16 −7
Original line number Diff line number Diff line
@@ -17,13 +17,14 @@
package com.android.systemui.keyboard.stickykeys.ui

import android.app.Dialog
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
import com.android.systemui.settings.DisplayTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import com.android.app.tracing.coroutines.launchTraced as launch

@SysUISingleton
class StickyKeysIndicatorCoordinator
@@ -33,20 +34,28 @@ constructor(
    private val stickyKeyDialogFactory: StickyKeyDialogFactory,
    private val viewModel: StickyKeysIndicatorViewModel,
    private val stickyKeysLogger: StickyKeysLogger,
    private val displayTracker: DisplayTracker,
) {

    private var dialog: Dialog? = null
    private var dialogs = mutableMapOf<Int, Dialog?>()

    fun startListening() {
        applicationScope.launch {
            viewModel.indicatorContent.collect { stickyKeys ->
                stickyKeysLogger.logNewUiState(stickyKeys)
                if (stickyKeys.isEmpty()) {
                    dialog?.dismiss()
                    dialog = null
                } else if (dialog == null) {
                    dialog = stickyKeyDialogFactory.create(viewModel)
                    dialog?.show()
                    displayTracker.allDisplays.forEach {
                        dialogs[it.displayId]?.dismiss()
                        dialogs[it.displayId] = null
                    }
                } else {
                    displayTracker.allDisplays
                        .filter { dialogs[it.displayId] == null }
                        .forEach { display ->
                            val newDialog = stickyKeyDialogFactory.create(display, viewModel)
                            dialogs[display.displayId] = newDialog
                            newDialog.show()
                        }
                }
            }
        }