Skip to content

Commit

Permalink
#1274 wip: support assistant trigger in keymapcontroller
Browse files Browse the repository at this point in the history
  • Loading branch information
sds100 committed Oct 6, 2024
1 parent a3dc103 commit b3125be
Show file tree
Hide file tree
Showing 18 changed files with 367 additions and 233 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1492,21 +1492,22 @@ class KeyMapController(
this.clickType == event.clickType
}

private fun KeyCodeTriggerKey.matchesWithOtherKey(otherKey: KeyCodeTriggerKey): Boolean = when (this.device) {
TriggerKeyDevice.Any ->
this.keyCode == otherKey.keyCode &&
this.clickType == otherKey.clickType

is TriggerKeyDevice.External ->
this.keyCode == otherKey.keyCode &&
this.device == otherKey.device &&
this.clickType == otherKey.clickType

TriggerKeyDevice.Internal ->
this.keyCode == otherKey.keyCode &&
otherKey.device == TriggerKeyDevice.Internal &&
this.clickType == otherKey.clickType
}
private fun KeyCodeTriggerKey.matchesWithOtherKey(otherKey: KeyCodeTriggerKey): Boolean =
when (this.device) {
TriggerKeyDevice.Any ->
this.keyCode == otherKey.keyCode &&
this.clickType == otherKey.clickType

is TriggerKeyDevice.External ->
this.keyCode == otherKey.keyCode &&
this.device == otherKey.device &&
this.clickType == otherKey.clickType

TriggerKeyDevice.Internal ->
this.keyCode == otherKey.keyCode &&
otherKey.device == TriggerKeyDevice.Internal &&
this.clickType == otherKey.clickType
}

private fun longPressDelay(trigger: Trigger): Long =
trigger.longPressDelay?.toLong() ?: defaultLongPressDelay.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import io.github.sds100.keymapper.data.repositories.PreferenceRepository
import io.github.sds100.keymapper.mappings.BaseConfigMappingUseCase
import io.github.sds100.keymapper.mappings.ClickType
import io.github.sds100.keymapper.mappings.ConfigMappingUseCase
import io.github.sds100.keymapper.mappings.keymaps.trigger.AssistantTriggerKey
import io.github.sds100.keymapper.mappings.keymaps.trigger.AssistantTriggerType
import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyCodeTriggerKey
import io.github.sds100.keymapper.mappings.keymaps.trigger.Trigger
import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKey
import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKeyDevice
import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerMode
import io.github.sds100.keymapper.system.devices.DevicesAdapter
Expand Down Expand Up @@ -38,36 +41,51 @@ class ConfigKeyMapUseCaseImpl(
private val showDeviceDescriptors: Flow<Boolean> =
preferenceRepository.get(Keys.showDeviceDescriptors).map { it ?: false }

override fun addTriggerKey(
keyCode: Int,
device: TriggerKeyDevice,
) = editTrigger { trigger ->
override fun addAssistantTriggerKey(type: AssistantTriggerType) = editTrigger { trigger ->
val clickType = when (trigger.mode) {
is TriggerMode.Parallel -> trigger.mode.clickType
TriggerMode.Sequence -> ClickType.SHORT_PRESS
TriggerMode.Undefined -> ClickType.SHORT_PRESS
}

val containsKey = trigger.keys.any { keyToCompare ->
if (trigger.mode != TriggerMode.Sequence) {
val sameKeyCode = keyCode == keyToCompare.keyCode
// Check whether the trigger already contains the key because if so
// then it must be converted to a sequence trigger.
val containsKey = trigger.keys.any { it is AssistantTriggerKey }

// if the new key is not external, check whether a trigger key already exists for this device
val sameDevice = when {
keyToCompare.device is TriggerKeyDevice.External &&
device is TriggerKeyDevice.External ->
keyToCompare.device.descriptor == device.descriptor
val triggerKey = AssistantTriggerKey(type = type, clickType = clickType)

else -> true
}
val newKeys = trigger.keys.plus(triggerKey)

sameKeyCode && sameDevice
} else {
false
}
val newMode = when {
trigger.mode != TriggerMode.Sequence && containsKey -> TriggerMode.Sequence
newKeys.size <= 1 -> TriggerMode.Undefined

/* Automatically make it a parallel trigger when the user makes a trigger with more than one key
because this is what most users are expecting when they make a trigger with multiple keys */
newKeys.size == 2 && !containsKey -> TriggerMode.Parallel(newKeys[0].clickType)
else -> trigger.mode
}

trigger.copy(keys = newKeys, mode = newMode)
}

override fun addKeyCodeTriggerKey(
keyCode: Int,
device: TriggerKeyDevice,
) = editTrigger { trigger ->
val clickType = when (trigger.mode) {
is TriggerMode.Parallel -> trigger.mode.clickType
TriggerMode.Sequence -> ClickType.SHORT_PRESS
TriggerMode.Undefined -> ClickType.SHORT_PRESS
}

val newKeys = trigger.keys.toMutableList()
// Check whether the trigger already contains the key because if so
// then it must be converted to a sequence trigger.
val containsKey = trigger.keys
.mapNotNull { it as? KeyCodeTriggerKey }
.any { keyToCompare ->
keyToCompare.keyCode == keyCode && keyToCompare.device.isSameDevice(device)
}

var consumeKeyEvent = true

Expand All @@ -80,18 +98,18 @@ class ConfigKeyMapUseCaseImpl(
keyCode = keyCode,
device = device,
clickType = clickType,
consumeKeyEvent = consumeKeyEvent,
consumeEvent = consumeKeyEvent,
)

newKeys.add(triggerKey)
val newKeys = trigger.keys.plus(triggerKey)

val newMode = when {
containsKey -> TriggerMode.Sequence
trigger.mode != TriggerMode.Sequence && containsKey -> TriggerMode.Sequence
newKeys.size <= 1 -> TriggerMode.Undefined

/* Automatically make it a parallel trigger when the user makes a trigger with more than one key
because this is what most users are expecting when they make a trigger with multiple keys */
newKeys.size == 2 && !containsKey -> TriggerMode.Parallel(clickType)
newKeys.size == 2 && !containsKey -> TriggerMode.Parallel(triggerKey.clickType)
else -> trigger.mode
}

Expand Down Expand Up @@ -120,27 +138,29 @@ class ConfigKeyMapUseCaseImpl(
}

override fun setParallelTriggerMode() = editTrigger { trigger ->
if (trigger.mode is TriggerMode.Parallel) return@editTrigger trigger
if (trigger.mode is TriggerMode.Parallel) {
return@editTrigger trigger
}

// undefined mode only allowed if one or no keys
if (trigger.keys.size <= 1) {
return@editTrigger trigger.copy(mode = TriggerMode.Undefined)
}

val oldKeys = trigger.keys
var newKeys = oldKeys.toMutableList()

if (trigger.mode !is TriggerMode.Parallel) {
// set all the keys to a short press if coming from a non-parallel trigger
// because they must all be the same click type and can't all be double pressed
newKeys = newKeys.map { key ->
key.copy(clickType = ClickType.SHORT_PRESS)
}.toMutableList()
var newKeys = oldKeys

// set all the keys to a short press if coming from a non-parallel trigger
// because they must all be the same click type and can't all be double pressed
newKeys = newKeys
.map { key -> key.setClickType(clickType = ClickType.SHORT_PRESS) }
// remove duplicates of keys that have the same keycode and device id
newKeys =
newKeys.distinctBy { Pair(it.keyCode, it.device) }.toMutableList()
}
.distinctBy { key ->
when (key) {
is AssistantTriggerKey -> key.type
is KeyCodeTriggerKey -> Pair(key.keyCode, key.device)
}
}

val newMode = if (newKeys.size <= 1) {
TriggerMode.Undefined
Expand Down Expand Up @@ -178,7 +198,7 @@ class ConfigKeyMapUseCaseImpl(
return@editTrigger oldTrigger
}

val newKeys = oldTrigger.keys.map { it.copy(clickType = ClickType.SHORT_PRESS) }
val newKeys = oldTrigger.keys.map { it.setClickType(clickType = ClickType.SHORT_PRESS) }
val newMode = if (newKeys.size <= 1) {
TriggerMode.Undefined
} else {
Expand All @@ -189,50 +209,65 @@ class ConfigKeyMapUseCaseImpl(
}

override fun setTriggerLongPress() {
editTrigger { oldTrigger ->
if (oldTrigger.mode == TriggerMode.Sequence) {
return@editTrigger oldTrigger
editTrigger { trigger ->
if (trigger.mode == TriggerMode.Sequence) {
return@editTrigger trigger
}

// You can't set the trigger to a long press if it contains a key
// that isn't detected with key codes. This is because there aren't
// separate key events for the up and down press that can be timed.
if (trigger.keys.any { it !is KeyCodeTriggerKey }) {
return@editTrigger trigger
}

val newKeys = oldTrigger.keys.map { it.copy(clickType = ClickType.LONG_PRESS) }
val newKeys = trigger.keys.map { it.setClickType(clickType = ClickType.LONG_PRESS) }
val newMode = if (newKeys.size <= 1) {
TriggerMode.Undefined
} else {
TriggerMode.Parallel(ClickType.LONG_PRESS)
}

oldTrigger.copy(keys = newKeys, mode = newMode)
trigger.copy(keys = newKeys, mode = newMode)
}
}

override fun setTriggerDoublePress() {
editTrigger { oldTrigger ->
if (oldTrigger.mode != TriggerMode.Undefined) {
return@editTrigger oldTrigger
editTrigger { trigger ->
if (trigger.mode != TriggerMode.Undefined) {
return@editTrigger trigger
}

val newKeys = oldTrigger.keys.map { it.copy(clickType = ClickType.DOUBLE_PRESS) }
val newKeys = trigger.keys.map { it.setClickType(clickType = ClickType.DOUBLE_PRESS) }
val newMode = TriggerMode.Undefined

oldTrigger.copy(keys = newKeys, mode = newMode)
trigger.copy(keys = newKeys, mode = newMode)
}
}

override fun setTriggerKeyClickType(keyUid: String, clickType: ClickType) {
editTriggerKey(keyUid) {
it.copy(clickType = clickType)
it.setClickType(clickType = clickType)
}
}

override fun setTriggerKeyDevice(keyUid: String, device: TriggerKeyDevice) {
editTriggerKey(keyUid) {
it.copy(device = device)
if (it is KeyCodeTriggerKey) {
it.copy(device = device)
} else {
it
}
}
}

override fun setTriggerKeyConsumeKeyEvent(keyUid: String, consumeKeyEvent: Boolean) {
editTriggerKey(keyUid) {
it.copy(consumeKeyEvent = consumeKeyEvent)
if (it is KeyCodeTriggerKey) {
it.copy(consumeEvent = consumeKeyEvent)
} else {
it
}
}
}

Expand Down Expand Up @@ -443,7 +478,7 @@ class ConfigKeyMapUseCaseImpl(
}
}

private fun editTriggerKey(uid: String, block: (key: KeyCodeTriggerKey) -> KeyCodeTriggerKey) {
private fun editTriggerKey(uid: String, block: (key: TriggerKey) -> TriggerKey) {
editTrigger { oldTrigger ->
val newKeys = oldTrigger.keys.map {
if (it.uid == uid) {
Expand All @@ -464,7 +499,8 @@ class ConfigKeyMapUseCaseImpl(

interface ConfigKeyMapUseCase : ConfigMappingUseCase<KeyMapAction, KeyMap> {
// trigger
fun addTriggerKey(keyCode: Int, device: TriggerKeyDevice)
fun addKeyCodeTriggerKey(keyCode: Int, device: TriggerKeyDevice)
fun addAssistantTriggerKey(type: AssistantTriggerType)
fun removeTriggerKey(uid: String)
fun moveTriggerKey(fromIndex: Int, toIndex: Int)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.view.KeyEvent
import io.github.sds100.keymapper.data.Keys
import io.github.sds100.keymapper.data.repositories.PreferenceRepository
import io.github.sds100.keymapper.mappings.DisplaySimpleMappingUseCase
import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyCodeTriggerKey
import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerError
import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter
import io.github.sds100.keymapper.system.inputmethod.KeyMapperImeHelper
Expand Down Expand Up @@ -49,7 +50,11 @@ class DisplayKeyMapUseCaseImpl(
errors.add(TriggerError.CANT_DETECT_IN_PHONE_CALL)
}

if (trigger.keys.any { it.keyCode in keysThatRequireDndAccess }) {
val requiresDndAccess = trigger.keys
.mapNotNull { it as? KeyCodeTriggerKey }
.any { it.keyCode in keysThatRequireDndAccess }

if (requiresDndAccess) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
!permissionAdapter.isGranted(Permission.ACCESS_NOTIFICATION_POLICY)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.github.sds100.keymapper.constraints.ConstraintState
import io.github.sds100.keymapper.data.entities.KeyMapEntity
import io.github.sds100.keymapper.mappings.Mapping
import io.github.sds100.keymapper.mappings.keymaps.detection.KeyMapController
import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyCodeTriggerKey
import io.github.sds100.keymapper.mappings.keymaps.trigger.Trigger
import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerEntityMapper
import kotlinx.serialization.Serializable
Expand Down Expand Up @@ -62,13 +63,25 @@ data class KeyMap(
}

/**
* @return whether this key map requires an input method to send the key events
* because otherwise it won't be detected.
* Whether this key map requires an input method to detect the key events.
* If the key map needs to answer or end a call then it must use an input method to detect
* the key events because volume key events are not sent to accessibility services when a call
* is incoming.
*/
fun KeyMap.requiresImeKeyEventForwarding(): Boolean =
trigger.keys.any { it.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || it.keyCode == KeyEvent.KEYCODE_VOLUME_UP } &&
fun KeyMap.requiresImeKeyEventForwarding(): Boolean {
val hasPhoneCallAction =
actionList.any { it.data is ActionData.AnswerCall || it.data is ActionData.EndCall }

val hasVolumeKeys = trigger.keys
.mapNotNull { it as? KeyCodeTriggerKey }
.any {
it.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
it.keyCode == KeyEvent.KEYCODE_VOLUME_UP
}

return hasVolumeKeys && hasPhoneCallAction
}

object KeyMapEntityMapper {
fun fromEntity(entity: KeyMapEntity): KeyMap {
val actionList = entity.actionList.mapNotNull { KeymapActionEntityMapper.fromEntity(it) }
Expand Down
Loading

0 comments on commit b3125be

Please sign in to comment.