Loading packages/SystemUI/multivalentTests/src/com/android/systemui/actioncorner/data/repository/ActionCornerRepositoryTest.kt +24 −8 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import com.android.systemui.cursorposition.data.model.CursorPosition import com.android.systemui.cursorposition.domain.data.repository.multiDisplayCursorPositionRepository import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository import com.android.systemui.display.shared.model.DisplayWindowProperties import com.android.systemui.inputdevice.data.repository.FakePointerDeviceRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.backgroundScope Loading @@ -59,10 +60,12 @@ import org.mockito.kotlin.whenever class ActionCornerRepositoryTest : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val Kosmos.fakePointerRepository by Fixture { FakePointerDeviceRepository() } private val Kosmos.underTest by Fixture { ActionCornerRepositoryImpl( cursorPositionRepository, kosmos.fakeDisplayWindowPropertiesRepository, kosmos.fakePointerRepository, kosmos.backgroundScope, ) } Loading @@ -75,12 +78,13 @@ class ActionCornerRepositoryTest : SysuiTestCase() { fun setup() { whenever(windowManager.currentWindowMetrics).thenReturn(metrics) displayRepository.insert(createDisplayWindowProperties()) kosmos.fakePointerRepository.setIsAnyPointerConnected(true) } @Test fun topLeftCursor_topLeftActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) cursorPositionRepository.addCursorPosition(display.topLeftCursorPos) assertThat(model) .isEqualTo( Loading @@ -94,7 +98,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun outOfBoundTopLeftCursor_noActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.topLeftCursorPos // Update x and y to make it just out of bound of action corner cursorPositionRepository.addCursorPosition( Loading @@ -110,7 +114,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun topRightCursor_topRightActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.topRightCursorPos cursorPositionRepository.addCursorPosition(actionCornerPos) assertThat(model) Loading @@ -122,7 +126,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun outOfBoundTopRightCursor_noActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.topRightCursorPos cursorPositionRepository.addCursorPosition( CursorPosition( Loading @@ -137,7 +141,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun bottomLeftCursor_bottomLeftActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.bottomLeftCursorPos cursorPositionRepository.addCursorPosition(actionCornerPos) assertThat(model) Loading @@ -149,7 +153,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun outOfBoundBottomLeftCursor_noActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.bottomLeftCursorPos cursorPositionRepository.addCursorPosition( CursorPosition( Loading @@ -164,7 +168,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun bottomRightCursor_bottomRightActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.bottomRightCursorPos cursorPositionRepository.addCursorPosition(actionCornerPos) assertThat(model) Loading @@ -176,7 +180,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun outOfBoundBottomRightCursor_noActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.bottomRightCursorPos cursorPositionRepository.addCursorPosition( CursorPosition( Loading Loading @@ -233,6 +237,18 @@ class ActionCornerRepositoryTest : SysuiTestCase() { assertThat(models.size).isEqualTo(1) } @Test fun activeActionCorner_pointerDeviceDisconnected_inactiveActionCorner() = kosmos.runTest { val actionCornerPos = display.bottomRightCursorPos cursorPositionRepository.addCursorPosition(actionCornerPos) fakePointerRepository.setIsAnyPointerConnected(false) val model by collectLastValue(underTest.actionCornerState) assertThat(model).isEqualTo(InactiveActionCorner) } private fun createDisplayWindowProperties() = DisplayWindowProperties( DEFAULT_DISPLAY, Loading packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/PointerDeviceRepositoryTest.kt 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.inputdevice.data.repository import android.hardware.input.InputManager.InputDeviceListener import android.hardware.input.fakeInputManager import android.os.fakeHandler import android.testing.TestableLooper import android.view.InputDevice import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mockito import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @SmallTest @TestableLooper.RunWithLooper @RunWith(ParameterizedAndroidJunit4::class) class PointerDeviceRepositoryTest(private val pointer: Int) : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() @Captor private lateinit var deviceListenerCaptor: ArgumentCaptor<InputDeviceListener> private val kosmos = testKosmos() private val fakeInputManager = kosmos.fakeInputManager private val dispatcher: CoroutineDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope private val inputDeviceRepo = InputDeviceRepository( kosmos.fakeHandler, testScope.backgroundScope, fakeInputManager.inputManager, ) private val underTest = PointerDeviceRepositoryImpl(dispatcher, fakeInputManager.inputManager, inputDeviceRepo) @Test fun emitsDisconnected_ifNothingIsConnected() = kosmos.runTest { val initialState = underTest.isAnyPointerDeviceConnected.first() assertThat(initialState).isFalse() } @Test fun emitsConnected_ifPointerAlreadyConnectedAtTheStart() = kosmos.runTest { fakeInputManager.addDevice(POINTER_ID, pointer) val initialValue = underTest.isAnyPointerDeviceConnected.first() assertThat(initialValue).isTrue() } @Test fun emitsConnected_whenNewPointerConnects() = kosmos.runTest { captureDeviceListener() val isPointerConnected by collectLastValue(underTest.isAnyPointerDeviceConnected) fakeInputManager.addDevice(POINTER_ID, pointer) assertThat(isPointerConnected).isTrue() } @Test fun emitsDisconnected_whenDeviceWithIdDoesNotExist() = testScope.runTest { captureDeviceListener() val isPointerConnected by collectLastValue(underTest.isAnyPointerDeviceConnected) whenever(fakeInputManager.inputManager.getInputDevice(eq(NULL_DEVICE_ID))) .thenReturn(null) fakeInputManager.addDevice(NULL_DEVICE_ID, InputDevice.SOURCE_UNKNOWN) assertThat(isPointerConnected).isFalse() } @Test fun emitsDisconnected_whenPointerDisconnects() = testScope.runTest { captureDeviceListener() val isPointerConnected by collectLastValue(underTest.isAnyPointerDeviceConnected) fakeInputManager.addDevice(POINTER_ID, pointer) assertThat(isPointerConnected).isTrue() fakeInputManager.removeDevice(POINTER_ID) assertThat(isPointerConnected).isFalse() } private suspend fun captureDeviceListener() { underTest.isAnyPointerDeviceConnected.first() Mockito.verify(fakeInputManager.inputManager) .registerInputDeviceListener(deviceListenerCaptor.capture(), anyOrNull()) fakeInputManager.registerInputDeviceListener(deviceListenerCaptor.value) } private companion object { private const val POINTER_ID = 1 private const val NULL_DEVICE_ID = 4 @JvmStatic @Parameters(name = "{0}") fun getParams(): List<Int> { return listOf(InputDevice.SOURCE_TOUCHPAD, InputDevice.SOURCE_MOUSE) } } } packages/SystemUI/src/com/android/systemui/actioncorner/data/repository/ActionCornerRepository.kt +14 −5 Original line number Diff line number Diff line Loading @@ -29,11 +29,13 @@ import com.android.systemui.cursorposition.data.model.CursorPosition import com.android.systemui.cursorposition.data.repository.MultiDisplayCursorPositionRepository import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository import com.android.systemui.inputdevice.data.repository.PointerDeviceRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn Loading @@ -53,14 +55,21 @@ class ActionCornerRepositoryImpl constructor( cursorRepository: MultiDisplayCursorPositionRepository, private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, pointerDeviceRepository: PointerDeviceRepository, @Background private val backgroundScope: CoroutineScope, ) : ActionCornerRepository { override val actionCornerState: StateFlow<ActionCornerState> = cursorRepository.cursorPositions .map(::mapToActionCornerState) // Avoid emitting duplicate values when cursor moves within the same corner .distinctUntilChanged() pointerDeviceRepository.isAnyPointerDeviceConnected .flatMapLatest { isConnected -> if (isConnected) { cursorRepository.cursorPositions.map(::mapToActionCornerState) } else { // When not connected, emit an InactiveActionCorner state and then complete this // inner flow. flowOf(InactiveActionCorner) } } .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), InactiveActionCorner) private fun mapToActionCornerState(cursorPos: CursorPosition?): ActionCornerState { Loading packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +3 −1 Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ import com.android.systemui.flags.FlagDependenciesModule; import com.android.systemui.flags.FlagsModule; import com.android.systemui.growth.dagger.GrowthModule; import com.android.systemui.haptics.msdl.dagger.MSDLModule; import com.android.systemui.inputdevice.InputDeviceModule; import com.android.systemui.inputmethod.InputMethodModule; import com.android.systemui.keyboard.KeyboardModule; import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; Loading Loading @@ -302,7 +303,8 @@ import javax.inject.Named; WalletModule.class, LowLightModule.class, LowLightClockModule.class, PerDisplayRepositoriesModule.class PerDisplayRepositoriesModule.class, InputDeviceModule.class, }, subcomponents = { ComplicationComponent.class, Loading packages/SystemUI/src/com/android/systemui/inputdevice/InputDeviceModule.kt 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.inputdevice import com.android.systemui.dagger.SysUISingleton import com.android.systemui.inputdevice.data.repository.PointerDeviceRepository import com.android.systemui.inputdevice.data.repository.PointerDeviceRepositoryImpl import dagger.Binds import dagger.Module @Module abstract class InputDeviceModule { @Binds @SysUISingleton abstract fun bindPointerDeviceRepository( repository: PointerDeviceRepositoryImpl ): PointerDeviceRepository } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/actioncorner/data/repository/ActionCornerRepositoryTest.kt +24 −8 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import com.android.systemui.cursorposition.data.model.CursorPosition import com.android.systemui.cursorposition.domain.data.repository.multiDisplayCursorPositionRepository import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository import com.android.systemui.display.shared.model.DisplayWindowProperties import com.android.systemui.inputdevice.data.repository.FakePointerDeviceRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.backgroundScope Loading @@ -59,10 +60,12 @@ import org.mockito.kotlin.whenever class ActionCornerRepositoryTest : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val Kosmos.fakePointerRepository by Fixture { FakePointerDeviceRepository() } private val Kosmos.underTest by Fixture { ActionCornerRepositoryImpl( cursorPositionRepository, kosmos.fakeDisplayWindowPropertiesRepository, kosmos.fakePointerRepository, kosmos.backgroundScope, ) } Loading @@ -75,12 +78,13 @@ class ActionCornerRepositoryTest : SysuiTestCase() { fun setup() { whenever(windowManager.currentWindowMetrics).thenReturn(metrics) displayRepository.insert(createDisplayWindowProperties()) kosmos.fakePointerRepository.setIsAnyPointerConnected(true) } @Test fun topLeftCursor_topLeftActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) cursorPositionRepository.addCursorPosition(display.topLeftCursorPos) assertThat(model) .isEqualTo( Loading @@ -94,7 +98,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun outOfBoundTopLeftCursor_noActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.topLeftCursorPos // Update x and y to make it just out of bound of action corner cursorPositionRepository.addCursorPosition( Loading @@ -110,7 +114,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun topRightCursor_topRightActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.topRightCursorPos cursorPositionRepository.addCursorPosition(actionCornerPos) assertThat(model) Loading @@ -122,7 +126,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun outOfBoundTopRightCursor_noActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.topRightCursorPos cursorPositionRepository.addCursorPosition( CursorPosition( Loading @@ -137,7 +141,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun bottomLeftCursor_bottomLeftActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.bottomLeftCursorPos cursorPositionRepository.addCursorPosition(actionCornerPos) assertThat(model) Loading @@ -149,7 +153,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun outOfBoundBottomLeftCursor_noActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.bottomLeftCursorPos cursorPositionRepository.addCursorPosition( CursorPosition( Loading @@ -164,7 +168,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun bottomRightCursor_bottomRightActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.bottomRightCursorPos cursorPositionRepository.addCursorPosition(actionCornerPos) assertThat(model) Loading @@ -176,7 +180,7 @@ class ActionCornerRepositoryTest : SysuiTestCase() { @Test fun outOfBoundBottomRightCursor_noActionCornerEmitted() = kosmos.runTest { val model by kosmos.collectLastValue(underTest.actionCornerState) val model by collectLastValue(underTest.actionCornerState) val actionCornerPos = display.bottomRightCursorPos cursorPositionRepository.addCursorPosition( CursorPosition( Loading Loading @@ -233,6 +237,18 @@ class ActionCornerRepositoryTest : SysuiTestCase() { assertThat(models.size).isEqualTo(1) } @Test fun activeActionCorner_pointerDeviceDisconnected_inactiveActionCorner() = kosmos.runTest { val actionCornerPos = display.bottomRightCursorPos cursorPositionRepository.addCursorPosition(actionCornerPos) fakePointerRepository.setIsAnyPointerConnected(false) val model by collectLastValue(underTest.actionCornerState) assertThat(model).isEqualTo(InactiveActionCorner) } private fun createDisplayWindowProperties() = DisplayWindowProperties( DEFAULT_DISPLAY, Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/PointerDeviceRepositoryTest.kt 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.inputdevice.data.repository import android.hardware.input.InputManager.InputDeviceListener import android.hardware.input.fakeInputManager import android.os.fakeHandler import android.testing.TestableLooper import android.view.InputDevice import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mockito import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @SmallTest @TestableLooper.RunWithLooper @RunWith(ParameterizedAndroidJunit4::class) class PointerDeviceRepositoryTest(private val pointer: Int) : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() @Captor private lateinit var deviceListenerCaptor: ArgumentCaptor<InputDeviceListener> private val kosmos = testKosmos() private val fakeInputManager = kosmos.fakeInputManager private val dispatcher: CoroutineDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope private val inputDeviceRepo = InputDeviceRepository( kosmos.fakeHandler, testScope.backgroundScope, fakeInputManager.inputManager, ) private val underTest = PointerDeviceRepositoryImpl(dispatcher, fakeInputManager.inputManager, inputDeviceRepo) @Test fun emitsDisconnected_ifNothingIsConnected() = kosmos.runTest { val initialState = underTest.isAnyPointerDeviceConnected.first() assertThat(initialState).isFalse() } @Test fun emitsConnected_ifPointerAlreadyConnectedAtTheStart() = kosmos.runTest { fakeInputManager.addDevice(POINTER_ID, pointer) val initialValue = underTest.isAnyPointerDeviceConnected.first() assertThat(initialValue).isTrue() } @Test fun emitsConnected_whenNewPointerConnects() = kosmos.runTest { captureDeviceListener() val isPointerConnected by collectLastValue(underTest.isAnyPointerDeviceConnected) fakeInputManager.addDevice(POINTER_ID, pointer) assertThat(isPointerConnected).isTrue() } @Test fun emitsDisconnected_whenDeviceWithIdDoesNotExist() = testScope.runTest { captureDeviceListener() val isPointerConnected by collectLastValue(underTest.isAnyPointerDeviceConnected) whenever(fakeInputManager.inputManager.getInputDevice(eq(NULL_DEVICE_ID))) .thenReturn(null) fakeInputManager.addDevice(NULL_DEVICE_ID, InputDevice.SOURCE_UNKNOWN) assertThat(isPointerConnected).isFalse() } @Test fun emitsDisconnected_whenPointerDisconnects() = testScope.runTest { captureDeviceListener() val isPointerConnected by collectLastValue(underTest.isAnyPointerDeviceConnected) fakeInputManager.addDevice(POINTER_ID, pointer) assertThat(isPointerConnected).isTrue() fakeInputManager.removeDevice(POINTER_ID) assertThat(isPointerConnected).isFalse() } private suspend fun captureDeviceListener() { underTest.isAnyPointerDeviceConnected.first() Mockito.verify(fakeInputManager.inputManager) .registerInputDeviceListener(deviceListenerCaptor.capture(), anyOrNull()) fakeInputManager.registerInputDeviceListener(deviceListenerCaptor.value) } private companion object { private const val POINTER_ID = 1 private const val NULL_DEVICE_ID = 4 @JvmStatic @Parameters(name = "{0}") fun getParams(): List<Int> { return listOf(InputDevice.SOURCE_TOUCHPAD, InputDevice.SOURCE_MOUSE) } } }
packages/SystemUI/src/com/android/systemui/actioncorner/data/repository/ActionCornerRepository.kt +14 −5 Original line number Diff line number Diff line Loading @@ -29,11 +29,13 @@ import com.android.systemui.cursorposition.data.model.CursorPosition import com.android.systemui.cursorposition.data.repository.MultiDisplayCursorPositionRepository import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository import com.android.systemui.inputdevice.data.repository.PointerDeviceRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn Loading @@ -53,14 +55,21 @@ class ActionCornerRepositoryImpl constructor( cursorRepository: MultiDisplayCursorPositionRepository, private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, pointerDeviceRepository: PointerDeviceRepository, @Background private val backgroundScope: CoroutineScope, ) : ActionCornerRepository { override val actionCornerState: StateFlow<ActionCornerState> = cursorRepository.cursorPositions .map(::mapToActionCornerState) // Avoid emitting duplicate values when cursor moves within the same corner .distinctUntilChanged() pointerDeviceRepository.isAnyPointerDeviceConnected .flatMapLatest { isConnected -> if (isConnected) { cursorRepository.cursorPositions.map(::mapToActionCornerState) } else { // When not connected, emit an InactiveActionCorner state and then complete this // inner flow. flowOf(InactiveActionCorner) } } .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), InactiveActionCorner) private fun mapToActionCornerState(cursorPos: CursorPosition?): ActionCornerState { Loading
packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +3 −1 Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ import com.android.systemui.flags.FlagDependenciesModule; import com.android.systemui.flags.FlagsModule; import com.android.systemui.growth.dagger.GrowthModule; import com.android.systemui.haptics.msdl.dagger.MSDLModule; import com.android.systemui.inputdevice.InputDeviceModule; import com.android.systemui.inputmethod.InputMethodModule; import com.android.systemui.keyboard.KeyboardModule; import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; Loading Loading @@ -302,7 +303,8 @@ import javax.inject.Named; WalletModule.class, LowLightModule.class, LowLightClockModule.class, PerDisplayRepositoriesModule.class PerDisplayRepositoriesModule.class, InputDeviceModule.class, }, subcomponents = { ComplicationComponent.class, Loading
packages/SystemUI/src/com/android/systemui/inputdevice/InputDeviceModule.kt 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.inputdevice import com.android.systemui.dagger.SysUISingleton import com.android.systemui.inputdevice.data.repository.PointerDeviceRepository import com.android.systemui.inputdevice.data.repository.PointerDeviceRepositoryImpl import dagger.Binds import dagger.Module @Module abstract class InputDeviceModule { @Binds @SysUISingleton abstract fun bindPointerDeviceRepository( repository: PointerDeviceRepositoryImpl ): PointerDeviceRepository }