Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepository.kt +21 −1 Original line number Diff line number Diff line Loading @@ -20,16 +20,24 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.hardware.input.InputManager import android.os.UserHandle import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD import com.android.systemui.CoreStartable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Inactive import com.android.systemui.shared.hardware.findInputDevice import com.android.systemui.statusbar.CommandQueue import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @SysUISingleton class ShortcutHelperRepository Loading @@ -37,6 +45,9 @@ class ShortcutHelperRepository constructor( private val commandQueue: CommandQueue, private val broadcastDispatcher: BroadcastDispatcher, private val inputManager: InputManager, @Background private val backgroundScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : CoreStartable { val state = MutableStateFlow<ShortcutHelperState>(Inactive) Loading @@ -44,7 +55,9 @@ constructor( override fun start() { registerBroadcastReceiver( action = Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS, onReceive = { state.value = Active() } onReceive = { backgroundScope.launch { state.value = Active(findPhysicalKeyboardId()) } } ) registerBroadcastReceiver( action = Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS, Loading Loading @@ -72,6 +85,13 @@ constructor( ) } private suspend fun findPhysicalKeyboardId() = withContext(backgroundDispatcher) { val firstEnabledPhysicalKeyboard = inputManager.findInputDevice { it.isEnabled && it.isFullKeyboard && !it.isVirtual } return@withContext firstEnabledPhysicalKeyboard?.id ?: VIRTUAL_KEYBOARD } fun hide() { state.value = Inactive } Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperState.kt +1 −1 Original line number Diff line number Diff line Loading @@ -19,5 +19,5 @@ package com.android.systemui.keyboard.shortcut.shared.model sealed interface ShortcutHelperState { data object Inactive : ShortcutHelperState data class Active(val deviceId: Int? = null) : ShortcutHelperState data class Active(val deviceId: Int) : ShortcutHelperState } packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepositoryTest.kt 0 → 100644 +102 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.keyboard.shortcut.data.repository import android.hardware.input.fakeInputManager import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState import com.android.systemui.keyboard.shortcut.shortcutHelperRepository import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ShortcutHelperRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val repo = kosmos.shortcutHelperRepository private val helper = kosmos.shortcutHelperTestHelper private val testScope = kosmos.testScope private val fakeInputManager = kosmos.fakeInputManager @Test fun state_activeThroughToggle_emitsActiveWithDeviceIdFromEvent() = testScope.runTest { val deviceId = 123 val state by collectLastValue(repo.state) helper.toggle(deviceId) assertThat(state).isEqualTo(ShortcutHelperState.Active(deviceId)) } @Test fun state_activeThroughActivity_noKeyboardActive_emitsActiveWithVirtualDeviceId() = testScope.runTest { val state by collectLastValue(repo.state) helper.showFromActivity() assertThat(state).isEqualTo(ShortcutHelperState.Active(VIRTUAL_KEYBOARD)) } @Test fun state_activeThroughActivity_virtualKeyboardActive_emitsActiveWithVirtualDeviceId() = testScope.runTest { val state by collectLastValue(repo.state) fakeInputManager.addVirtualKeyboard() helper.showFromActivity() assertThat(state).isEqualTo(ShortcutHelperState.Active(VIRTUAL_KEYBOARD)) } @Test fun state_activeThroughActivity_physicalKeyboardActive_emitsActiveWithDeviceId() = testScope.runTest { val deviceId = 456 val state by collectLastValue(repo.state) fakeInputManager.addPhysicalKeyboard(deviceId) helper.showFromActivity() assertThat(state).isEqualTo(ShortcutHelperState.Active(deviceId)) } @Test fun state_activeThroughActivity_physicalKeyboardDisabled_emitsActiveWithVirtualDeviceId() = testScope.runTest { val deviceId = 456 val state by collectLastValue(repo.state) fakeInputManager.addPhysicalKeyboard(deviceId) fakeInputManager.inputManager.disableInputDevice(deviceId) helper.showFromActivity() assertThat(state).isEqualTo(ShortcutHelperState.Active(VIRTUAL_KEYBOARD)) } } packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt 0 → 100644 +85 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 android.hardware.input import android.view.InputDevice import android.view.KeyCharacterMap import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import org.mockito.ArgumentMatchers.anyInt import org.mockito.invocation.InvocationOnMock class FakeInputManager { private val devices = mutableMapOf<Int, InputDevice>() val inputManager = mock<InputManager> { whenever(getInputDevice(anyInt())).thenAnswer { invocation -> val deviceId = invocation.arguments[0] as Int return@thenAnswer devices[deviceId] } whenever(inputDeviceIds).thenAnswer { return@thenAnswer devices.keys.toIntArray() } fun setDeviceEnabled(invocation: InvocationOnMock, enabled: Boolean) { val deviceId = invocation.arguments[0] as Int val device = devices[deviceId] ?: return devices[deviceId] = device.copy(enabled = enabled) } whenever(disableInputDevice(anyInt())).thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = false) } whenever(enableInputDevice(anyInt())).thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = true) } } fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) { check(id > 0) { "Physical keyboard ids have to be > 0" } addKeyboard(id, enabled) } fun addVirtualKeyboard(enabled: Boolean = true) { addKeyboard(id = KeyCharacterMap.VIRTUAL_KEYBOARD, enabled) } private fun addKeyboard(id: Int, enabled: Boolean = true) { devices[id] = InputDevice.Builder() .setId(id) .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC) .setSources(InputDevice.SOURCE_KEYBOARD) .setEnabled(enabled) .build() } private fun InputDevice.copy( id: Int = getId(), type: Int = keyboardType, sources: Int = getSources(), enabled: Boolean = isEnabled ) = InputDevice.Builder() .setId(id) .setKeyboardType(type) .setSources(sources) .setEnabled(enabled) .build() } packages/SystemUI/tests/utils/src/android/hardware/input/InputManagerKosmos.kt 0 → 100644 +21 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 android.hardware.input import com.android.systemui.kosmos.Kosmos val Kosmos.fakeInputManager by Kosmos.Fixture { FakeInputManager() } Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepository.kt +21 −1 Original line number Diff line number Diff line Loading @@ -20,16 +20,24 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.hardware.input.InputManager import android.os.UserHandle import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD import com.android.systemui.CoreStartable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Inactive import com.android.systemui.shared.hardware.findInputDevice import com.android.systemui.statusbar.CommandQueue import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @SysUISingleton class ShortcutHelperRepository Loading @@ -37,6 +45,9 @@ class ShortcutHelperRepository constructor( private val commandQueue: CommandQueue, private val broadcastDispatcher: BroadcastDispatcher, private val inputManager: InputManager, @Background private val backgroundScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : CoreStartable { val state = MutableStateFlow<ShortcutHelperState>(Inactive) Loading @@ -44,7 +55,9 @@ constructor( override fun start() { registerBroadcastReceiver( action = Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS, onReceive = { state.value = Active() } onReceive = { backgroundScope.launch { state.value = Active(findPhysicalKeyboardId()) } } ) registerBroadcastReceiver( action = Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS, Loading Loading @@ -72,6 +85,13 @@ constructor( ) } private suspend fun findPhysicalKeyboardId() = withContext(backgroundDispatcher) { val firstEnabledPhysicalKeyboard = inputManager.findInputDevice { it.isEnabled && it.isFullKeyboard && !it.isVirtual } return@withContext firstEnabledPhysicalKeyboard?.id ?: VIRTUAL_KEYBOARD } fun hide() { state.value = Inactive } Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperState.kt +1 −1 Original line number Diff line number Diff line Loading @@ -19,5 +19,5 @@ package com.android.systemui.keyboard.shortcut.shared.model sealed interface ShortcutHelperState { data object Inactive : ShortcutHelperState data class Active(val deviceId: Int? = null) : ShortcutHelperState data class Active(val deviceId: Int) : ShortcutHelperState }
packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepositoryTest.kt 0 → 100644 +102 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.keyboard.shortcut.data.repository import android.hardware.input.fakeInputManager import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState import com.android.systemui.keyboard.shortcut.shortcutHelperRepository import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ShortcutHelperRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val repo = kosmos.shortcutHelperRepository private val helper = kosmos.shortcutHelperTestHelper private val testScope = kosmos.testScope private val fakeInputManager = kosmos.fakeInputManager @Test fun state_activeThroughToggle_emitsActiveWithDeviceIdFromEvent() = testScope.runTest { val deviceId = 123 val state by collectLastValue(repo.state) helper.toggle(deviceId) assertThat(state).isEqualTo(ShortcutHelperState.Active(deviceId)) } @Test fun state_activeThroughActivity_noKeyboardActive_emitsActiveWithVirtualDeviceId() = testScope.runTest { val state by collectLastValue(repo.state) helper.showFromActivity() assertThat(state).isEqualTo(ShortcutHelperState.Active(VIRTUAL_KEYBOARD)) } @Test fun state_activeThroughActivity_virtualKeyboardActive_emitsActiveWithVirtualDeviceId() = testScope.runTest { val state by collectLastValue(repo.state) fakeInputManager.addVirtualKeyboard() helper.showFromActivity() assertThat(state).isEqualTo(ShortcutHelperState.Active(VIRTUAL_KEYBOARD)) } @Test fun state_activeThroughActivity_physicalKeyboardActive_emitsActiveWithDeviceId() = testScope.runTest { val deviceId = 456 val state by collectLastValue(repo.state) fakeInputManager.addPhysicalKeyboard(deviceId) helper.showFromActivity() assertThat(state).isEqualTo(ShortcutHelperState.Active(deviceId)) } @Test fun state_activeThroughActivity_physicalKeyboardDisabled_emitsActiveWithVirtualDeviceId() = testScope.runTest { val deviceId = 456 val state by collectLastValue(repo.state) fakeInputManager.addPhysicalKeyboard(deviceId) fakeInputManager.inputManager.disableInputDevice(deviceId) helper.showFromActivity() assertThat(state).isEqualTo(ShortcutHelperState.Active(VIRTUAL_KEYBOARD)) } }
packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt 0 → 100644 +85 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 android.hardware.input import android.view.InputDevice import android.view.KeyCharacterMap import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import org.mockito.ArgumentMatchers.anyInt import org.mockito.invocation.InvocationOnMock class FakeInputManager { private val devices = mutableMapOf<Int, InputDevice>() val inputManager = mock<InputManager> { whenever(getInputDevice(anyInt())).thenAnswer { invocation -> val deviceId = invocation.arguments[0] as Int return@thenAnswer devices[deviceId] } whenever(inputDeviceIds).thenAnswer { return@thenAnswer devices.keys.toIntArray() } fun setDeviceEnabled(invocation: InvocationOnMock, enabled: Boolean) { val deviceId = invocation.arguments[0] as Int val device = devices[deviceId] ?: return devices[deviceId] = device.copy(enabled = enabled) } whenever(disableInputDevice(anyInt())).thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = false) } whenever(enableInputDevice(anyInt())).thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = true) } } fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) { check(id > 0) { "Physical keyboard ids have to be > 0" } addKeyboard(id, enabled) } fun addVirtualKeyboard(enabled: Boolean = true) { addKeyboard(id = KeyCharacterMap.VIRTUAL_KEYBOARD, enabled) } private fun addKeyboard(id: Int, enabled: Boolean = true) { devices[id] = InputDevice.Builder() .setId(id) .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC) .setSources(InputDevice.SOURCE_KEYBOARD) .setEnabled(enabled) .build() } private fun InputDevice.copy( id: Int = getId(), type: Int = keyboardType, sources: Int = getSources(), enabled: Boolean = isEnabled ) = InputDevice.Builder() .setId(id) .setKeyboardType(type) .setSources(sources) .setEnabled(enabled) .build() }
packages/SystemUI/tests/utils/src/android/hardware/input/InputManagerKosmos.kt 0 → 100644 +21 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 android.hardware.input import com.android.systemui.kosmos.Kosmos val Kosmos.fakeInputManager by Kosmos.Fixture { FakeInputManager() }