commit 328b1248fba2a5aabc0071101d2803ac9fc686be truex <truex@equwal.com> 2026-09-21 04:14:25 -0700 Add an action that shows or hides the button bar Android 16 has no auto-hide for the three-button bar. ActionKind.NAV with the payload toggle_bar switches the bar through shell access, with the commands that the Navigation screen uses, and keeps the gestures as the user set them. The allow step asks for shell access when an action works through the shell: the button bar and the light.
CHANGELOG.md | 5 +++++ .../main/java/dev/equwal/assistkey/model/ActionSpec.kt | 6 ++++++ .../main/java/dev/equwal/assistkey/native/NavNative.kt | 16 ++++++++++++++++ .../main/java/dev/equwal/assistkey/route/ActionRouter.kt | 1 + .../dev/equwal/assistkey/setup/GuidedSetupActivity.kt | 15 +++++++++++++-- .../java/dev/equwal/assistkey/ui/ActionPickerActivity.kt | 3 +++ .../dev/equwal/assistkey/model/ActionSpecNameTest.kt | 6 ++++++ 7 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md index f30fae8..e9f68c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ - **Change a button from the Done step.** "Change what it does" and "Change how you press it" go back to those steps with the button kept. Back on the Done step goes to the actions too. Tests: `Route.back`. +- **Show or hide the button bar** is an action (build for direct install). + Android 16 has no auto-hide for the three-button bar, so a button does it. + It uses the shell commands of the Navigation screen and keeps the gestures + as they are. The allow step asks for shell access for this action and for + the light actions. - **AI voice prompt** is an action of its own (Viwoods). It is what the firmware does on a hold of the AI key: the Viwoods AI screen, told to start its voice prompt. It needs the Viwoods AI account, as the stock hold does. diff --git a/app/src/main/java/dev/equwal/assistkey/model/ActionSpec.kt b/app/src/main/java/dev/equwal/assistkey/model/ActionSpec.kt index 23e5d1d..a904292 100644 --- a/app/src/main/java/dev/equwal/assistkey/model/ActionSpec.kt +++ b/app/src/main/java/dev/equwal/assistkey/model/ActionSpec.kt @@ -26,6 +26,12 @@ enum class ActionKind { /** payload = package name; launches its main activity */ LAUNCH_APP, + /** + * payload = toggle_bar. Shows or hides the three-button bar through shell + * access. Android 16 has no auto-hide for that bar, so a button does it. + */ + NAV, + /** payload = "pkg/class"; explicit component, no intent-filter needed */ LAUNCH_COMPONENT, diff --git a/app/src/main/java/dev/equwal/assistkey/native/NavNative.kt b/app/src/main/java/dev/equwal/assistkey/native/NavNative.kt index 9dd71f1..18f652d 100644 --- a/app/src/main/java/dev/equwal/assistkey/native/NavNative.kt +++ b/app/src/main/java/dev/equwal/assistkey/native/NavNative.kt @@ -77,6 +77,22 @@ object NavNative { return out } + const val PREFS = "assistkey_nav" + + /** + * Shows the button bar when it is hidden, and hides it when it shows. The + * gestures stay as the user set them. It needs shell access. False where + * there is none. + */ + fun toggleBar(c: Context): Boolean { + if (!dev.equwal.assistkey.shell.Shell.ready) return false + val p = c.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + val buttons = !p.getBoolean("buttons", true) + p.edit().putBoolean("buttons", buttons).apply() + dev.equwal.assistkey.shell.Shell.runAll(shellCommands(buttons, p.getBoolean("gestures", true))) + return true + } + /** The same, for someone typing them at a computer. */ fun commands(buttons: Boolean, gestures: Boolean): List<String> = shellCommands(buttons, gestures).filterNot { it.startsWith("sleep") }.distinct().map { "adb shell $it" } diff --git a/app/src/main/java/dev/equwal/assistkey/route/ActionRouter.kt b/app/src/main/java/dev/equwal/assistkey/route/ActionRouter.kt index 0a4324c..47a7bb1 100644 --- a/app/src/main/java/dev/equwal/assistkey/route/ActionRouter.kt +++ b/app/src/main/java/dev/equwal/assistkey/route/ActionRouter.kt @@ -37,6 +37,7 @@ object ActionRouter { dev.equwal.assistkey.display.ExtraDim.act(ctx.applicationContext, spec.payload) true } + ActionKind.NAV -> dev.equwal.assistkey.native.NavNative.toggleBar(ctx.applicationContext) ActionKind.VOICE -> ServiceHolder.service ?.let { dev.equwal.assistkey.voice.Dictation.toggle(it) } ?: false } diff --git a/app/src/main/java/dev/equwal/assistkey/setup/GuidedSetupActivity.kt b/app/src/main/java/dev/equwal/assistkey/setup/GuidedSetupActivity.kt index 6c5523d..bf059c1 100644 --- a/app/src/main/java/dev/equwal/assistkey/setup/GuidedSetupActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/setup/GuidedSetupActivity.kt @@ -295,7 +295,8 @@ class GuidedSetupActivity : Activity() { val missing = plan.needs.filterNot(::satisfied) val microphone = spec.kind == ActionKind.VOICE && !Dictation.hasMicrophone(this) - if (plan.possible && missing.isEmpty() && !microphone) return if (askOnly) finish() else go(Step.DONE) + val shell = needsShell(spec) && !Shell.ready + if (plan.possible && missing.isEmpty() && !microphone && !shell) return if (askOnly) finish() else go(Step.DONE) plan.blocked?.let { col.note(it) @@ -309,6 +310,11 @@ class GuidedSetupActivity : Activity() { requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 1) } } + if (shell) { + col.row("Turn on shell access", "This action works through Shizuku") { + startActivity(Intent(this, ShellActivity::class.java)) + } + } if (Need.KEY_FILTER in missing) { col.note("If the switch turns itself off, allow restricted settings in App info.") col.button("Open App info") { Channels.safeStart(this, Channels.appInfoIntent(this)) } @@ -374,9 +380,14 @@ class GuidedSetupActivity : Activity() { val needsFilter = ActionRouter.requiresAccessibility(spec) || spec.kind == ActionKind.MENU val plan = Route.plan(t, needsFilter, env(c)) val microphone = spec.kind == ActionKind.VOICE && !Dictation.hasMicrophone(c) - return !(plan.possible && plan.needs.all { satisfied(c, it) } && !microphone) + val shell = needsShell(spec) && !Shell.ready + return !(plan.possible && plan.needs.all { satisfied(c, it) } && !microphone && !shell) } + /** The light and the button bar are set by shell commands. */ + private fun needsShell(spec: ActionSpec): Boolean = + Shell.SUPPORTED && (spec.kind == ActionKind.NAV || spec.kind == ActionKind.DIM) + private fun env(c: android.content.Context): Route.Env = Route.Env(powerIsDirect = Shell.ready && PowerControl.wanted(c), shellSupported = Shell.SUPPORTED) diff --git a/app/src/main/java/dev/equwal/assistkey/ui/ActionPickerActivity.kt b/app/src/main/java/dev/equwal/assistkey/ui/ActionPickerActivity.kt index a404c0f..ef8f939 100644 --- a/app/src/main/java/dev/equwal/assistkey/ui/ActionPickerActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/ui/ActionPickerActivity.kt @@ -65,6 +65,8 @@ class ActionPickerActivity : Activity() { viwoods(col) navigation(col) if (dev.equwal.assistkey.shell.Shell.SUPPORTED) { + col.header("Button bar") + col.row(NAV_TOGGLE.label, "Needs shell access") { choose(NAV_TOGGLE) } col.header("Light") listOf("toggle", "darker", "brighter", "system_up", "system_down").forEach { what -> val spec = ActionSpec(ActionKind.DIM, what, "") @@ -231,6 +233,7 @@ class ActionPickerActivity : Activity() { companion object { private const val EXTRA_TRIGGER = "trigger" private const val EXTRA_PICK_ONLY = "pick_only" + private val NAV_TOGGLE = ActionSpec(ActionKind.NAV, "toggle_bar", "Show or hide the button bar") /** Set by the guided setup, which asks for what a binding needs by itself. */ const val EXTRA_NO_ASK = "no_ask" diff --git a/app/src/test/java/dev/equwal/assistkey/model/ActionSpecNameTest.kt b/app/src/test/java/dev/equwal/assistkey/model/ActionSpecNameTest.kt index de5fcd9..0456411 100644 --- a/app/src/test/java/dev/equwal/assistkey/model/ActionSpecNameTest.kt +++ b/app/src/test/java/dev/equwal/assistkey/model/ActionSpecNameTest.kt @@ -21,4 +21,10 @@ class ActionSpecNameTest { @Test fun `other actions keep their stored label`() { assertEquals("Back", ActionSpec(ActionKind.GLOBAL, "BACK", "Back").describe()) } + + @Test fun `the button bar action has a name a person can read, and survives a save`() { + val spec = ActionSpec(ActionKind.NAV, "toggle_bar", "Show or hide the button bar") + assertEquals("Show or hide the button bar", spec.describe()) + assertEquals(ActionKind.NAV, ActionKind.fromName("NAV")) + } }