Skip to content

Commit 91a73c5

Browse files
committed
Merge branch 'develop' into copilot/create-notification-channel
2 parents c8dcc53 + ec764dd commit 91a73c5

File tree

156 files changed

+2291
-663
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

156 files changed

+2291
-663
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
## [4.0.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.03)
2+
3+
#### TO BE RELEASED
4+
5+
## Added
6+
- #1871 action to modify any system settings
7+
8+
## [4.0.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.02)
9+
10+
#### 08 November 2025
11+
12+
## Added
13+
14+
- #1890 add button to save log to file and share it. The clipboard button now cuts off older entries and keeps newest ones.
15+
16+
## Fixed
17+
18+
- Only autostart PRO mode with Shizuku if Shizuku permission is granted. Otherwise fallback to method with Wireless Debugging and WRITE_SECURE_SETTINGS permission.
19+
- Starting system bridge for the first time would be janky because granting READ_LOGS kills the app process. Only grant for READ_LOGS when sharing logcat from settings.
20+
- #1886 mobile data actions work in PRO mode.
21+
122
## [4.0.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.01)
223

324
#### 01 November 2025

app/proguard-rules.pro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
-keep class io.github.sds100.keymapper.api.IKeyEventRelayService$Stub { *; }
7676
-keep class io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback { *; }
7777
-keep class io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback$Stub { *; }
78+
-keep class com.android.internal.telephony.ITelephony { *; }
79+
-keep class com.android.internal.telephony.ITelephony$Stub { *; }
7880

7981
-keepattributes *Annotation*, InnerClasses
8082
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations

app/version.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
VERSION_NAME=4.0.0-beta.2
2-
VERSION_CODE=186
1+
VERSION_NAME=4.0.0-beta.3
2+
VERSION_CODE=189
33
VERSION_NUM=01

base/src/main/assets/whats-new.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
✨ Screen-off remapping
2-
You can now remap buttons when the screen is off (including the power button) for free with PRO mode.
2+
You can now remap ALL buttons when the screen is off (including the power button) for free with PRO mode.
33

44
🎯 New Actions
55
• Run shell commands
66
• Send SMS messages
77
• Force stop current app or clear from recents
88
• Mute/unmute microphone
9+
• Modify any system setting
910

1011
🆕 New Features
1112
• Redesigned Settings screen

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import io.github.sds100.keymapper.data.repositories.LogRepository
2929
import io.github.sds100.keymapper.data.repositories.PreferenceRepositoryImpl
3030
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManagerImpl
3131
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
32+
import io.github.sds100.keymapper.sysbridge.manager.isConnected
3233
import io.github.sds100.keymapper.system.apps.AndroidPackageManagerAdapter
3334
import io.github.sds100.keymapper.system.devices.AndroidDevicesAdapter
3435
import io.github.sds100.keymapper.system.inputmethod.KeyEventRelayServiceWrapperImpl
@@ -224,6 +225,12 @@ abstract class BaseKeyMapperApp : MultiDexApplication() {
224225
autoGrantPermissionController.start()
225226
keyEventRelayServiceWrapper.bind()
226227

228+
if (systemBridgeConnectionManager.isConnected()) {
229+
Timber.i("KeyMapperApp: System bridge is connected")
230+
} else {
231+
Timber.i("KeyMapperApp: System bridge is disconnected")
232+
}
233+
227234
if (Build.VERSION.SDK_INT >= Constants.SYSTEM_BRIDGE_MIN_API) {
228235
systemBridgeAutoStarter.init()
229236

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import androidx.lifecycle.Lifecycle
2121
import androidx.lifecycle.flowWithLifecycle
2222
import androidx.lifecycle.lifecycleScope
2323
import androidx.lifecycle.withStateAtLeast
24-
import androidx.navigation.findNavController
2524
import com.anggrayudi.storage.extension.openInputStream
2625
import com.anggrayudi.storage.extension.openOutputStream
2726
import com.anggrayudi.storage.extension.toDocumentFile
@@ -32,6 +31,7 @@ import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase
3231
import io.github.sds100.keymapper.base.system.accessibility.AccessibilityServiceAdapterImpl
3332
import io.github.sds100.keymapper.base.system.permissions.RequestPermissionDelegate
3433
import io.github.sds100.keymapper.base.trigger.RecordTriggerControllerImpl
34+
import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
3535
import io.github.sds100.keymapper.base.utils.ui.ResourceProviderImpl
3636
import io.github.sds100.keymapper.common.BuildConfigProvider
3737
import io.github.sds100.keymapper.sysbridge.service.SystemBridgeSetupControllerImpl
@@ -105,6 +105,9 @@ abstract class BaseMainActivity : AppCompatActivity() {
105105
@Inject
106106
lateinit var inputEventHub: InputEventHubImpl
107107

108+
@Inject
109+
lateinit var navigationProvider: NavigationProvider
110+
108111
private lateinit var requestPermissionDelegate: RequestPermissionDelegate
109112

110113
private val currentNightMode: Int
@@ -162,15 +165,14 @@ abstract class BaseMainActivity : AppCompatActivity() {
162165
notificationReceiverAdapter = notificationReceiverAdapter,
163166
buildConfigProvider = buildConfigProvider,
164167
shizukuAdapter = shizukuAdapter,
168+
navigationProvider = navigationProvider,
169+
coroutineScope = lifecycleScope,
165170
)
166171

167172
permissionAdapter.request
168173
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
169174
.onEach { permission ->
170-
requestPermissionDelegate.requestPermission(
171-
permission,
172-
findNavController(R.id.container),
173-
)
175+
requestPermissionDelegate.requestPermission(permission)
174176
}
175177
.launchIn(lifecycleScope)
176178

@@ -201,9 +203,10 @@ abstract class BaseMainActivity : AppCompatActivity() {
201203
// the activities have not necessarily resumed at that point.
202204
permissionAdapter.onPermissionsChanged()
203205
serviceAdapter.invalidateState()
204-
suAdapter.invalidateIsRooted()
206+
suAdapter.requestPermission()
205207
systemBridgeSetupController.invalidateSettings()
206208
networkAdapter.invalidateState()
209+
onboardingUseCase.handledMigrateScreenOffKeyMapsNotification()
207210
}
208211

209212
override fun onDestroy() {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.navigation.compose.NavHost
1818
import androidx.navigation.compose.composable
1919
import io.github.sds100.keymapper.base.actions.ChooseActionScreen
2020
import io.github.sds100.keymapper.base.actions.ChooseActionViewModel
21+
import io.github.sds100.keymapper.base.actions.ChooseSettingScreen
2122
import io.github.sds100.keymapper.base.actions.ConfigCreateNotificationViewModel
2223
import io.github.sds100.keymapper.base.actions.ConfigShellCommandViewModel
2324
import io.github.sds100.keymapper.base.actions.CreateNotificationActionScreen
@@ -179,6 +180,13 @@ fun BaseMainNavHost(
179180
)
180181
}
181182

183+
composable<NavDestination.ChooseSetting> {
184+
ChooseSettingScreen(
185+
modifier = Modifier.fillMaxSize(),
186+
viewModel = hiltViewModel(),
187+
)
188+
}
189+
182190
composableDestinations()
183191
}
184192
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,4 +963,24 @@ sealed class ActionData : Comparable<ActionData> {
963963
data object ClearRecentApp : ActionData() {
964964
override val id: ActionId = ActionId.CLEAR_RECENT_APP
965965
}
966+
967+
@Serializable
968+
data class ModifySetting(
969+
val settingType: io.github.sds100.keymapper.system.settings.SettingType,
970+
val settingKey: String,
971+
val value: String,
972+
) : ActionData() {
973+
override val id: ActionId = ActionId.MODIFY_SETTING
974+
975+
override fun compareTo(other: ActionData) = when (other) {
976+
is ModifySetting -> compareValuesBy(
977+
this,
978+
other,
979+
{ it.settingType },
980+
{ it.settingKey },
981+
{ it.value },
982+
)
983+
else -> super.compareTo(other)
984+
}
985+
}
966986
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import io.github.sds100.keymapper.system.camera.CameraLens
2222
import io.github.sds100.keymapper.system.intents.IntentExtraModel
2323
import io.github.sds100.keymapper.system.intents.IntentTarget
2424
import io.github.sds100.keymapper.system.network.HttpMethod
25+
import io.github.sds100.keymapper.system.settings.SettingType
2526
import io.github.sds100.keymapper.system.volume.DndMode
2627
import io.github.sds100.keymapper.system.volume.RingerMode
2728
import io.github.sds100.keymapper.system.volume.VolumeStream
@@ -50,6 +51,7 @@ object ActionDataEntityMapper {
5051

5152
ActionEntity.Type.INTERACT_UI_ELEMENT -> ActionId.INTERACT_UI_ELEMENT
5253
ActionEntity.Type.SHELL_COMMAND -> ActionId.SHELL_COMMAND
54+
ActionEntity.Type.MODIFY_SETTING -> ActionId.MODIFY_SETTING
5355
ActionEntity.Type.CREATE_NOTIFICATION -> ActionId.CREATE_NOTIFICATION
5456
}
5557

@@ -740,6 +742,26 @@ object ActionDataEntityMapper {
740742

741743
ActionId.FORCE_STOP_APP -> ActionData.ForceStopApp
742744
ActionId.CLEAR_RECENT_APP -> ActionData.ClearRecentApp
745+
746+
ActionId.MODIFY_SETTING -> {
747+
val value = entity.extras.getData(ActionEntity.EXTRA_SETTING_VALUE)
748+
.valueOrNull() ?: return null
749+
750+
val settingTypeString = entity.extras.getData(ActionEntity.EXTRA_SETTING_TYPE)
751+
.valueOrNull() ?: "SYSTEM" // Default to SYSTEM for backward compatibility
752+
753+
val settingType = try {
754+
SettingType.valueOf(settingTypeString)
755+
} catch (_: IllegalArgumentException) {
756+
SettingType.SYSTEM
757+
}
758+
759+
ActionData.ModifySetting(
760+
settingType = settingType,
761+
settingKey = entity.data,
762+
value = value,
763+
)
764+
}
743765
}
744766
}
745767

@@ -766,6 +788,7 @@ object ActionDataEntityMapper {
766788
is ActionData.Sound -> ActionEntity.Type.SOUND
767789
is ActionData.InteractUiElement -> ActionEntity.Type.INTERACT_UI_ELEMENT
768790
is ActionData.ShellCommand -> ActionEntity.Type.SHELL_COMMAND
791+
is ActionData.ModifySetting -> ActionEntity.Type.MODIFY_SETTING
769792
is ActionData.CreateNotification -> ActionEntity.Type.CREATE_NOTIFICATION
770793
else -> ActionEntity.Type.SYSTEM_ACTION
771794
}
@@ -844,6 +867,7 @@ object ActionDataEntityMapper {
844867
is ActionData.ControlMedia.Rewind -> SYSTEM_ACTION_ID_MAP[data.id]!!
845868
is ActionData.ControlMedia.Stop -> SYSTEM_ACTION_ID_MAP[data.id]!!
846869
is ActionData.GoBack -> SYSTEM_ACTION_ID_MAP[data.id]!!
870+
is ActionData.ModifySetting -> data.settingKey
847871
else -> SYSTEM_ACTION_ID_MAP[data.id]!!
848872
}
849873

@@ -1124,6 +1148,11 @@ object ActionDataEntityMapper {
11241148
EntityExtra(ActionEntity.EXTRA_SHELL_COMMAND_TIMEOUT, data.timeoutMillis.toString()),
11251149
)
11261150

1151+
is ActionData.ModifySetting -> listOf(
1152+
EntityExtra(ActionEntity.EXTRA_SETTING_VALUE, data.value),
1153+
EntityExtra(ActionEntity.EXTRA_SETTING_TYPE, data.settingType.name),
1154+
)
1155+
11271156
is ActionData.CreateNotification -> buildList {
11281157
add(EntityExtra(ActionEntity.EXTRA_NOTIFICATION_TITLE, data.title))
11291158
data.timeoutMs?.let {
@@ -1305,5 +1334,7 @@ object ActionDataEntityMapper {
13051334
ActionId.HTTP_REQUEST to "http_request",
13061335
ActionId.FORCE_STOP_APP to "force_stop_app",
13071336
ActionId.CLEAR_RECENT_APP to "clear_recent_app",
1337+
1338+
ActionId.MODIFY_SETTING to "modify_setting",
13081339
)
13091340
}

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

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

3132
class LazyActionErrorSnapshot(
3233
private val packageManager: PackageManagerAdapter,
@@ -231,6 +232,27 @@ class LazyActionErrorSnapshot(
231232
}
232233
}
233234

235+
is ActionData.ModifySetting -> {
236+
return when (action.settingType) {
237+
SettingType.SYSTEM -> {
238+
if (!isPermissionGranted(Permission.WRITE_SETTINGS)) {
239+
SystemError.PermissionDenied(Permission.WRITE_SETTINGS)
240+
} else {
241+
null
242+
}
243+
}
244+
SettingType.SECURE,
245+
SettingType.GLOBAL,
246+
-> {
247+
if (!isPermissionGranted(Permission.WRITE_SECURE_SETTINGS)) {
248+
SystemError.PermissionDenied(Permission.WRITE_SECURE_SETTINGS)
249+
} else {
250+
null
251+
}
252+
}
253+
}
254+
}
255+
234256
else -> {}
235257
}
236258

0 commit comments

Comments
 (0)