Skip to content

Commit be3c305

Browse files
authored
Merge pull request #2058 from keymapperorg/develop
Version 4.0.5
2 parents 6d795b7 + c9c2d8d commit be3c305

File tree

14 files changed

+148
-88
lines changed

14 files changed

+148
-88
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
## [4.0.5](https://github.com/sds100/KeyMapper/releases/tag/v4.0.5)
2+
3+
#### 26 February 2026
4+
5+
## Fixed
6+
7+
- #2047 allow empty text in Text action.
8+
- #2056 replace old "PRO" in triggers on home screen with "Expert".
9+
- #2053 reduce latency when a lot of key maps with open app actions.
10+
- #2054 fix "Fix key event action" bottom sheet done button being hidden on small screens.
11+
112
## [4.0.4](https://github.com/sds100/KeyMapper/releases/tag/v4.0.4)
213

314
#### 21 February 2026

app/version.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
VERSION_NAME=4.0.4
2-
VERSION_CODE=246
1+
VERSION_NAME=4.0.5
2+
VERSION_CODE=247

base/src/main/AndroidManifest.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
77
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
8-
<uses-permission android:name="android.permission.READ_LOGS"/>
8+
<uses-permission android:name="android.permission.READ_LOGS" />
9+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
910

1011
<uses-permission
1112
android:name="android.permission.QUERY_ALL_PACKAGES"
@@ -117,5 +118,15 @@
117118
android:name="android.support.FILE_PROVIDER_PATHS"
118119
android:resource="@xml/provider_paths" />
119120
</provider>
121+
122+
<receiver
123+
android:name=".BootBroadcastReceiver"
124+
android:directBootAware="true"
125+
android:exported="false">
126+
<intent-filter>
127+
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
128+
<action android:name="android.intent.action.BOOT_COMPLETED" />
129+
</intent-filter>
130+
</receiver>
120131
</application>
121132
</manifest>

base/src/main/java/io/github/sds100/keymapper/base/BootBroadcastReceiver.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@ package io.github.sds100.keymapper.base
33
import android.content.BroadcastReceiver
44
import android.content.Context
55
import android.content.Intent
6+
import android.os.SystemClock
7+
import timber.log.Timber
68

79
class BootBroadcastReceiver : BroadcastReceiver() {
810
override fun onReceive(context: Context?, intent: Intent?) {
911
context ?: return
1012

11-
if (intent?.action == Intent.ACTION_LOCKED_BOOT_COMPLETED) {
12-
(context.applicationContext as? BaseKeyMapperApp)?.onBootUnlocked()
13+
when (intent?.action) {
14+
Intent.ACTION_BOOT_COMPLETED -> {
15+
Timber.i(
16+
"Boot completed broadcast: time since boot = ${SystemClock.elapsedRealtime() / 1000}",
17+
)
18+
}
19+
20+
Intent.ACTION_LOCKED_BOOT_COMPLETED -> {
21+
(context.applicationContext as? BaseKeyMapperApp)?.onBootUnlocked()
22+
}
1323
}
1424
}
1525
}

base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import io.github.sds100.keymapper.system.permissions.PermissionAdapter
2727
import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter
2828
import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter
2929
import io.github.sds100.keymapper.system.settings.SettingType
30+
import java.util.concurrent.ConcurrentHashMap
3031

3132
class LazyActionErrorSnapshot(
3233
private val packageManager: PackageManagerAdapter,
@@ -65,6 +66,9 @@ class LazyActionErrorSnapshot(
6566
}
6667
}
6768

69+
private val isAppEnabledCache = ConcurrentHashMap<String, Boolean>()
70+
private val isAppInstalledCache = ConcurrentHashMap<String, Boolean>()
71+
6872
private val isSystemBridgeConnected: Boolean by lazy {
6973
systemBridgeConnectionManager.isConnected()
7074
}
@@ -250,13 +254,30 @@ class LazyActionErrorSnapshot(
250254
}
251255

252256
private fun getAppError(packageName: String): KMError? {
257+
if (isAppEnabledCache.contains(packageName) && isAppInstalledCache.contains(packageName)) {
258+
if (isAppEnabledCache[packageName] == false) {
259+
return KMError.AppDisabled(packageName)
260+
}
261+
262+
if (isAppInstalledCache[packageName] == false) {
263+
return KMError.AppDisabled(packageName)
264+
}
265+
266+
return null
267+
}
268+
269+
val isAppInstalled = packageManager.isAppInstalled(packageName)
270+
isAppInstalledCache[packageName] = isAppInstalled
271+
253272
packageManager.isAppEnabled(packageName).onSuccess { isEnabled ->
273+
isAppEnabledCache[packageName] = isEnabled
274+
254275
if (!isEnabled) {
255276
return KMError.AppDisabled(packageName)
256277
}
257278
}
258279

259-
if (!packageManager.isAppInstalled(packageName)) {
280+
if (!isAppInstalled) {
260281
return KMError.AppNotFound(packageName)
261282
}
262283

base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ class CreateActionDelegate(
843843
"create_text_action",
844844
DialogModel.Text(
845845
hint = getString(R.string.hint_create_text_action),
846-
allowEmpty = false,
846+
allowEmpty = true,
847847
text = oldText,
848848
),
849849
) ?: return null

base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/FixKeyEventActionBottomSheet.kt

Lines changed: 73 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@ fun FixKeyEventActionBottomSheet(
7878
) {
7979
Column(
8080
modifier = Modifier
81+
.animateContentSize()
82+
.verticalScroll(rememberScrollState())
8183
.padding(16.dp)
8284
.fillMaxWidth(),
83-
verticalArrangement = Arrangement.spacedBy(16.dp),
85+
verticalArrangement = Arrangement.spacedBy(8.dp),
8486
) {
8587
Text(
8688
modifier = Modifier.align(Alignment.CenterHorizontally),
@@ -90,24 +92,68 @@ fun FixKeyEventActionBottomSheet(
9092
overflow = TextOverflow.Ellipsis,
9193
)
9294

93-
Column(
94-
modifier = Modifier
95-
.animateContentSize()
96-
.verticalScroll(rememberScrollState()),
97-
verticalArrangement = Arrangement.spacedBy(8.dp),
95+
Text(stringResource(R.string.fix_key_event_action_text))
96+
97+
FixKeyEventActionOptionCard(
98+
onClick = onSelectInputMethod,
99+
selected = state is FixKeyEventActionState.InputMethod,
100+
title = stringResource(R.string.fix_key_event_action_input_method_title),
101+
icon = Icons.Rounded.Keyboard,
98102
) {
99-
Text(stringResource(R.string.fix_key_event_action_text))
103+
val annotatedText = buildAnnotatedString {
104+
appendInlineContent("icon", "[icon]")
105+
append(" ")
106+
append(stringResource(R.string.fix_key_event_action_input_method_text))
107+
}
108+
val inlineContent = mapOf(
109+
Pair(
110+
"icon",
111+
InlineTextContent(
112+
Placeholder(
113+
width = MaterialTheme.typography.bodyLarge.fontSize,
114+
height = MaterialTheme.typography.bodyLarge.fontSize,
115+
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter,
116+
),
117+
) {
118+
Icon(
119+
imageVector = Icons.Rounded.Remove,
120+
contentDescription = null,
121+
tint = MaterialTheme.colorScheme.error,
122+
)
123+
},
124+
),
125+
)
126+
Text(
127+
annotatedText,
128+
inlineContent = inlineContent,
129+
style = MaterialTheme.typography.bodyMedium,
130+
)
131+
}
100132

101-
FixKeyEventActionOptionCard(
102-
onClick = onSelectInputMethod,
103-
selected = state is FixKeyEventActionState.InputMethod,
104-
title = stringResource(R.string.fix_key_event_action_input_method_title),
105-
icon = Icons.Rounded.Keyboard,
106-
) {
133+
val isExpertModeUnsupported = state.expertModeStatus == ExpertModeStatus.UNSUPPORTED
134+
135+
FixKeyEventActionOptionCard(
136+
onClick = onSelectExpertMode,
137+
selected = state is FixKeyEventActionState.ExpertMode,
138+
title = stringResource(R.string.expert_mode_app_bar_title),
139+
icon = Icons.Outlined.OfflineBolt,
140+
enabled = !isExpertModeUnsupported,
141+
) {
142+
if (isExpertModeUnsupported) {
143+
Text(
144+
stringResource(R.string.trigger_setup_expert_mode_unsupported),
145+
style = MaterialTheme.typography.bodyMedium,
146+
color = MaterialTheme.colorScheme.error,
147+
)
148+
} else {
107149
val annotatedText = buildAnnotatedString {
108150
appendInlineContent("icon", "[icon]")
109151
append(" ")
110-
append(stringResource(R.string.fix_key_event_action_input_method_text))
152+
append(stringResource(R.string.fix_key_event_action_expert_mode_text_1))
153+
appendLine()
154+
appendInlineContent("icon", "[icon]")
155+
append(" ")
156+
append(stringResource(R.string.fix_key_event_action_expert_mode_text_2))
111157
}
112158
val inlineContent = mapOf(
113159
Pair(
@@ -116,13 +162,14 @@ fun FixKeyEventActionBottomSheet(
116162
Placeholder(
117163
width = MaterialTheme.typography.bodyLarge.fontSize,
118164
height = MaterialTheme.typography.bodyLarge.fontSize,
119-
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter,
165+
placeholderVerticalAlign =
166+
PlaceholderVerticalAlign.TextCenter,
120167
),
121168
) {
122169
Icon(
123-
imageVector = Icons.Rounded.Remove,
170+
imageVector = Icons.Rounded.Add,
124171
contentDescription = null,
125-
tint = MaterialTheme.colorScheme.error,
172+
tint = LocalCustomColorsPalette.current.green,
126173
)
127174
},
128175
),
@@ -133,65 +180,13 @@ fun FixKeyEventActionBottomSheet(
133180
style = MaterialTheme.typography.bodyMedium,
134181
)
135182
}
136-
137-
val isExpertModeUnsupported = state.expertModeStatus == ExpertModeStatus.UNSUPPORTED
138-
139-
FixKeyEventActionOptionCard(
140-
onClick = onSelectExpertMode,
141-
selected = state is FixKeyEventActionState.ExpertMode,
142-
title = stringResource(R.string.expert_mode_app_bar_title),
143-
icon = Icons.Outlined.OfflineBolt,
144-
enabled = !isExpertModeUnsupported,
145-
) {
146-
if (isExpertModeUnsupported) {
147-
Text(
148-
stringResource(R.string.trigger_setup_expert_mode_unsupported),
149-
style = MaterialTheme.typography.bodyMedium,
150-
color = MaterialTheme.colorScheme.error,
151-
)
152-
} else {
153-
val annotatedText = buildAnnotatedString {
154-
appendInlineContent("icon", "[icon]")
155-
append(" ")
156-
append(stringResource(R.string.fix_key_event_action_expert_mode_text_1))
157-
appendLine()
158-
appendInlineContent("icon", "[icon]")
159-
append(" ")
160-
append(stringResource(R.string.fix_key_event_action_expert_mode_text_2))
161-
}
162-
val inlineContent = mapOf(
163-
Pair(
164-
"icon",
165-
InlineTextContent(
166-
Placeholder(
167-
width = MaterialTheme.typography.bodyLarge.fontSize,
168-
height = MaterialTheme.typography.bodyLarge.fontSize,
169-
placeholderVerticalAlign =
170-
PlaceholderVerticalAlign.TextCenter,
171-
),
172-
) {
173-
Icon(
174-
imageVector = Icons.Rounded.Add,
175-
contentDescription = null,
176-
tint = LocalCustomColorsPalette.current.green,
177-
)
178-
},
179-
),
180-
)
181-
Text(
182-
annotatedText,
183-
inlineContent = inlineContent,
184-
style = MaterialTheme.typography.bodyMedium,
185-
)
186-
}
187-
}
188-
189-
Text(
190-
stringResource(R.string.fix_key_event_action_change_in_settings_caption),
191-
style = MaterialTheme.typography.labelMedium,
192-
)
193183
}
194184

185+
Text(
186+
stringResource(R.string.fix_key_event_action_change_in_settings_caption),
187+
style = MaterialTheme.typography.labelMedium,
188+
)
189+
195190
HeaderText(text = stringResource(R.string.fix_key_event_action_setup_title))
196191

197192
AccessibilityServiceRequirementRow(
@@ -234,7 +229,10 @@ fun FixKeyEventActionBottomSheet(
234229
}
235230
}
236231

237-
Button(modifier = Modifier.align(Alignment.End), onClick = onDoneClick) {
232+
Button(
233+
modifier = Modifier.align(Alignment.End),
234+
onClick = onDoneClick,
235+
) {
238236
Text(stringResource(R.string.pos_done))
239237
}
240238
}

base/src/main/java/io/github/sds100/keymapper/base/detection/KeyMapAlgorithm.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,8 @@ class KeyMapAlgorithm(
802802
val detectedShortPressTriggers = mutableSetOf<Int>()
803803
val vibrateDurations = mutableListOf<Long>()
804804

805+
val errorSnapshot = performActionsUseCase.getErrorSnapshot()
806+
805807
/*
806808
loop through triggers in a different loop first to increment the last matched index.
807809
Otherwise the order of the key maps affects the logic.
@@ -815,8 +817,6 @@ class KeyMapAlgorithm(
815817

816818
val lastMatchedIndex = lastMatchedEventIndices[triggerIndex]
817819

818-
val errorSnapshot = performActionsUseCase.getErrorSnapshot()
819-
820820
val actionList = triggerActions[triggerIndex]
821821
.map { actionKey -> actionMap[actionKey]?.data }
822822
.filterNotNull()

base/src/main/java/io/github/sds100/keymapper/base/expertmode/SystemBridgeAutoStarter.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,17 @@ class SystemBridgeAutoStarter @Inject constructor(
110110

111111
useShizukuFlow.flatMapLatest { useShizuku ->
112112
if (useShizuku) {
113+
Timber.i("autoStartTypeFlow: Use shizuku")
113114
flowOf(AutoStartEligibility.Eligible(AutoStartType.SHIZUKU))
114115
} else if (buildConfig.sdkInt >= Build.VERSION_CODES.R) {
116+
Timber.i("autoStartTypeFlow: Do not use shizuku")
115117
combine(
116118
permissionAdapter.isGrantedFlow(Permission.WRITE_SECURE_SETTINGS),
117119
networkAdapter.isWifiConnected,
118120
) { isWriteSecureSettingsGranted, isWifiConnected ->
121+
Timber.i(
122+
"autoStartTypeFlow: Write secure settings: $isWriteSecureSettingsGranted, Wifi connected: $isWifiConnected",
123+
)
119124
when {
120125
!isWifiConnected -> {
121126
AutoStartEligibility.NotEligible.WiFiDisconnected

base/src/main/java/io/github/sds100/keymapper/base/home/KeyMapListItemCreator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ class KeyMapListItemCreator(
323323
append(key.getCodeLabel(this@KeyMapListItemCreator))
324324

325325
val parts = buildList {
326-
add("PRO")
326+
add("Expert")
327327
add(key.device.name)
328328

329329
if (!key.consumeEvent) {

0 commit comments

Comments
 (0)