Skip to content

Commit 3592f73

Browse files
Add gesture lock re-arm option (#2452)
- Add an opt-in preference that re-arms gesture lock after a captured gesture is disabled - Surface the setting in app gesture settings and the accessibility service settings menu - Keep pattern playback from re-arming gesture lock and cover lock state transitions with tests 🤖 Auto-generated Co-authored-by: Owen McGirr <o.a.mcgirr@gmail.com>
1 parent 82b9d54 commit 3592f73

9 files changed

Lines changed: 261 additions & 28 deletions

File tree

app/src/main/java/com/enaboapps/switchify/backend/preferences/PreferenceManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class PreferenceManager(context: Context) {
4646
const val PREFERENCE_KEY_STOP_PATTERN_ON_SWITCH = "stop_pattern_on_switch"
4747
const val PREFERENCE_KEY_GESTURE_PATTERN_MANUAL_PROGRESSION = "gesture_pattern_manual_progression"
4848
const val PREFERENCE_KEY_GESTURE_LOCK = "gesture_lock"
49+
const val PREFERENCE_KEY_GESTURE_LOCK_AUTO_REENABLE = "gesture_lock_auto_reenable"
4950
const val PREFERENCE_KEY_SCAN_COLOR_SET = "scan_color_set"
5051
const val PREFERENCE_KEY_SCAN_HIGHLIGHT_TYPE = "scan_highlight_type"
5152
const val PREFERENCE_KEY_MENU_TRANSPARENCY = "menu_transparency"

app/src/main/java/com/enaboapps/switchify/screens/settings/sections/GestureSettingsSection.kt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,43 @@
11
package com.enaboapps.switchify.screens.settings.sections
22

33
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.mutableStateOf
5+
import androidx.compose.runtime.remember
6+
import androidx.compose.ui.platform.LocalContext
47
import androidx.navigation.NavController
58
import com.enaboapps.switchify.R
9+
import com.enaboapps.switchify.backend.preferences.PreferenceManager
610
import com.enaboapps.switchify.components.NavRouteLink
11+
import com.enaboapps.switchify.components.PreferenceSwitch
712
import com.enaboapps.switchify.components.Section
813
import com.enaboapps.switchify.nav.NavigationRoute
914

1015
@Composable
1116
fun GesturesSettingsSection(navController: NavController) {
17+
val context = LocalContext.current
18+
val preferenceManager = PreferenceManager(context)
19+
val gestureLockAutoReenableState = remember {
20+
mutableStateOf(
21+
preferenceManager.getBooleanValue(
22+
PreferenceManager.PREFERENCE_KEY_GESTURE_LOCK_AUTO_REENABLE,
23+
false
24+
)
25+
)
26+
}
27+
1228
Section(titleResId = R.string.settings_section_gesture) {
29+
PreferenceSwitch(
30+
titleResId = R.string.preference_title_gesture_lock_auto_reenable,
31+
summaryResId = R.string.preference_summary_gesture_lock_auto_reenable,
32+
checked = gestureLockAutoReenableState.value,
33+
onCheckedChange = {
34+
preferenceManager.setBooleanValue(
35+
PreferenceManager.PREFERENCE_KEY_GESTURE_LOCK_AUTO_REENABLE,
36+
it
37+
)
38+
gestureLockAutoReenableState.value = it
39+
}
40+
)
1341
NavRouteLink(
1442
titleResId = R.string.screen_title_gesture_patterns,
1543
summaryResId = R.string.gesture_patterns_section_summary,
@@ -23,4 +51,4 @@ fun GesturesSettingsSection(navController: NavController) {
2351
route = NavigationRoute.ScrollingSettings.name
2452
)
2553
}
26-
}
54+
}

app/src/main/java/com/enaboapps/switchify/service/gestures/GestureLockManager.kt

Lines changed: 75 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.enaboapps.switchify.service.gestures
22

33
import android.accessibilityservice.AccessibilityService
44
import com.enaboapps.switchify.R
5+
import com.enaboapps.switchify.backend.preferences.PreferenceManager
56
import com.enaboapps.switchify.service.gestures.data.GestureData
67
import com.enaboapps.switchify.service.window.MessageSeverity
78
import com.enaboapps.switchify.service.window.ServiceMessageHUD
@@ -14,6 +15,8 @@ class GestureLockManager private constructor() {
1415
private var timeoutTimer: Timer? = null
1516
private val lockTimeout = 120000L
1617
private var accessibilityService: AccessibilityService? = null
18+
private var autoReenableProviderForTesting: (() -> Boolean)? = null
19+
private var suppressHudForTesting = false
1720

1821
companion object {
1922
val instance: GestureLockManager by lazy { GestureLockManager() }
@@ -29,31 +32,19 @@ class GestureLockManager private constructor() {
2932

3033
isLocked = !isLocked
3134
if (isLocked) {
32-
ServiceMessageHUD.instance.showMessage(
33-
R.string.gesture_lock_enabled,
34-
ServiceMessageHUD.MessageType.DISAPPEARING,
35-
severity = MessageSeverity.Info
36-
)
35+
showMessage(R.string.gesture_lock_enabled, MessageSeverity.Info)
3736
} else {
38-
ServiceMessageHUD.instance.showMessage(
39-
R.string.gesture_lock_disabled,
40-
ServiceMessageHUD.MessageType.DISAPPEARING,
41-
severity = MessageSeverity.Info
42-
)
43-
44-
// Clear the locked gesture data
45-
lockedGestureData = null
37+
showMessage(R.string.gesture_lock_disabled, MessageSeverity.Info)
38+
disableLockInternal(allowAutoReenable = true)
4639
}
4740
}
4841

4942
/**
5043
* Explicitly disable the gesture lock.
5144
*/
52-
fun disableLock() {
45+
fun disableLock(allowAutoReenable: Boolean = true) {
5346
if (isLocked) {
54-
isLocked = false
55-
lockedGestureData = null
56-
stopTimer()
47+
disableLockInternal(allowAutoReenable)
5748
}
5849
}
5950

@@ -94,8 +85,10 @@ class GestureLockManager private constructor() {
9485
} else {
9586
null
9687
}
97-
if (isLocked) {
88+
if (lockedGestureData != null) {
9889
startTimer()
90+
} else {
91+
stopTimer()
9992
}
10093
}
10194

@@ -107,13 +100,7 @@ class GestureLockManager private constructor() {
107100
timeoutTimer = Timer()
108101
timeoutTimer?.schedule(object : TimerTask() {
109102
override fun run() {
110-
isLocked = false
111-
setLockedGestureData(null)
112-
ServiceMessageHUD.instance.showMessage(
113-
R.string.gesture_lock_timeout_disabled,
114-
ServiceMessageHUD.MessageType.DISAPPEARING,
115-
severity = MessageSeverity.Warning
116-
)
103+
handleLockTimeout()
117104
}
118105
}, lockTimeout)
119106
}
@@ -125,4 +112,67 @@ class GestureLockManager private constructor() {
125112
timeoutTimer?.cancel()
126113
timeoutTimer = null
127114
}
115+
116+
private fun disableLockInternal(allowAutoReenable: Boolean) {
117+
val hadGesture = lockedGestureData != null
118+
isLocked = false
119+
lockedGestureData = null
120+
stopTimer()
121+
if (allowAutoReenable && hadGesture && isAutoReenableEnabled()) {
122+
isLocked = true
123+
showMessage(R.string.gesture_lock_enabled, MessageSeverity.Info)
124+
}
125+
}
126+
127+
private fun handleLockTimeout() {
128+
val hadGesture = lockedGestureData != null
129+
isLocked = false
130+
lockedGestureData = null
131+
stopTimer()
132+
showMessage(R.string.gesture_lock_timeout_disabled, MessageSeverity.Warning)
133+
if (hadGesture && isAutoReenableEnabled()) {
134+
isLocked = true
135+
showMessage(R.string.gesture_lock_enabled, MessageSeverity.Info)
136+
}
137+
}
138+
139+
private fun isAutoReenableEnabled(): Boolean {
140+
autoReenableProviderForTesting?.let { return it() }
141+
return accessibilityService?.let {
142+
PreferenceManager(it).getBooleanValue(
143+
PreferenceManager.PREFERENCE_KEY_GESTURE_LOCK_AUTO_REENABLE,
144+
false
145+
)
146+
} ?: false
147+
}
148+
149+
private fun showMessage(messageResId: Int, severity: MessageSeverity) {
150+
if (suppressHudForTesting) return
151+
ServiceMessageHUD.instance.showMessage(
152+
messageResId,
153+
ServiceMessageHUD.MessageType.DISAPPEARING,
154+
severity = severity
155+
)
156+
}
157+
158+
internal fun handleLockTimeoutForTesting() {
159+
handleLockTimeout()
160+
}
161+
162+
internal fun setAutoReenableProviderForTesting(provider: (() -> Boolean)?) {
163+
autoReenableProviderForTesting = provider
164+
}
165+
166+
internal fun setSuppressHudForTesting(suppress: Boolean) {
167+
suppressHudForTesting = suppress
168+
}
169+
170+
internal fun resetForTesting() {
171+
isLocked = false
172+
lockedGestureData = null
173+
stopTimer()
174+
accessibilityService = null
175+
autoReenableProviderForTesting = null
176+
suppressHudForTesting = false
177+
}
128178
}

app/src/main/java/com/enaboapps/switchify/service/gestures/patterns/GesturePatternExecutor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class GesturePatternExecutor(
4848
fun execute() {
4949
if (isCleanedUp) return // Prevent execution on cleaned up executor
5050

51-
GestureLockManager.instance.disableLock()
51+
GestureLockManager.instance.disableLock(allowAutoReenable = false)
5252
GesturePatternManager.registerExecutor(this)
5353
isManualMode = isManualProgressionEnabled()
5454
currentStepIndex.set(-1)

app/src/main/java/com/enaboapps/switchify/service/menu/menus/settings/SettingsMenuStructure.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,33 @@ class SettingsMenuStructure(
125125
MenuManager.getInstance().closeMenuHierarchy()
126126
}
127127
)
128+
},
129+
MenuItemRegistry.getDefinition(
130+
MenuConstants.MenuIds.SETTINGS_MENU,
131+
MenuConstants.ItemIds.Settings.TOGGLE_GESTURE_LOCK_REARM
132+
)?.let { def ->
133+
val prefManager = PreferenceManager(accessibilityService)
134+
val currentlyEnabled = prefManager.getBooleanValue(
135+
PreferenceManager.PREFERENCE_KEY_GESTURE_LOCK_AUTO_REENABLE,
136+
false
137+
)
138+
val stateLabel = accessibilityService.getString(
139+
if (currentlyEnabled) R.string.menu_item_turn_gesture_lock_rearm_off
140+
else R.string.menu_item_turn_gesture_lock_rearm_on
141+
)
142+
MenuItem(
143+
id = def.id,
144+
userProvidedText = stateLabel,
145+
descriptionResource = def.descriptionResource,
146+
drawableId = def.drawableId,
147+
action = {
148+
prefManager.setBooleanValue(
149+
PreferenceManager.PREFERENCE_KEY_GESTURE_LOCK_AUTO_REENABLE,
150+
!currentlyEnabled
151+
)
152+
MenuManager.getInstance().closeMenuHierarchy()
153+
}
154+
)
128155
}
129156
),
130157
context = accessibilityService,

app/src/main/java/com/enaboapps/switchify/service/menu/structure/MenuConstants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ object MenuConstants {
122122
const val SWITCH_TO_POINT_SCAN = "switch_to_point_scan"
123123
const val TOGGLE_HEAD_CONTROL = "toggle_head_control"
124124
const val TOGGLE_GROUP_SCAN = "toggle_group_scan"
125+
const val TOGGLE_GESTURE_LOCK_REARM = "toggle_gesture_lock_rearm"
125126
}
126127

127128
/**

app/src/main/java/com/enaboapps/switchify/service/menu/structure/MenuItemRegistry.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ object MenuItemRegistry {
4545
MenuItemDefinition(MenuConstants.ItemIds.Settings.SWITCH_TO_RADAR, labelResource = R.string.access_technique_radar, descriptionResource = R.string.menu_item_switch_to_radar_description, drawableId = R.drawable.ic_radar),
4646
MenuItemDefinition(MenuConstants.ItemIds.Settings.SWITCH_TO_POINT_SCAN, labelResource = R.string.access_technique_point_scan, descriptionResource = R.string.menu_item_switch_to_point_scan_description, drawableId = R.drawable.ic_point_scan),
4747
MenuItemDefinition(MenuConstants.ItemIds.Settings.TOGGLE_HEAD_CONTROL, labelResource = R.string.menu_item_enable_head_control, descriptionResource = R.string.menu_item_toggle_head_control_description, drawableId = R.drawable.ic_head_control_pointer),
48-
MenuItemDefinition(MenuConstants.ItemIds.Settings.TOGGLE_GROUP_SCAN, labelResource = R.string.menu_item_group_scan, descriptionResource = R.string.menu_item_group_scan_description, drawableId = R.drawable.ic_toggle_group_scan)
48+
MenuItemDefinition(MenuConstants.ItemIds.Settings.TOGGLE_GROUP_SCAN, labelResource = R.string.menu_item_group_scan, descriptionResource = R.string.menu_item_group_scan_description, drawableId = R.drawable.ic_toggle_group_scan),
49+
MenuItemDefinition(MenuConstants.ItemIds.Settings.TOGGLE_GESTURE_LOCK_REARM, labelResource = R.string.preference_title_gesture_lock_auto_reenable, descriptionResource = R.string.menu_item_gesture_lock_rearm_description, drawableId = R.drawable.ic_toggle_gesture_lock)
4950
)
5051
}
5152

app/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
<string name="menu_item_turn_group_scan_on">Turn Group Scan On</string>
3636
<string name="menu_item_turn_group_scan_off">Turn Group Scan Off</string>
3737
<string name="menu_item_group_scan_description">Toggle group scan</string>
38+
<string name="menu_item_turn_gesture_lock_rearm_on">Turn Re-arm Gesture Lock On</string>
39+
<string name="menu_item_turn_gesture_lock_rearm_off">Turn Re-arm Gesture Lock Off</string>
40+
<string name="menu_item_gesture_lock_rearm_description">Turn gesture lock back on ready to choose another gesture</string>
3841
<string name="menu_item_pause">Pause Switchify</string>
3942

4043
<!-- Head Control Menu Items -->
@@ -693,6 +696,8 @@
693696
<string name="preference_summary_manual_pattern_progression">Advance through gesture pattern steps by pressing a switch instead of automatic timing</string>
694697
<string name="preference_title_stop_pattern_on_switch">Stop Pattern on Switch Press</string>
695698
<string name="preference_summary_stop_pattern_on_switch">When enabled, pressing any switch will stop the currently executing gesture pattern</string>
699+
<string name="preference_title_gesture_lock_auto_reenable">Re-arm Gesture Lock</string>
700+
<string name="preference_summary_gesture_lock_auto_reenable">After gesture lock turns off, turn it back on ready to choose another gesture</string>
696701

697702
<!-- Access Technique Settings Screen -->
698703
<string name="screen_title_access_technique_settings">Access Technique Settings</string>

0 commit comments

Comments
 (0)