feat(gestures): add user-selectable sleep method preference (#6664)

Allow users to choose their preferred sleep method (Auto/Root/
Accessibility Service/Device Admin) for the double-tap-to-sleep
gesture, instead of relying on the fixed priority fallback chain.

This addresses the issue where OEM ROMs aggressively kill
accessibility services, making the sleep gesture unreliable on
non-rooted devices. Users can now explicitly select Device Admin
as an alternative that won't be killed by battery optimization.

Closes #6463
This commit is contained in:
wellorbetter
2026-04-23 18:23:12 +08:00
committed by GitHub
parent 0aafcc14ca
commit 923bc293d1
5 changed files with 75 additions and 1 deletions
+6
View File
@@ -616,6 +616,12 @@
<string name="generic_a11y_hint">To %1$s, turn on the Lawnchair accessibility service. Tap \"Open settings\", select \"Lawnchair\" and turn on \"Use Lawnchair.\"</string>
<string name="generic_a11y_disclaimer">Lawnchair uses Accessibility\'s `performGlobalAction` method to perform this action. This is a sensitive permission that allows monitoring other apps. However, Lawnchair is not configured for that functionality and receives no events.</string>
<string name="sleep_mode_label">Sleep mode</string>
<string name="sleep_mode_auto">Automatic</string>
<string name="sleep_mode_root">Root</string>
<string name="sleep_mode_accessibility">Accessibility service</string>
<string name="sleep_mode_device_admin">Device admin</string>
<!--
Bug reporting
@@ -25,16 +25,31 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import app.lawnchair.LawnchairLauncher
import app.lawnchair.preferences2.PreferenceManager2
import app.lawnchair.util.requireSystemService
import app.lawnchair.views.ComposeBottomSheet
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.flow.first
class SleepGestureHandler(context: Context) : GestureHandler(context) {
override suspend fun onTrigger(launcher: LawnchairLauncher) {
methods.first { it.isSupported() }.sleep(launcher)
val pref = PreferenceManager2.getInstance(context).sleepMode.get().first()
val method = when (pref) {
SleepMode.AUTO -> methods.first { it.isSupported() }
SleepMode.ROOT -> methods.filterIsInstance<SleepMethodRoot>().first()
SleepMode.ACCESSIBILITY -> methods.filterIsInstance<SleepMethodPieAccessibility>().first()
SleepMode.DEVICE_ADMIN -> methods.filterIsInstance<SleepMethodDeviceAdmin>().first()
}
if (pref != SleepMode.AUTO && !method.isSupported()) {
methods.first { it.isSupported() }.sleep(launcher)
return
}
method.sleep(launcher)
}
private val methods = listOf(
@@ -0,0 +1,34 @@
package app.lawnchair.gestures.handlers
import androidx.annotation.StringRes
import androidx.compose.ui.res.stringResource
import app.lawnchair.ui.preferences.components.controls.ListPreferenceEntry
import com.android.launcher3.R
enum class SleepMode(
@StringRes val labelResourceId: Int,
) {
AUTO(
labelResourceId = R.string.sleep_mode_auto,
),
ROOT(
labelResourceId = R.string.sleep_mode_root,
),
ACCESSIBILITY(
labelResourceId = R.string.sleep_mode_accessibility,
),
DEVICE_ADMIN(
labelResourceId = R.string.sleep_mode_device_admin,
),
;
companion object {
fun values() = enumValues<SleepMode>().toList()
fun fromString(string: String) = values().firstOrNull { it.toString() == string }
fun entries(): List<ListPreferenceEntry<SleepMode>> = values().map {
ListPreferenceEntry(value = it) { stringResource(id = it.labelResourceId) }
}
}
}
@@ -31,6 +31,7 @@ import androidx.datastore.preferences.preferencesDataStore
import app.lawnchair.data.Converters
import app.lawnchair.font.FontCache
import app.lawnchair.gestures.config.GestureHandlerConfig
import app.lawnchair.gestures.handlers.SleepMode
import app.lawnchair.gestures.type.GestureType
import app.lawnchair.hotseat.HotseatMode
import app.lawnchair.icons.CustomAdaptiveIconDrawable
@@ -747,6 +748,13 @@ class PreferenceManager2 @Inject constructor(
defaultValue = GestureHandlerConfig.Sleep,
)
val sleepMode = preference(
key = stringPreferencesKey(name = "sleep_mode"),
defaultValue = SleepMode.AUTO,
parse = { SleepMode.fromString(it) ?: SleepMode.AUTO },
save = { it.toString() },
)
val swipeUpGestureHandler = serializablePreference<GestureHandlerConfig>(
key = stringPreferencesKey("swipe_up_gesture_handler"),
defaultValue = GestureHandlerConfig.OpenAppDrawer,
@@ -3,10 +3,12 @@ package app.lawnchair.ui.preferences.destinations
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import app.lawnchair.gestures.handlers.SleepMode
import app.lawnchair.preferences.getAdapter
import app.lawnchair.preferences2.preferenceManager2
import app.lawnchair.ui.preferences.LocalIsExpandedScreen
import app.lawnchair.ui.preferences.components.GestureHandlerPreference
import app.lawnchair.ui.preferences.components.controls.ListPreference
import app.lawnchair.ui.preferences.components.layout.PreferenceGroup
import app.lawnchair.ui.preferences.components.layout.PreferenceLayout
import com.android.launcher3.R
@@ -65,5 +67,14 @@ fun GesturePreferences(
)
}
}
PreferenceGroup(heading = stringResource(id = R.string.sleep_mode_label)) {
Item {
ListPreference(
adapter = prefs.sleepMode.getAdapter(),
entries = SleepMode.entries(),
label = stringResource(id = R.string.sleep_mode_label),
)
}
}
}
}