From 4bd54afadeb0924d236cd191e9b006037dc12c39 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 25 Jan 2026 17:55:24 +0100 Subject: [PATCH 01/15] chore: bump version to 4.0.1 --- app/version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/version.properties b/app/version.properties index c11699a2d3..671a5b78fa 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,2 +1,2 @@ -VERSION_NAME=4.0.0 -VERSION_CODE=238 +VERSION_NAME=4.0.1 +VERSION_CODE=239 From 984dd53e6488a78be03df9dbacc9c011b50c01d7 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 31 Jan 2026 12:38:19 +0100 Subject: [PATCH 02/15] log from Rust when system bridge is emergency killed --- evdev/src/main/rust/evdev_manager/jni/src/evdev_jni_observer.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evdev/src/main/rust/evdev_manager/jni/src/evdev_jni_observer.rs b/evdev/src/main/rust/evdev_manager/jni/src/evdev_jni_observer.rs index 5bf31d541f..738f115bc0 100644 --- a/evdev/src/main/rust/evdev_manager/jni/src/evdev_jni_observer.rs +++ b/evdev/src/main/rust/evdev_manager/jni/src/evdev_jni_observer.rs @@ -59,6 +59,8 @@ impl EvdevJniObserver { // Button up - check if held for 10+ seconds let down_time = *time_guard; if down_time > 0 && time_sec - down_time >= 10 { + // Must send log to Key Mapper for diagnostic purposes. + warn!("Emergency killing system bridge!"); // Call BaseSystemBridge.onEmergencyKillSystemBridge() via JNI if let Ok(mut env) = self.jvm.attach_current_thread() { let _ = env.call_method( From 6375e39ee939de1dce6b8744bb192f003ad0b118 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 12:54:43 +0100 Subject: [PATCH 03/15] update gradle.xml --- evdev/.idea/gradle.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/evdev/.idea/gradle.xml b/evdev/.idea/gradle.xml index e77e5a3224..cb0c8464b9 100644 --- a/evdev/.idea/gradle.xml +++ b/evdev/.idea/gradle.xml @@ -6,6 +6,20 @@ From 18fd6df3cff29a8b8bc7fbbff08fb3ea99ea5b26 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 13:14:09 +0100 Subject: [PATCH 04/15] #2007 fix: volume up/down action bottom sheet is cut off on some devices --- CHANGELOG.md | 7 +++ .../base/actions/VolumeActionBottomSheet.kt | 60 ++++++++++--------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93aff66fde..d3289a5715 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [4.0.1](https://github.com/sds100/KeyMapper/releases/tag/v4.0.1) + +#### TO BE RELEASED + +## Fixed +- #2007 Volume up/down action bottom sheet is cut off on some devices + ## [4.0.0](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0) #### 25 January 2026 diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt index 10b8b0ead8..b36066a3a8 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt @@ -105,7 +105,9 @@ private fun VolumeActionBottomSheet( dragHandle = null, ) { Column( - modifier = Modifier.verticalScroll(scrollState), + modifier = Modifier + .verticalScroll(scrollState) + .weight(1f), ) { Spacer(modifier = Modifier.height(16.dp)) @@ -162,45 +164,45 @@ private fun VolumeActionBottomSheet( isChecked = state.showVolumeUi, onCheckedChange = onToggleShowVolumeUi, ) - } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedButton( - modifier = Modifier.weight(1f), - onClick = { - scope.launch { - sheetState.hide() - onDismissRequest() - } - }, + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { - Text(stringResource(R.string.neg_cancel)) - } + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = { + scope.launch { + sheetState.hide() + onDismissRequest() + } + }, + ) { + Text(stringResource(R.string.neg_cancel)) + } - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(16.dp)) - Button( - modifier = Modifier.weight(1f), - onClick = onDoneClick, - ) { - Text(stringResource(R.string.pos_done)) + Button( + modifier = Modifier.weight(1f), + onClick = onDoneClick, + ) { + Text(stringResource(R.string.pos_done)) + } } - } - Spacer(Modifier.height(16.dp)) + Spacer(Modifier.height(16.dp)) + } } } @OptIn(ExperimentalMaterial3Api::class) -@Preview +@Preview(heightDp = 400) @Composable private fun PreviewVolumeActionBottomSheet() { KeyMapperTheme { From a11f6e5a15358bb7aa3831cb52c51f8b9d1a39f0 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 13:16:01 +0100 Subject: [PATCH 05/15] #2004 fix: do not crash when launching wireless debugging screen on some devices. --- CHANGELOG.md | 3 ++- .../sysbridge/service/SystemBridgeSetupController.kt | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3289a5715..f099af3900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ #### TO BE RELEASED ## Fixed -- #2007 Volume up/down action bottom sheet is cut off on some devices +- #2007 Volume up/down action bottom sheet is cut off on some devices. +- #2004 Do not crash when launching wireless debugging screen on some devices. ## [4.0.0](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0) diff --git a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt index a6ba627e9b..8aa7055c84 100644 --- a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt +++ b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt @@ -369,6 +369,10 @@ class SystemBridgeSetupControllerImpl @Inject constructor( try { ctx.startActivity(quickSettingsIntent) return true + } catch (_: SecurityException) { + highlightDeveloperOptionsWirelessDebuggingOption() + + return false } catch (_: ActivityNotFoundException) { highlightDeveloperOptionsWirelessDebuggingOption() From 92f81d46a17d9c80959702a4927e1bdc2ad23e82 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 13:34:07 +0100 Subject: [PATCH 06/15] #2006 bump compose-bom library to 2026-01-01. maybe this will fix the bug --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8caa284e77..7e7b6352ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ androidx-viewpager2 = "1.1.0" dagger-hilt-android = "2.56.2" hilt-navigation-compose = "1.3.0" -compose-bom = "2025.11.00" +compose-bom = "2026.01.01" compose-compiler = "1.5.10" # kotlinCompilerExtensionVersion desugar-jdk-libs = "2.1.5" From c54dc7c3da136b7ddc1aac5a1be42991cf4e09e3 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 13:41:05 +0100 Subject: [PATCH 07/15] bump material library to 1.13.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7e7b6352ac..784eb823c6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,7 +56,7 @@ ksp-gradle-plugin = "2.1.0-1.0.28" ktlint-gradle = "13.1.0" #leakcanary = "2.6" # Commented out in original file lingala-zip4j = "2.8.0" -material = "1.13.0-alpha13" +material = "1.13.0" mflisar-dragselectrecyclerview = "0.3" mockito-android = "4.6.1" From 790610f71158962cec62259da9007c335b67a7a9 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 13:45:00 +0100 Subject: [PATCH 08/15] #2005 fix: NPE in onSaveInstanceState due to null navhostcontroller --- CHANGELOG.md | 1 + .../java/io/github/sds100/keymapper/MainFragment.kt | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f099af3900..71cf636c3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Fixed - #2007 Volume up/down action bottom sheet is cut off on some devices. - #2004 Do not crash when launching wireless debugging screen on some devices. +- #2005 NPE in onSaveInstanceState ## [4.0.0](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0) diff --git a/app/src/main/java/io/github/sds100/keymapper/MainFragment.kt b/app/src/main/java/io/github/sds100/keymapper/MainFragment.kt index 0a1fec850b..95b18b79c0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/MainFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/MainFragment.kt @@ -60,7 +60,7 @@ class MainFragment : Fragment() { private lateinit var composeView: ComposeView - private lateinit var navController: NavHostController + private var navController: NavHostController? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -98,7 +98,7 @@ class MainFragment : Fragment() { // is destroyed setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - SetupNavigation(navigationProvider, navController) + SetupNavigation(navigationProvider, navController!!) KeyMapperTheme { BaseMainNavHost( @@ -111,10 +111,10 @@ class MainFragment : Fragment() { ), ), ), - navController = navController, + navController = navController!!, setupAccessibilityServiceDelegate = setupAccessibilityServiceDelegate, composableDestinations = { - composableDestinations(navController) + composableDestinations(navController!!) }, ) } @@ -123,7 +123,7 @@ class MainFragment : Fragment() { } override fun onSaveInstanceState(outState: Bundle) { - navController.saveState()?.let(outState::putAll) + navController?.saveState()?.let(outState::putAll) super.onSaveInstanceState(outState) } @@ -131,7 +131,7 @@ class MainFragment : Fragment() { override fun onDestroyView() { // onSaveInstanceState is only called when the activity's onSaveInstanceState method // is called so use our own place to save the navigation state - navigationProvider.savedState = navController.saveState() + navigationProvider.savedState = navController?.saveState() super.onDestroyView() } From d6ecb73e1cb85e872e87763ae0e7332d4e669e70 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 13:47:30 +0100 Subject: [PATCH 09/15] #2000 tell the user to tap OS version or build number --- CHANGELOG.md | 3 ++- base/src/main/res/values/strings.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71cf636c3e..c71a436725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ ## Fixed - #2007 Volume up/down action bottom sheet is cut off on some devices. - #2004 Do not crash when launching wireless debugging screen on some devices. -- #2005 NPE in onSaveInstanceState +- #2005 NPE in onSaveInstanceState. +- #2000 tell the user to tap OS version or build number ## [4.0.0](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0) diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 0a2ddf7718..365adef8c5 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1754,7 +1754,7 @@ Finish Enable developer options - Tap build number repeatedly + Tap build number or OS version repeatedly Pairing automatically Searching for pairing code and port… From 7452a5ec86517cdcf21695b6886271fcd99bd833 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 13:51:42 +0100 Subject: [PATCH 10/15] fix: do not center text in docked search bar after upgrading material library --- .../keymapper/base/utils/ui/compose/SearchAppBarActions.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/SearchAppBarActions.kt b/base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/SearchAppBarActions.kt index 02176b229e..d8e9c4d04c 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/SearchAppBarActions.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/SearchAppBarActions.kt @@ -46,10 +46,9 @@ fun RowScope.SearchAppBarActions( } DockedSearchBar( - modifier = Modifier.Companion.align(Alignment.Companion.CenterVertically), + modifier = Modifier.align(Alignment.CenterVertically), inputField = { SearchBarDefaults.InputField( - modifier = Modifier.Companion.align(Alignment.Companion.CenterVertically), onSearch = { onQueryChange(it) isExpanded = false From 9cc046ccb8c230a0539ddd3a411031890b26436c Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 13:52:53 +0100 Subject: [PATCH 11/15] fix: do not expand volume action bottom sheet to fill screen after fixing it on small screens --- .../sds100/keymapper/base/actions/VolumeActionBottomSheet.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt index b36066a3a8..959951b03c 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt @@ -105,9 +105,7 @@ private fun VolumeActionBottomSheet( dragHandle = null, ) { Column( - modifier = Modifier - .verticalScroll(scrollState) - .weight(1f), + modifier = Modifier.verticalScroll(scrollState), ) { Spacer(modifier = Modifier.height(16.dp)) From e1970e3d367811bbae68f6349b1d3ef6b59d8c93 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 14:08:46 +0100 Subject: [PATCH 12/15] #1996 fix: check if Night Shift is supported on a device before activating to prevent the screen going black when activated --- CHANGELOG.md | 3 ++- .../base/actions/IsActionSupportedUseCase.kt | 13 +++++++++++++ .../sds100/keymapper/base/utils/ErrorUtils.kt | 3 +++ base/src/main/res/values/strings.xml | 1 + .../sds100/keymapper/common/utils/KMResult.kt | 1 + 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c71a436725..3bcea6d4a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ - #2007 Volume up/down action bottom sheet is cut off on some devices. - #2004 Do not crash when launching wireless debugging screen on some devices. - #2005 NPE in onSaveInstanceState. -- #2000 tell the user to tap OS version or build number +- #2000 tell the user to tap OS version or build number. +- #1996 Check if Night Shift is supported on a device before activating to prevent the screen going black when activated. ## [4.0.0](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/IsActionSupportedUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/IsActionSupportedUseCase.kt index bb2b26ea5c..cea7c9a0af 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/IsActionSupportedUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/IsActionSupportedUseCase.kt @@ -1,6 +1,7 @@ package io.github.sds100.keymapper.base.actions import android.content.pm.PackageManager +import android.content.res.Resources import android.os.Build import io.github.sds100.keymapper.common.utils.KMError import io.github.sds100.keymapper.system.SystemError @@ -56,6 +57,18 @@ class IsActionSupportedUseCaseImpl( } } + if (id == ActionId.TOGGLE_NIGHT_SHIFT || + id == ActionId.ENABLE_NIGHT_SHIFT || + id == ActionId.DISABLE_NIGHT_SHIFT + ) { + // See https://cs.android.com/android/platform/superproject/+/android-latest-release:frameworks/base/core/java/android/hardware/display/ColorDisplayManager.java;l=498;drc=787314ed22d859e510163327dd6c58b215c2f7f9 + val res = Resources.getSystem() + val resId = res.getIdentifier("config_nightDisplayAvailable", "bool", "android") + if (resId == 0 || !res.getBoolean(resId)) { + return KMError.NightDisplayNotSupported + } + } + if (ActionUtils.getRequiredPermissions(id).contains(Permission.ROOT) && !permissionAdapter.isGranted(Permission.ROOT) ) { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/utils/ErrorUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/utils/ErrorUtils.kt index a791092b9a..06cf713a1f 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/utils/ErrorUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/utils/ErrorUtils.kt @@ -362,6 +362,9 @@ fun KMError.getFullMessage(resourceProvider: ResourceProvider): String { R.string.error_variable_flashlight_strength_unsupported, ) + KMError.NightDisplayNotSupported -> + resourceProvider.getString(R.string.error_night_display_not_supported) + is KMError.FailedToModifySystemSetting -> resourceProvider.getString( R.string.error_failed_to_modify_system_setting, diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index 365adef8c5..310a1c2da6 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -817,6 +817,7 @@ No front flash No back flash Variable flashlight strength unsupported + Your device doesn\'t support night display. Accessibility service needs to be enabled! The accessibility service needs to be restarted! diff --git a/common/src/main/java/io/github/sds100/keymapper/common/utils/KMResult.kt b/common/src/main/java/io/github/sds100/keymapper/common/utils/KMResult.kt index 0e9f9f9f52..bce1fbb41a 100644 --- a/common/src/main/java/io/github/sds100/keymapper/common/utils/KMResult.kt +++ b/common/src/main/java/io/github/sds100/keymapper/common/utils/KMResult.kt @@ -53,6 +53,7 @@ abstract class KMError : KMResult() { data object MaxCamerasInUse : KMError() data object CameraError : KMError() data object CameraVariableFlashlightStrengthUnsupported : KMError() + data object NightDisplayNotSupported : KMError() data class FailedToModifySystemSetting(val setting: String) : KMError() data object SwitchImeFailed : KMError() From 12ea212930a1893bba47cf992e436fa611e3204d Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 14:33:29 +0100 Subject: [PATCH 13/15] #1999 Use a more reliable method to check whether Shell has GRANT_RUNTIME_PERMISSIONS permission --- CHANGELOG.md | 1 + .../base/expertmode/ExpertModeScreen.kt | 8 +- .../base/expertmode/ExpertModeViewModel.kt | 8 +- .../expertmode/SystemBridgeSetupUseCase.kt | 11 +-- .../service/SystemBridgeSetupController.kt | 89 ++++--------------- 5 files changed, 29 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bcea6d4a3..8f50feaa20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - #2005 NPE in onSaveInstanceState. - #2000 tell the user to tap OS version or build number. - #1996 Check if Night Shift is supported on a device before activating to prevent the screen going black when activated. +- #1999 Use a more reliable method to check whether Shell has GRANT_RUNTIME_PERMISSIONS permission. ## [4.0.0](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0) diff --git a/base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeScreen.kt index 8d75d6dbc0..5bd52a8319 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeScreen.kt @@ -332,7 +332,7 @@ private fun LoadedContent( // Only show auto-start options and warnings when Expert Mode is started // Show USB debugging security settings warning if disabled - if (state.isAdbInputSecurityEnabled == false) { + if (state.showXiaomiAdbInputSecurityWarning) { UsbDebuggingSecuritySettingsCard( modifier = Modifier .fillMaxWidth() @@ -970,7 +970,7 @@ private fun PreviewDark() { isDefaultUsbModeCompatible = true, autoStartBootChecked = true, autoStartBootEnabled = true, - isAdbInputSecurityEnabled = null, + showXiaomiAdbInputSecurityWarning = false, ), ), showInfoCard = false, @@ -1013,7 +1013,7 @@ private fun PreviewStarted() { isDefaultUsbModeCompatible = false, autoStartBootChecked = false, autoStartBootEnabled = true, - isAdbInputSecurityEnabled = null, + showXiaomiAdbInputSecurityWarning = false, ), ), showInfoCard = false, @@ -1062,7 +1062,7 @@ private fun PreviewUsbDebuggingSecuritySettingsCard() { isDefaultUsbModeCompatible = true, autoStartBootChecked = false, autoStartBootEnabled = true, - isAdbInputSecurityEnabled = false, + showXiaomiAdbInputSecurityWarning = false, ), ), showInfoCard = false, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeViewModel.kt index 04680bc924..d9ce09e193 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/expertmode/ExpertModeViewModel.kt @@ -198,15 +198,15 @@ class ExpertModeViewModel @Inject constructor( private fun startedStateFlow(): Flow = combine( useCase.isAutoStartBootEnabled, useCase.isAutoStartBootAllowed, - useCase.isAdbInputSecurityEnabled, - ) { autoStartBootChecked, autoStartBootEnabled, isAdbInputSecurityEnabled -> + useCase.shellHasGrantRuntimePermissions, + ) { autoStartBootChecked, autoStartBootEnabled, shellHasGrantRuntimePermissions -> ExpertModeState.Started( isDefaultUsbModeCompatible = useCase.isCompatibleUsbModeSelected().valueOrNull() ?: false, autoStartBootChecked = autoStartBootChecked, autoStartBootEnabled = autoStartBootEnabled, - isAdbInputSecurityEnabled = isAdbInputSecurityEnabled, + showXiaomiAdbInputSecurityWarning = !shellHasGrantRuntimePermissions, ) } } @@ -230,7 +230,7 @@ sealed class ExpertModeState { val isDefaultUsbModeCompatible: Boolean, val autoStartBootChecked: Boolean, val autoStartBootEnabled: Boolean, - val isAdbInputSecurityEnabled: Boolean?, + val showXiaomiAdbInputSecurityWarning: Boolean, ) : ExpertModeState() } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/expertmode/SystemBridgeSetupUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/expertmode/SystemBridgeSetupUseCase.kt index a35ca5f30d..7caf46f401 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/expertmode/SystemBridgeSetupUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/expertmode/SystemBridgeSetupUseCase.kt @@ -186,13 +186,8 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor( systemBridgeSetupController.launchDeveloperOptions() } - @RequiresApi(Build.VERSION_CODES.R) - override val isAdbInputSecurityEnabled: Flow = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - systemBridgeSetupController.isAdbInputSecurityEnabled - } else { - flowOf(null) - } + override val shellHasGrantRuntimePermissions: Flow = + systemBridgeSetupController.shellHasGrantRuntimePermissions override fun connectWifiNetwork() { networkAdapter.connectWifiNetwork() @@ -355,7 +350,7 @@ interface SystemBridgeSetupUseCase { fun startSystemBridgeWithAdb() fun autoStartSystemBridgeWithAdb() - val isAdbInputSecurityEnabled: Flow + val shellHasGrantRuntimePermissions: Flow fun isCompatibleUsbModeSelected(): KMResult diff --git a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt index 8aa7055c84..545e46f2a6 100644 --- a/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt +++ b/sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt @@ -19,15 +19,12 @@ import io.github.sds100.keymapper.common.KeyMapperClassProvider import io.github.sds100.keymapper.common.utils.Constants import io.github.sds100.keymapper.common.utils.KMResult import io.github.sds100.keymapper.common.utils.SettingsUtils -import io.github.sds100.keymapper.common.utils.Success import io.github.sds100.keymapper.common.utils.isSuccess import io.github.sds100.keymapper.common.utils.onSuccess import io.github.sds100.keymapper.sysbridge.adb.AdbManager import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState -import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState.Connected import io.github.sds100.keymapper.sysbridge.manager.awaitConnected -import io.github.sds100.keymapper.sysbridge.manager.isConnected import javax.inject.Inject import javax.inject.Singleton import kotlinx.coroutines.CoroutineScope @@ -81,8 +78,8 @@ class SystemBridgeSetupControllerImpl @Inject constructor( private val isAdbPairedResult: MutableStateFlow = MutableStateFlow(null) private var isAdbPairedJob: Job? = null - override val isAdbInputSecurityEnabled: MutableStateFlow = MutableStateFlow(null) - private var checkAdbInputSecurityJob: Job? = null + override val shellHasGrantRuntimePermissions: MutableStateFlow = + MutableStateFlow(getShellHasGrantRuntimePermissions()) init { // Automatically go back to the Key Mapper app when turning on wireless debugging @@ -94,7 +91,6 @@ class SystemBridgeSetupControllerImpl @Inject constructor( // Do not automatically go back to Key Mapper after this step because // some devices show a dialog that will be auto dismissed resulting in wireless // ADB being immediately disabled. E.g OnePlus 6T Oxygen OS 11 - // Note: ADB input security check is handled by monitoring isWirelessDebuggingEnabled flow } } @@ -110,27 +106,6 @@ class SystemBridgeSetupControllerImpl @Inject constructor( } } } - - // Automatically check ADB input security when SystemBridge is connected - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - coroutineScope.launch { - // Check when SystemBridge becomes connected - connectionManager.connectionState.collect { connectionState -> - when (connectionState) { - is Connected -> { - // Delay a bit to ensure SystemBridge is ready - kotlinx.coroutines.delay(1000L) - checkAdbInputSecurityEnabled() - } - - is SystemBridgeConnectionState.Disconnected -> { - // Reset to null when SystemBridge is disconnected - isAdbInputSecurityEnabled.value = null - } - } - } - } - } } override fun startWithRoot() { @@ -396,51 +371,10 @@ class SystemBridgeSetupControllerImpl @Inject constructor( ) } - private fun checkAdbInputSecurityEnabled() { - if (!connectionManager.isConnected()) { - isAdbInputSecurityEnabled.value = null - return - } - - // Only run one check at a time - if (checkAdbInputSecurityJob == null || checkAdbInputSecurityJob?.isCompleted == true) { - checkAdbInputSecurityJob?.cancel() - - checkAdbInputSecurityJob = coroutineScope.launch { - try { - val result = connectionManager.run { systemBridge -> - systemBridge.executeCommand("getprop persist.security.adbinput", 5000L) - } - - val isEnabled = when (result) { - is Success -> { - val stdout = result.value.stdout.trim() - - when (stdout) { - "1" -> true - - "0" -> false - - // If it is empty or anything else then set the value to null - // because what we are expecting does not exist. - else -> null - } - } - - else -> null - } - isAdbInputSecurityEnabled.value = isEnabled - } catch (_: Exception) { - // If check fails, set to null - isAdbInputSecurityEnabled.value = null - } - } - } - } - fun invalidateSettings() { isDeveloperOptionsEnabled.update { getDeveloperOptionsEnabled() } isWirelessDebuggingEnabled.update { getWirelessDebuggingEnabled() } + shellHasGrantRuntimePermissions.update { getShellHasGrantRuntimePermissions() } } private fun getDeveloperOptionsEnabled(): Boolean { @@ -459,6 +393,13 @@ class SystemBridgeSetupControllerImpl @Inject constructor( } } + private fun getShellHasGrantRuntimePermissions(): Boolean { + return ctx.packageManager.checkPermission( + "android.permission.GRANT_RUNTIME_PERMISSIONS", + "com.android.shell", + ) == PackageManager.PERMISSION_GRANTED + } + private fun getKeyMapperAppTask(): ActivityManager.AppTask? { val task = activityManager.appTasks ?.firstOrNull { @@ -506,10 +447,14 @@ interface SystemBridgeSetupController { fun autoStartWithAdb() /** - * If this value is null then the option does not exist or can not be checked - * because the system bridge is disconnected. + * See issue #1965. + * + * Whether the Shell package has GRANT_RUNTIME_PERMISSIONS permission. On Xiaomi devices, the + * user needs to enable "USB debugging security settings" in Developer Options for + * the Shell to have permission to grant runtime permissions, such as WRITE_SECURE_SETTINGS + * to Key Mapper. */ - val isAdbInputSecurityEnabled: StateFlow + val shellHasGrantRuntimePermissions: StateFlow fun launchDeveloperOptions() From c61af613fbbdcc7ac7b779ce9f79a1f85c998ae8 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 14:57:09 +0100 Subject: [PATCH 14/15] chore bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 671a5b78fa..a40000211d 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,2 +1,2 @@ VERSION_NAME=4.0.1 -VERSION_CODE=239 +VERSION_CODE=241 From ec6756f21117b8a375ab4dca04b2a4b338e1652c Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 1 Feb 2026 15:29:26 +0100 Subject: [PATCH 15/15] set release date in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f50feaa20..f41e8d4d67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [4.0.1](https://github.com/sds100/KeyMapper/releases/tag/v4.0.1) -#### TO BE RELEASED +#### 01 February 2026 ## Fixed - #2007 Volume up/down action bottom sheet is cut off on some devices.