diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ed837fe12..6adf831157 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+## [3.0 Beta 4](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0-beta.4)
+
+#### 2 April 2025
+
+_See the changes from previous 3.0 Beta releases as well._
+
+## Added
+
+- #1620 enable Key Mapper Basic Input Method without user interaction on Android 13+.
+- #1619 Automatically select the non key mapper keyboard when the device is locked and wanting to type.
+
+## Changed
+
+- *Finally* renamed the theme settings after many years. @jambl3r.
+
+## Bug fixes
+
+- #1618, #1532, #1590 The Key Mapper keyboard is no longer required for Text actions.
+- Flashlight action works again on devices that do not support variable brightness
+
## [3.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0-beta.3)
_See the changes from previous 3.0 Beta releases as well._
diff --git a/app/build.gradle b/app/build.gradle
index 117a9d541a..f148f0d864 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -165,7 +165,6 @@ dependencies {
def room_version = "2.6.1"
def coroutinesVersion = "1.9.0"
def nav_version = '2.8.9'
- def work_version = "2.10.0"
def epoxy_version = "4.6.2"
def splitties_version = "3.0.0"
def multidex_version = "2.0.1"
@@ -211,7 +210,6 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.7"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7"
implementation "androidx.room:room-ktx:$room_version"
- implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.multidex:multidex:$multidex_version"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8181ee9028..c581f61ca5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -79,6 +79,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
+ android:directBootAware="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme.NoActionBar"
@@ -174,6 +175,7 @@
android:exported="true">
+
@@ -231,8 +233,10 @@
android:resource="@xml/config_accessibility_service" />
+
diff --git a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt
index 92ddd731f7..b5b3275dc6 100644
--- a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt
@@ -1,8 +1,12 @@
package io.github.sds100.keymapper
+import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
+import android.os.UserManager
+import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.content.getSystemService
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
@@ -64,7 +68,10 @@ import java.util.Calendar
/**
* Created by sds100 on 19/05/2020.
*/
+@SuppressLint("LogNotTimber")
class KeyMapperApp : MultiDexApplication() {
+ private val tag = KeyMapperApp::class.simpleName
+
val appCoroutineScope = MainScope()
val notificationAdapter by lazy { AndroidNotificationAdapter(this, appCoroutineScope) }
@@ -167,9 +174,16 @@ class KeyMapperApp : MultiDexApplication() {
private val processLifecycleOwner by lazy { ProcessLifecycleOwner.get() }
+ private val userManager: UserManager? by lazy { getSystemService() }
+
+ private val initLock: Any = Any()
+ private var initialized = false
+
override fun onCreate() {
val priorExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
+ Log.i(tag, "KeyMapperApp: OnCreate")
+
Thread.setDefaultUncaughtExceptionHandler { thread, exception ->
// log in a blocking manner and always log regardless of whether the setting is turned on
val entry = LogEntryEntity(
@@ -188,9 +202,30 @@ class KeyMapperApp : MultiDexApplication() {
super.onCreate()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-// DynamicColors.applyToActivitiesIfAvailable(this)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && userManager?.isUserUnlocked == false) {
+ Log.i(tag, "KeyMapperApp: Delay init because locked.")
+ // If the device is still encrypted and locked do not initialize anything that
+ // may potentially need the encrypted app storage like databases.
+ return
+ }
+
+ synchronized(initLock) {
+ init()
+ initialized = true
}
+ }
+
+ fun onBootUnlocked() {
+ synchronized(initLock) {
+ if (!initialized) {
+ init()
+ }
+ initialized = true
+ }
+ }
+
+ private fun init() {
+ Log.i(tag, "KeyMapperApp: Init")
ServiceLocator.settingsRepository(this).get(Keys.darkTheme)
.map { it?.toIntOrNull() }
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt
index d77967ed77..8c3abdb624 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt
@@ -777,7 +777,8 @@ fun ActionData.canBeHeldDown(): Boolean = when (this) {
fun ActionData.canUseImeToPerform(): Boolean = when (this) {
is ActionData.InputKeyEvent -> !useShell
- is ActionData.Text -> true
+ // Android 13+ can use the accessibility service to input text.
+ is ActionData.Text -> Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
is ActionData.MoveCursorToEnd -> true
else -> false
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt
index d97e5a9df0..5313492065 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt
@@ -333,7 +333,11 @@ class PerformActionsUseCaseImpl(
}
is ActionData.Text -> {
- imeInputEventInjector.inputText(action.text)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ accessibilityService.inputText(action.text)
+ } else {
+ imeInputEventInjector.inputText(action.text)
+ }
result = Success(Unit)
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt
index 4d0a584670..7b9abecc16 100644
--- a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt
@@ -476,7 +476,8 @@ class BackupManagerImpl(
modifiedGroup = RepositoryUtils.saveUniqueName(
modifiedGroup,
saveBlock = { renamedGroup ->
- if (siblings.any { sibling -> sibling.name == renamedGroup.name }) {
+ // Do not rename the group with a (1) if it is the same UID. Just overwrite the name.
+ if (siblings.any { sibling -> sibling.uid != renamedGroup.uid && sibling.name == renamedGroup.name }) {
throw IllegalStateException("Non unique group name")
}
},
diff --git a/app/src/main/java/io/github/sds100/keymapper/groups/GroupRow.kt b/app/src/main/java/io/github/sds100/keymapper/groups/GroupRow.kt
index a9f112206a..49f36f5762 100644
--- a/app/src/main/java/io/github/sds100/keymapper/groups/GroupRow.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/groups/GroupRow.kt
@@ -51,6 +51,7 @@ import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
fun GroupRow(
modifier: Modifier = Modifier,
groups: List,
+ showNewGroup: Boolean = true,
onNewGroupClick: () -> Unit = {},
onGroupClick: (String) -> Unit = {},
enabled: Boolean = true,
@@ -80,19 +81,21 @@ fun GroupRow(
// Show new group button in the expand indicator if the new group button
// in the flow row has overflowed.
Row {
- NewGroupButton(
- onClick = onNewGroupClick,
- text = if (isSubgroups) {
- stringResource(R.string.home_new_subgroup_button)
- } else {
- stringResource(R.string.home_new_group_button)
- },
- icon = {
- Icon(imageVector = Icons.Rounded.Add, null)
- },
- showText = groups.isEmpty(),
- enabled = enabled,
- )
+ if (showNewGroup) {
+ NewGroupButton(
+ onClick = onNewGroupClick,
+ text = if (isSubgroups) {
+ stringResource(R.string.home_new_subgroup_button)
+ } else {
+ stringResource(R.string.home_new_group_button)
+ },
+ icon = {
+ Icon(imageVector = Icons.Rounded.Add, null)
+ },
+ showText = groups.isEmpty(),
+ enabled = enabled,
+ )
+ }
Spacer(Modifier.width(8.dp))
@@ -160,19 +163,21 @@ fun GroupRow(
)
}
- NewGroupButton(
- onClick = onNewGroupClick,
- text = if (isSubgroups) {
- stringResource(R.string.home_new_subgroup_button)
- } else {
- stringResource(R.string.home_new_group_button)
- },
- icon = {
- Icon(imageVector = Icons.Rounded.Add, null)
- },
- showText = groups.isEmpty(),
- enabled = enabled,
- )
+ if (showNewGroup) {
+ NewGroupButton(
+ onClick = onNewGroupClick,
+ text = if (isSubgroups) {
+ stringResource(R.string.home_new_subgroup_button)
+ } else {
+ stringResource(R.string.home_new_group_button)
+ },
+ icon = {
+ Icon(imageVector = Icons.Rounded.Add, null)
+ },
+ showText = groups.isEmpty(),
+ enabled = enabled,
+ )
+ }
}
}
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt
index 453c9caed9..0f955052e4 100644
--- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt
@@ -41,6 +41,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -176,7 +177,7 @@ fun HomeKeyMapListScreen(
)
},
appBarContent = {
- KeyMapAppBar(
+ KeyMapListAppBar(
state = state.appBarState,
scrollBehavior = scrollBehavior,
onSettingsClick = onSettingsClick,
@@ -499,7 +500,7 @@ private fun PreviewSelectingKeyMaps() {
)
},
appBarContent = {
- KeyMapAppBar(state = appBarState)
+ KeyMapListAppBar(state = appBarState)
},
selectionBottomSheet = {
SelectionBottomSheet(
@@ -542,7 +543,7 @@ private fun PreviewKeyMapsRunning() {
)
},
appBarContent = {
- KeyMapAppBar(state = appBarState)
+ KeyMapListAppBar(state = appBarState)
},
selectionBottomSheet = {},
)
@@ -578,7 +579,7 @@ private fun PreviewKeyMapsPaused() {
)
},
appBarContent = {
- KeyMapAppBar(state = appBarState)
+ KeyMapListAppBar(state = appBarState)
},
selectionBottomSheet = {},
)
@@ -633,7 +634,7 @@ private fun PreviewKeyMapsWarnings() {
)
},
appBarContent = {
- KeyMapAppBar(state = appBarState)
+ KeyMapListAppBar(state = appBarState)
},
selectionBottomSheet = {},
)
@@ -641,7 +642,7 @@ private fun PreviewKeyMapsWarnings() {
}
@OptIn(ExperimentalMaterial3Api::class)
-@Preview
+@Preview(device = Devices.PIXEL)
@Composable
private fun PreviewKeyMapsWarningsEmpty() {
val warnings = listOf(
@@ -680,7 +681,7 @@ private fun PreviewKeyMapsWarningsEmpty() {
)
},
appBarContent = {
- KeyMapAppBar(state = appBarState)
+ KeyMapListAppBar(state = appBarState)
},
selectionBottomSheet = {},
)
diff --git a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapAppBar.kt b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt
similarity index 97%
rename from app/src/main/java/io/github/sds100/keymapper/home/KeyMapAppBar.kt
rename to app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt
index d952ec3ae4..12555778e4 100644
--- a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapAppBar.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt
@@ -111,7 +111,7 @@ import kotlinx.coroutines.launch
@Composable
@OptIn(ExperimentalMaterial3Api::class)
-fun KeyMapAppBar(
+fun KeyMapListAppBar(
modifier: Modifier = Modifier,
state: KeyMapAppBarState,
onSettingsClick: () -> Unit = {},
@@ -146,10 +146,17 @@ fun KeyMapAppBar(
state = state,
scrollBehavior = scrollBehavior,
onTogglePausedClick = onTogglePausedClick,
- onSortClick = onSortClick,
onFixWarningClick = onFixWarningClick,
onNewGroupClick = onNewGroupClick,
onGroupClick = onGroupClick,
+ navigationIcon = {
+ IconButton(onClick = onSortClick) {
+ Icon(
+ Icons.AutoMirrored.Rounded.Sort,
+ contentDescription = stringResource(R.string.home_app_bar_sort),
+ )
+ }
+ },
actions = {
AppBarActions(
onHelpClick,
@@ -185,16 +192,18 @@ fun KeyMapAppBar(
LaunchedEffect(state.groupName) {
showDeleteGroupDialog = false
error = null
+ val endPosition = state.groupName.length
if (state.isEditingGroupName) {
if (state.isNewGroup) {
newName = TextFieldValue()
} else {
- val endPosition = state.groupName.length
-
newName =
TextFieldValue(state.groupName, selection = TextRange(endPosition))
}
+ } else {
+ newName =
+ TextFieldValue(state.groupName, selection = TextRange(endPosition))
}
}
@@ -280,10 +289,10 @@ private fun RootGroupAppBar(
state: KeyMapAppBarState.RootGroup,
scrollBehavior: TopAppBarScrollBehavior,
onTogglePausedClick: () -> Unit,
- onSortClick: () -> Unit,
onFixWarningClick: (String) -> Unit,
onNewGroupClick: () -> Unit,
onGroupClick: (String) -> Unit,
+ navigationIcon: @Composable () -> Unit,
actions: @Composable RowScope.() -> Unit,
) {
// This is taken from the AppBar color code.
@@ -317,14 +326,7 @@ private fun RootGroupAppBar(
onTogglePausedClick = onTogglePausedClick,
)
},
- navigationIcon = {
- IconButton(onClick = onSortClick) {
- Icon(
- Icons.AutoMirrored.Rounded.Sort,
- contentDescription = stringResource(R.string.home_app_bar_sort),
- )
- }
- },
+ navigationIcon = navigationIcon,
actions = actions,
colors = appBarColors,
)
@@ -912,7 +914,7 @@ private fun KeyMapsChildGroupPreview() {
isNewGroup = false,
)
KeyMapperTheme {
- KeyMapAppBar(modifier = Modifier.fillMaxWidth(), state = state)
+ KeyMapListAppBar(modifier = Modifier.fillMaxWidth(), state = state)
}
}
@@ -931,7 +933,7 @@ private fun KeyMapsChildGroupDarkPreview() {
isNewGroup = false,
)
KeyMapperTheme(darkTheme = true) {
- KeyMapAppBar(modifier = Modifier.fillMaxWidth(), state = state)
+ KeyMapListAppBar(modifier = Modifier.fillMaxWidth(), state = state)
}
}
@@ -982,7 +984,7 @@ private fun KeyMapsChildGroupEditingDarkPreview() {
}
KeyMapperTheme(darkTheme = true) {
- KeyMapAppBar(
+ KeyMapListAppBar(
state = state,
)
}
@@ -1022,7 +1024,7 @@ private fun KeyMapsRunningPreview() {
isPaused = false,
)
KeyMapperTheme {
- KeyMapAppBar(state = state)
+ KeyMapListAppBar(state = state)
}
}
@@ -1036,7 +1038,7 @@ private fun HomeStatePausedPreview() {
isPaused = true,
)
KeyMapperTheme {
- KeyMapAppBar(state = state)
+ KeyMapListAppBar(state = state)
}
}
@@ -1062,7 +1064,7 @@ private fun HomeStateWarningsPreview() {
isPaused = true,
)
KeyMapperTheme {
- KeyMapAppBar(state = state)
+ KeyMapListAppBar(state = state)
}
}
@@ -1088,7 +1090,7 @@ private fun HomeStateWarningsDarkPreview() {
isPaused = true,
)
KeyMapperTheme(darkTheme = true) {
- KeyMapAppBar(state = state)
+ KeyMapListAppBar(state = state)
}
}
@@ -1105,7 +1107,7 @@ private fun HomeStateSelectingPreview() {
showThisGroup = false,
)
KeyMapperTheme {
- KeyMapAppBar(state = state)
+ KeyMapListAppBar(state = state)
}
}
@@ -1122,6 +1124,6 @@ private fun HomeStateSelectingDisabledPreview() {
showThisGroup = false,
)
KeyMapperTheme {
- KeyMapAppBar(state = state)
+ KeyMapListAppBar(state = state)
}
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt
index b37c03f323..9c5b0c9390 100644
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt
@@ -148,7 +148,7 @@ private fun ConfigKeyMapScreen(
modifier.displayCutoutPadding(),
snackbarHost = { SnackbarHost(snackbarHostState) },
bottomBar = {
- KeyMapAppBar(
+ ConfigKeyMapAppBar(
isKeyMapEnabled = isKeyMapEnabled,
onKeyMapEnabledChange = onKeyMapEnabledChange,
onBackClick = onBackClick,
@@ -163,7 +163,7 @@ private fun ConfigKeyMapScreen(
ConfigKeyMapTab.ACTIONS -> actionsHelpUrl
ConfigKeyMapTab.CONSTRAINTS -> constraintsHelpUrl
ConfigKeyMapTab.OPTIONS -> optionsHelpUrl
- else -> return@KeyMapAppBar
+ else -> return@ConfigKeyMapAppBar
}
if (url.isNotEmpty()) {
@@ -298,7 +298,7 @@ private fun ConfigKeyMapScreen(
}
@Composable
-private fun KeyMapAppBar(
+private fun ConfigKeyMapAppBar(
modifier: Modifier = Modifier,
isKeyMapEnabled: Boolean,
onKeyMapEnabledChange: (Boolean) -> Unit = {},
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutScreen.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutScreen.kt
index 07c63bd78e..ae124afada 100644
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutScreen.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutScreen.kt
@@ -1,6 +1,7 @@
package io.github.sds100.keymapper.mappings.keymaps
import androidx.activity.compose.BackHandler
+import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -9,14 +10,19 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.FlashlightOn
+import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.BottomAppBar
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@@ -27,12 +33,17 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.github.sds100.keymapper.Constants
import io.github.sds100.keymapper.R
import io.github.sds100.keymapper.compose.KeyMapperTheme
import io.github.sds100.keymapper.constraints.ConstraintMode
+import io.github.sds100.keymapper.groups.GroupBreadcrumbRow
+import io.github.sds100.keymapper.groups.GroupListItemModel
+import io.github.sds100.keymapper.groups.GroupRow
import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel
import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerError
import io.github.sds100.keymapper.util.Error
@@ -52,24 +63,28 @@ fun CreateKeyMapShortcutScreen(
CreateKeyMapShortcutScreen(
modifier = modifier,
- listItems = state.listItems,
+ state = state,
showShortcutNameDialog = viewModel.showShortcutNameDialog,
dismissShortcutNameDialog = { viewModel.showShortcutNameDialog = null },
onShortcutNameResult = { name ->
viewModel.shortcutNameDialogResult.value = name
viewModel.showShortcutNameDialog = null
},
- onClickKeyMap = viewModel::onKeyMapCardClick,
+ onKeyMapClick = viewModel::onKeyMapCardClick,
+ onGroupClick = viewModel::onGroupClick,
+ onPopGroupClick = viewModel::onPopGroupClick,
finishActivity = finishActivity,
-
)
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CreateKeyMapShortcutScreen(
modifier: Modifier = Modifier,
- listItems: State>,
- onClickKeyMap: (String) -> Unit = {},
+ state: KeyMapListState,
+ onKeyMapClick: (String) -> Unit = {},
+ onGroupClick: (String?) -> Unit = {},
+ onPopGroupClick: () -> Unit = {},
finishActivity: () -> Unit = {},
showShortcutNameDialog: String?,
dismissShortcutNameDialog: () -> Unit = {},
@@ -92,41 +107,101 @@ private fun CreateKeyMapShortcutScreen(
)
}
- // TODO allow navigating between groups and hide the FAB.
+ BackHandler { showBackDialog = true }
+
Scaffold(
modifier = modifier,
- bottomBar = {
- BottomAppBar(
- actions = {
- IconButton(onClick = { showBackDialog = true }) {
- Icon(
- Icons.AutoMirrored.Rounded.ArrowBack,
- contentDescription = stringResource(R.string.bottom_app_bar_back_content_description),
- )
+ topBar = {
+ AnimatedContent(state.appBarState, contentKey = { it::class }) { state ->
+ when (state) {
+ is KeyMapAppBarState.RootGroup ->
+ Column(modifier) {
+ CenterAlignedTopAppBar(
+ title = {
+ Text(stringResource(R.string.create_key_map_shortcut_app_title))
+ },
+ navigationIcon = {
+ IconButton(onClick = { showBackDialog = true }) {
+ Icon(
+ Icons.AutoMirrored.Rounded.ArrowBack,
+ contentDescription = stringResource(R.string.bottom_app_bar_back_content_description),
+ )
+ }
+ },
+ )
+
+ GroupRow(
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ .fillMaxWidth(),
+ groups = state.subGroups,
+ showNewGroup = false,
+ onGroupClick = onGroupClick,
+ isSubgroups = false,
+ )
+ }
+
+ is KeyMapAppBarState.ChildGroup -> {
+ Column {
+ TopAppBar(
+ title = {
+ Text(
+ state.groupName,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = onPopGroupClick) {
+ Icon(
+ Icons.AutoMirrored.Rounded.ArrowBack,
+ contentDescription = stringResource(R.string.home_app_bar_pop_group),
+ )
+ }
+ },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ ),
+ )
+
+ GroupBreadcrumbRow(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ groups = state.breadcrumbs,
+ onGroupClick = onGroupClick,
+ )
+
+ GroupRow(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 8.dp),
+ groups = state.subGroups,
+ showNewGroup = false,
+ onGroupClick = onGroupClick,
+ enabled = true,
+ isSubgroups = true,
+ )
+ }
}
- },
- )
+
+ else -> {}
+ }
+ }
},
) { contentPadding ->
Column(modifier = Modifier.padding(contentPadding)) {
- Text(
- modifier = Modifier.padding(16.dp),
- text = stringResource(R.string.caption_create_keymap_shortcut),
- )
-
KeyMapList(
modifier = Modifier.fillMaxSize(),
footerText = stringResource(R.string.create_key_map_shortcut_footer),
- listItems = listItems,
+ listItems = state.listItems,
isSelectable = false,
- onClickKeyMap = onClickKeyMap,
+ onClickKeyMap = onKeyMapClick,
)
}
}
-
- BackHandler {
- showBackDialog = true
- }
}
@Composable
@@ -193,7 +268,7 @@ private fun ShortcutNameDialog(
}
@Composable
-private fun sampleList(): List {
+private fun keyMapSampleList(): List {
val context = LocalContext.current
return listOf(
@@ -260,12 +335,106 @@ private fun sampleList(): List {
)
}
+@Composable
+private fun constraintsSampleList(): List {
+ val ctx = LocalContext.current
+
+ return listOf(
+ ComposeChipModel.Normal(
+ id = "1",
+ text = "Device is locked",
+ icon = ComposeIconInfo.Vector(Icons.Outlined.Lock),
+ ),
+ ComposeChipModel.Normal(
+ id = "2",
+ text = "Key Mapper is open",
+ icon = ComposeIconInfo.Drawable(ctx.drawable(R.mipmap.ic_launcher_round)),
+ ),
+ ComposeChipModel.Error(
+ id = "2",
+ text = "Key Mapper not found",
+ error = Error.AppNotFound(Constants.PACKAGE_NAME),
+ ),
+ )
+}
+
+@Composable
+private fun groupSampleList(): List {
+ val ctx = LocalContext.current
+
+ return listOf(
+ GroupListItemModel(
+ uid = "1",
+ name = "Lockscreen",
+ icon = ComposeIconInfo.Vector(Icons.Outlined.Lock),
+ ),
+ GroupListItemModel(
+ uid = "2",
+ name = "Key Mapper",
+ icon = ComposeIconInfo.Drawable(ctx.drawable(R.mipmap.ic_launcher_round)),
+ ),
+ GroupListItemModel(
+ uid = "3",
+ name = "Key Mapper",
+ icon = null,
+ ),
+ )
+}
+
+@Preview
+@Composable
+private fun PreviewRootGroup() {
+ KeyMapperTheme {
+ CreateKeyMapShortcutScreen(
+ state = KeyMapListState(
+ appBarState = KeyMapAppBarState.RootGroup(
+ subGroups = groupSampleList(),
+ warnings = emptyList(),
+ isPaused = true,
+ ),
+ listItems = State.Data(keyMapSampleList()),
+ ),
+ showShortcutNameDialog = null,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewChildGroup() {
+ KeyMapperTheme {
+ CreateKeyMapShortcutScreen(
+ state = KeyMapListState(
+ appBarState = KeyMapAppBarState.ChildGroup(
+ groupName = "Very very very very very long name",
+ subGroups = groupSampleList(),
+ constraints = constraintsSampleList(),
+ parentConstraintCount = 1,
+ constraintMode = ConstraintMode.AND,
+ breadcrumbs = groupSampleList(),
+ isEditingGroupName = false,
+ isNewGroup = false,
+ ),
+ listItems = State.Data(keyMapSampleList()),
+ ),
+ showShortcutNameDialog = null,
+ )
+ }
+}
+
@Preview
@Composable
-private fun Preview() {
+private fun PreviewEmpty() {
KeyMapperTheme {
CreateKeyMapShortcutScreen(
- listItems = State.Data(sampleList()),
+ state = KeyMapListState(
+ appBarState = KeyMapAppBarState.RootGroup(
+ subGroups = emptyList(),
+ warnings = emptyList(),
+ isPaused = true,
+ ),
+ listItems = State.Data(emptyList()),
+ ),
showShortcutNameDialog = null,
)
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutViewModel.kt
index 102e6582f0..50cde0b7a6 100644
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutViewModel.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutViewModel.kt
@@ -122,7 +122,7 @@ class CreateKeyMapShortcutViewModel(
)
}
- val parentGroupListItems = keyMapGroup.parents.map { group ->
+ val breadcrumbs = keyMapGroup.parents.plus(keyMapGroup.group).filterNotNull().map { group ->
GroupListItemModel(
uid = group.uid,
name = group.name,
@@ -142,7 +142,7 @@ class CreateKeyMapShortcutViewModel(
subGroups = subGroupListItems,
constraints = emptyList(),
constraintMode = ConstraintMode.AND,
- breadcrumbs = parentGroupListItems,
+ breadcrumbs = breadcrumbs,
isEditingGroupName = false,
isNewGroup = false,
parentConstraintCount = keyMapGroup.parents.sumOf { it.constraintState.constraints.size },
@@ -220,6 +220,18 @@ class CreateKeyMapShortcutViewModel(
}
}
+ fun onGroupClick(uid: String?) {
+ viewModelScope.launch {
+ listKeyMaps.openGroup(uid)
+ }
+ }
+
+ fun onPopGroupClick() {
+ viewModelScope.launch {
+ listKeyMaps.popGroup()
+ }
+ }
+
class Factory(
private val configKeyMapUseCase: ConfigKeyMapUseCase,
private val listUseCase: ListKeyMapsUseCase,
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt
index 36bfa61983..71d155010d 100644
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt
@@ -90,7 +90,9 @@ fun KeyMapList(
is State.Data -> {
Surface(modifier = modifier) {
if (listItems.data.isEmpty()) {
- EmptyKeyMapList(modifier = Modifier.fillMaxSize())
+ EmptyKeyMapList(
+ modifier = Modifier.fillMaxSize().padding(bottom = bottomListPadding),
+ )
} else {
LoadedKeyMapList(
Modifier.fillMaxSize(),
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt
index 318b40f42b..4352967f2b 100644
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt
@@ -539,7 +539,9 @@ class KeyMapListViewModel(
}
fun onEnableGuiKeyboardClick() {
- setupGuiKeyboard.enableInputMethod()
+ coroutineScope.launch {
+ setupGuiKeyboard.enableInputMethod()
+ }
}
fun onChooseGuiKeyboardClick() {
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt
index 7116cf1550..847da51c4b 100644
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt
@@ -754,7 +754,9 @@ abstract class BaseConfigTriggerViewModel(
}
fun onEnableGuiKeyboardClick() {
- setupGuiKeyboard.enableInputMethod()
+ coroutineScope.launch {
+ setupGuiKeyboard.enableInputMethod()
+ }
}
fun onChooseGuiKeyboardClick() {
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardUseCase.kt
index 9e08992011..c891d88b8a 100644
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardUseCase.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardUseCase.kt
@@ -31,7 +31,7 @@ class SetupGuiKeyboardUseCaseImpl(
}
}
- override fun enableInputMethod() {
+ override suspend fun enableInputMethod() {
inputMethodAdapter.getInfoByPackageName(KeyMapperImeHelper.KEY_MAPPER_GUI_IME_PACKAGE)
.onSuccess {
inputMethodAdapter.enableIme(it.id)
@@ -54,7 +54,7 @@ interface SetupGuiKeyboardUseCase {
val isInstalled: Flow
val isEnabled: Flow
- fun enableInputMethod()
+ suspend fun enableInputMethod()
val isChosen: Flow
fun chooseInputMethod()
diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/ConfigSettingsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/settings/ConfigSettingsUseCase.kt
index d817d1e4a8..fbc4707022 100644
--- a/app/src/main/java/io/github/sds100/keymapper/settings/ConfigSettingsUseCase.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/settings/ConfigSettingsUseCase.kt
@@ -81,7 +81,7 @@ class ConfigSettingsUseCaseImpl(
override val connectedInputDevices: StateFlow>>
get() = devicesAdapter.connectedInputDevices
- override fun enableCompatibleIme() {
+ override suspend fun enableCompatibleIme() {
imeHelper.enableCompatibleInputMethods()
}
@@ -190,7 +190,7 @@ interface ConfigSettingsUseCase {
val rerouteKeyEvents: Flow
val isCompatibleImeChosen: Flow
val isCompatibleImeEnabled: Flow
- fun enableCompatibleIme()
+ suspend fun enableCompatibleIme()
suspend fun chooseCompatibleIme(): Result
suspend fun showImePicker(): Result<*>
diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt b/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt
index f1946aad5a..5c887d835e 100644
--- a/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt
@@ -11,7 +11,6 @@ import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.preference.DropDownPreference
-import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreferenceCompat
@@ -135,9 +134,9 @@ class MainSettingsFragment : BaseSettingsFragment() {
isSingleLineTitle = false
setTitle(R.string.title_pref_dark_theme)
+ setSummary(R.string.summary_pref_dark_theme)
entries = strArray(R.array.pref_dark_theme_entries)
entryValues = ThemeUtils.THEMES.map { it.toString() }.toTypedArray()
- summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
addPreference(this)
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt
index 8bb9c59352..6c96e76939 100644
--- a/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt
@@ -140,7 +140,9 @@ class SettingsViewModel(
}
fun onEnableCompatibleImeClick() {
- useCase.enableCompatibleIme()
+ viewModelScope.launch {
+ useCase.enableCompatibleIme()
+ }
}
fun resetDefaultMappingOptions() {
diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/ThemeUtils.kt b/app/src/main/java/io/github/sds100/keymapper/settings/ThemeUtils.kt
index e8bbde0f2c..83a6a98994 100644
--- a/app/src/main/java/io/github/sds100/keymapper/settings/ThemeUtils.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/settings/ThemeUtils.kt
@@ -9,5 +9,5 @@ object ThemeUtils {
const val LIGHT = 1
const val AUTO = 2
- val THEMES = arrayOf(DARK, LIGHT, AUTO)
+ val THEMES = arrayOf(LIGHT, DARK, AUTO)
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/BootBroadcastReceiver.kt b/app/src/main/java/io/github/sds100/keymapper/system/BootBroadcastReceiver.kt
index 57d5be017c..c61aab628b 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/BootBroadcastReceiver.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/BootBroadcastReceiver.kt
@@ -3,7 +3,7 @@ package io.github.sds100.keymapper.system
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import io.github.sds100.keymapper.ServiceLocator
+import io.github.sds100.keymapper.KeyMapperApp
/**
* Created by sds100 on 24/03/2019.
@@ -12,12 +12,9 @@ import io.github.sds100.keymapper.ServiceLocator
class BootBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
- if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
- /*
- Initializing the controller will update any notifications since it will collect the values
- in the constructor
- */
- ServiceLocator.notificationController(context)
+
+ if (intent?.action == Intent.ACTION_LOCKED_BOOT_COMPLETED) {
+ (context.applicationContext as? KeyMapperApp)?.onBootUnlocked()
}
}
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt
index 6da22df6af..51252b41d7 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt
@@ -150,6 +150,10 @@ abstract class BaseAccessibilityServiceController(
flags = flags.withFlag(AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME)
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ flags = flags.withFlag(AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR)
+ }
+
return@lazy flags
}
@@ -505,6 +509,11 @@ abstract class BaseAccessibilityServiceController(
}
is ServiceEvent.TriggerKeyMap -> triggerKeyMapFromIntent(event.uid)
+
+ is ServiceEvent.EnableInputMethod -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ accessibilityService.setInputMethodEnabled(event.imeId, true)
+ }
+
else -> Unit
}
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt
index 4698e6d4b6..449f83c0fa 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt
@@ -49,6 +49,8 @@ interface IAccessibilityService {
val rootNode: AccessibilityNodeModel?
val activeWindowPackage: Flow
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ fun setInputMethodEnabled(imeId: String, enabled: Boolean)
fun hideKeyboard()
fun showKeyboard()
val isKeyboardHidden: Flow
@@ -59,4 +61,7 @@ interface IAccessibilityService {
fun disableSelf()
fun findFocussedNode(focus: Int): AccessibilityNodeModel?
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ fun inputText(text: String)
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt
index b909f9ae6f..54e3538019 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt
@@ -4,6 +4,7 @@ import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.FingerprintGestureController
import android.accessibilityservice.GestureDescription
import android.accessibilityservice.GestureDescription.StrokeDescription
+import android.annotation.SuppressLint
import android.app.ActivityManager
import android.content.Intent
import android.content.res.Configuration
@@ -560,4 +561,17 @@ class MyAccessibilityService :
}
override fun findFocussedNode(focus: Int): AccessibilityNodeModel? = findFocus(focus)?.toModel()
+
+ override fun inputText(text: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ inputMethod?.currentInputConnection?.commitText(text, 1, null)
+ }
+ }
+
+ override fun setInputMethodEnabled(imeId: String, enabled: Boolean) {
+ @SuppressLint("CheckResult")
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ softKeyboardController.setInputMethodEnabled(imeId, enabled)
+ }
+ }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/camera/AndroidCameraAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/camera/AndroidCameraAdapter.kt
index d2a41d3712..ca96844989 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/camera/AndroidCameraAdapter.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/camera/AndroidCameraAdapter.kt
@@ -209,39 +209,21 @@ class AndroidCameraAdapter(context: Context) : CameraAdapter {
try {
val cameraId = getFlashlightCameraIdForLens(lens)
+ val flashInfo = getFlashInfo(lens)
- if (cameraId == null) {
+ if (cameraId == null || flashInfo == null) {
return when (lens) {
CameraLens.FRONT -> Error.FrontFlashNotFound
CameraLens.BACK -> Error.BackFlashNotFound
}
}
- val maxStrength = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getCharacteristicForLens(
- lens,
- CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL,
- )
- } else {
- null
- }
-
- val defaultStrength =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- getCharacteristicForLens(
- lens,
- CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL,
- )
- } else {
- null
- }
-
// try to find a camera with a flash
- if (enabled && maxStrength != null && defaultStrength != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && enabled && flashInfo.supportsVariableStrength) {
val strength = if (strengthPercent == null) {
- defaultStrength
+ flashInfo.defaultStrength
} else {
- (strengthPercent * maxStrength).toInt().coerceAtLeast(1)
+ (strengthPercent * flashInfo.maxStrength).toInt().coerceAtLeast(1)
}
cameraManager.turnOnTorchWithStrengthLevel(cameraId, strength)
} else {
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/AndroidInputMethodAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/AndroidInputMethodAdapter.kt
index 4701c96ee4..65b99149e4 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/AndroidInputMethodAdapter.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/AndroidInputMethodAdapter.kt
@@ -13,6 +13,7 @@ import android.provider.Settings
import android.view.inputmethod.InputMethodManager
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
+import io.github.sds100.keymapper.Constants
import io.github.sds100.keymapper.system.JobSchedulerHelper
import io.github.sds100.keymapper.system.SettingsUtils
import io.github.sds100.keymapper.system.accessibility.ServiceAdapter
@@ -177,7 +178,7 @@ class AndroidInputMethodAdapter(
}
}
- override fun enableIme(imeId: String): Result<*> = enableImeWithoutUserInput(imeId).otherwise {
+ override suspend fun enableIme(imeId: String): Result<*> = enableImeWithoutUserInput(imeId).otherwise {
try {
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK
@@ -189,7 +190,15 @@ class AndroidInputMethodAdapter(
}
}
- private fun enableImeWithoutUserInput(imeId: String): Result<*> = suAdapter.execute("ime enable $imeId")
+ private suspend fun enableImeWithoutUserInput(imeId: String): Result<*> {
+ return getInfoByPackageName(Constants.PACKAGE_NAME).then { keyMapperImeInfo ->
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && imeId == keyMapperImeInfo.id) {
+ serviceAdapter.send(ServiceEvent.EnableInputMethod(keyMapperImeInfo.id))
+ } else {
+ suAdapter.execute("ime enable $imeId")
+ }
+ }
+ }
override suspend fun chooseImeWithoutUserInput(imeId: String): Result {
getInfoById(imeId).onSuccess {
@@ -292,7 +301,8 @@ class AndroidInputMethodAdapter(
private fun getChosenImeId(): String = Settings.Secure.getString(ctx.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD)
private fun getImeId(packageName: String): Result {
- val imeId = inputMethodManager.inputMethodList.find { it.packageName == packageName }?.id
+ val imeId =
+ inputMethodManager.inputMethodList.find { it.packageName == packageName }?.id
return if (imeId == null) {
Error.InputMethodNotFound(packageName)
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/InputMethodAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/InputMethodAdapter.kt
index b284f031ab..1b0257b4b3 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/InputMethodAdapter.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/InputMethodAdapter.kt
@@ -11,7 +11,7 @@ interface InputMethodAdapter {
val isUserInputRequiredToChangeIme: Flow
fun showImePicker(fromForeground: Boolean): Result<*>
- fun enableIme(imeId: String): Result<*>
+ suspend fun enableIme(imeId: String): Result<*>
suspend fun chooseImeWithoutUserInput(imeId: String): Result
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeHelper.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeHelper.kt
index 3bf0d1f0d0..e62df2ad11 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeHelper.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeHelper.kt
@@ -40,7 +40,7 @@ class KeyMapperImeHelper(private val imeAdapter: InputMethodAdapter) {
imeAdapter.inputMethods
.map { containsCompatibleIme(it) }
- fun enableCompatibleInputMethods() {
+ suspend fun enableCompatibleInputMethods() {
KEY_MAPPER_IME_PACKAGE_LIST.forEach { packageName ->
imeAdapter.getInfoByPackageName(packageName).onSuccess {
imeAdapter.enableIme(it.id)
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt
index 2773a4eddf..20f63ce5a7 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeService.kt
@@ -1,13 +1,21 @@
package io.github.sds100.keymapper.system.inputmethod
+import android.annotation.SuppressLint
+import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.inputmethodservice.InputMethodService
+import android.os.Build
+import android.os.UserManager
+import android.util.Log
import android.view.KeyEvent
import android.view.MotionEvent
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
import androidx.core.content.ContextCompat
+import androidx.core.content.getSystemService
import io.github.sds100.keymapper.Constants
import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback
import io.github.sds100.keymapper.api.KeyEventRelayService
@@ -36,6 +44,15 @@ class KeyMapperImeService : InputMethodService() {
"io.github.sds100.keymapper.inputmethod.EXTRA_KEY_EVENT"
}
+ private val userManager: UserManager? by lazy { getSystemService() }
+ private val inputMethodManager: InputMethodManager? by lazy {
+ getSystemService()
+ }
+
+ private val keyguardManager: KeyguardManager? by lazy {
+ getSystemService()
+ }
+
private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action ?: return
@@ -120,6 +137,23 @@ class KeyMapperImeService : InputMethodService() {
keyEventRelayServiceWrapper.onCreate()
}
+ override fun onStartInput(attribute: EditorInfo?, restarting: Boolean) {
+ super.onStartInput(attribute, restarting)
+
+ // IMPORTANT! Select a keyboard with an actual GUI if the user needs
+ // to unlock their device. This must not be in onCreate because
+ // the switchInputMethod does not work there.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && userManager?.isUserUnlocked == false) {
+ selectNonBasicKeyboard()
+ } else if (
+ !restarting &&
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 &&
+ keyguardManager?.isDeviceLocked == true
+ ) {
+ selectNonBasicKeyboard()
+ }
+ }
+
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
event ?: return super.onGenericMotionEvent(null)
@@ -174,4 +208,28 @@ class KeyMapperImeService : InputMethodService() {
super.onDestroy()
}
+
+ @SuppressLint("LogNotTimber")
+ private fun selectNonBasicKeyboard() {
+ inputMethodManager ?: return
+
+ inputMethodManager!!.enabledInputMethodList
+ .filter {
+ it.packageName != "io.github.sds100.keymapper" &&
+ it.packageName != "io.github.sds100.keymapper.debug" &&
+ it.packageName != "io.github.sds100.keymapper.ci"
+ }
+ // Select a random one in case one of them can't be used on the lock screen such as
+ // the Google Voice Typing keyboard. This is critical because i
+ // f an input method can't be used
+ // then it will select the Key Mapper Basic Input method again and loop forever.
+ .randomOrNull()
+ ?.also {
+ Log.e(
+ KeyMapperImeService::class.simpleName,
+ "Device is locked! Select ${it.id} input method",
+ )
+ switchInputMethod(it.id)
+ }
+ }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt b/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt
index 4db23d48bb..054dd384fa 100644
--- a/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt
@@ -69,4 +69,7 @@ sealed class ServiceEvent {
@Serializable
data class TriggerKeyMap(val uid: String) : ServiceEvent()
+
+ @Serializable
+ data class EnableInputMethod(val imeId: String) : ServiceEvent()
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9d6ba52057..47df1d9510 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -55,8 +55,8 @@
You can delete saved sound files in the settings.
Can\'t find any paired devices. Is Bluetooth turned on?
- The \"Allow other apps to trigger this key map\" option will be turned on for the key map that you select if it isn\'t already. If you turn this option off later then any shortcuts or Intents to trigger this key map will not work.
Tap a key map to use as a shortcut.
+ Create key map shortcut
Enabled
Disabled
@@ -621,7 +621,8 @@
Enable this if you want to use features/actions which only work on rooted devices. Key Mapper must have root permission from your root-access-management app (e.g Magisk, SuperSU) for these features to work.
Only turn this on if you know your device is rooted and you have given Key Mapper root permission.
- Dark theme
+ Choose theme
+ Light and dark themes available
Switch between the Key Mapper keyboard and your default keyboard when you tap the notification.
Toggle Key Mapper keyboard notification
@@ -708,9 +709,9 @@
- - @string/on
- - @string/off
- - @string/follow_system
+ - Light
+ - Dark
+ - Follow system
diff --git a/app/version.properties b/app/version.properties
index ce8caecb21..993f2b53ec 100644
--- a/app/version.properties
+++ b/app/version.properties
@@ -1,3 +1,3 @@
-VERSION_NAME=3.0.0-beta.3
-VERSION_CODE=90
+VERSION_NAME=3.0.0-beta.4
+VERSION_CODE=91
VERSION_NUM=0
\ No newline at end of file