Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
- #2045 add action to input on-screen keyboard enter/send button.
- #2106 disable the keyboard auto-switching setting when manually switching the keyboard in the Key Mapper homescreen menu.

## Fixed

- #2091 show an error on the "open device assistant" action when no device assistant is installed.

## [4.0.5](https://github.com/sds100/KeyMapper/releases/tag/v4.0.5)

#### 26 February 2026
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.github.sds100.keymapper.common.BuildConfigProvider
import io.github.sds100.keymapper.common.models.ShellExecutionMode
import io.github.sds100.keymapper.common.utils.KMError
import io.github.sds100.keymapper.common.utils.firstBlocking
import io.github.sds100.keymapper.common.utils.isSuccess
import io.github.sds100.keymapper.common.utils.onFailure
import io.github.sds100.keymapper.common.utils.onSuccess
import io.github.sds100.keymapper.common.utils.valueOrNull
Expand Down Expand Up @@ -53,6 +54,9 @@ class LazyActionErrorSnapshot(
private val isCompatibleImeEnabled by lazy { keyMapperImeHelper.isCompatibleImeEnabled() }
private val isCompatibleImeChosen by lazy { keyMapperImeHelper.isCompatibleImeChosen() }
private val isVoiceAssistantInstalled by lazy { packageManager.isVoiceAssistantInstalled() }
private val isDeviceAssistantInstalled by lazy {
packageManager.getDeviceAssistantPackage().isSuccess
}
private val grantedPermissions: MutableMap<Permission, Boolean> = mutableMapOf()
private val flashLenses by lazy {
buildSet {
Expand Down Expand Up @@ -190,6 +194,12 @@ class LazyActionErrorSnapshot(
}
}

is ActionData.DeviceAssistant -> {
if (!isDeviceAssistantInstalled) {
return KMError.NoDeviceAssistant
}
}

is ActionData.Flashlight ->
if (!flashLenses.contains(action.lens)) {
return when (action.lens) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import io.github.sds100.keymapper.base.repositories.FakePreferenceRepository
import io.github.sds100.keymapper.base.system.inputmethod.FakeInputMethodAdapter
import io.github.sds100.keymapper.base.utils.TestBuildConfigProvider
import io.github.sds100.keymapper.common.utils.KMError
import io.github.sds100.keymapper.common.utils.Success
import io.github.sds100.keymapper.data.Keys
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
import io.github.sds100.keymapper.sysbridge.utils.SystemBridgeError
import io.github.sds100.keymapper.system.apps.PackageManagerAdapter
import io.github.sds100.keymapper.system.inputmethod.ImeInfo
import io.github.sds100.keymapper.system.permissions.Permission
import io.github.sds100.keymapper.system.permissions.PermissionAdapter
Expand Down Expand Up @@ -59,16 +61,18 @@ class GetActionErrorUseCaseTest {
private lateinit var mockPermissionAdapter: PermissionAdapter
private lateinit var fakePreferenceRepository: FakePreferenceRepository
private lateinit var mockSystemBridgeConnectionManager: SystemBridgeConnectionManager
private lateinit var mockPackageManagerAdapter: PackageManagerAdapter

@Before
fun init() {
fakeInputMethodAdapter = FakeInputMethodAdapter()
mockPermissionAdapter = mock()
fakePreferenceRepository = FakePreferenceRepository()
mockSystemBridgeConnectionManager = mock()
mockPackageManagerAdapter = mock()

useCase = GetActionErrorUseCaseImpl(
packageManagerAdapter = mock(),
packageManagerAdapter = mockPackageManagerAdapter,
inputMethodAdapter = fakeInputMethodAdapter,
switchImeInterface = mock(),
permissionAdapter = mockPermissionAdapter,
Expand Down Expand Up @@ -300,4 +304,32 @@ class GetActionErrorUseCaseTest {

assertThat(errors[0], `is`(KMError.KeyEventActionError(SystemBridgeError.Disconnected)))
}

@Test
fun `show an error for device assistant action when no device assistant is installed`() =
testScope.runTest {
whenever(
mockPackageManagerAdapter.getDeviceAssistantPackage(),
).thenReturn(KMError.NoDeviceAssistant)

val action = ActionData.DeviceAssistant

val errors = useCase.actionErrorSnapshot.first().getErrors(listOf(action))

assertThat(errors[action], `is`(KMError.NoDeviceAssistant))
}

@Test
fun `do not show an error for device assistant action when a device assistant is installed`() =
testScope.runTest {
whenever(
mockPackageManagerAdapter.getDeviceAssistantPackage(),
).thenReturn(Success("com.google.android.googlequicksearchbox"))

val action = ActionData.DeviceAssistant

val errors = useCase.actionErrorSnapshot.first().getErrors(listOf(action))

assertThat(errors[action], nullValue())
}
}
Loading