commit 89ba7f3d3b8126cd2c8d729a674b9b6df85a2708 truex <truex@equwal.com> 2026-09-20 22:38:14 -0700 Split the build into play and full flavours The play flavour is the build for Google Play. It has no Shizuku code and no Shizuku permission. The full flavour is for direct install and keeps shell access. Both have the same application id and signature, so one can replace the other. Shizuku is on Google Play, and so are apps that use it, so Play does not forbid it. The store build leaves it out anyway, because an app with an accessibility service and a shell has a harder review. Shell.kt exists once per flavour with the same surface. The play one always answers "not available". Shell.SUPPORTED hides the rows that lead nowhere: shell access, extra-dim, the shell notes on the Power, Navigation, Recent apps and Device report screens. Every feature that depends on the shell already falls back without it. Tested on the reader: after the play build replaced the full build, the app gave the Power button back to the firmware (short press 4, long press 5), and hold Power still reached Home through the assistant role. Listing, privacy policy, declarations and checklist now describe the play build.
CHANGELOG.md | 6 +++ README.md | 26 +++++++++-- app/build.gradle.kts | 19 ++++++-- app/src/full/AndroidManifest.xml | 19 ++++++++ .../java/dev/equwal/assistkey/shell/Shell.kt | 3 ++ app/src/main/AndroidManifest.xml | 11 ----- .../dev/equwal/assistkey/home/RecentsActivity.kt | 14 +++--- .../java/dev/equwal/assistkey/ui/MainActivity.kt | 19 +++++--- .../dev/equwal/assistkey/ui/NavigationActivity.kt | 36 +++++++++++---- .../java/dev/equwal/assistkey/ui/PowerActivity.kt | 13 ++++-- .../java/dev/equwal/assistkey/ui/ReportActivity.kt | 2 +- .../play/java/dev/equwal/assistkey/shell/Shell.kt | 51 ++++++++++++++++++++++ play/CHECKLIST.md | 9 ++-- play/DECLARATIONS.md | 6 +-- play/LISTING.md | 8 +--- play/PRIVACY.md | 3 +- 16 files changed, 189 insertions(+), 56 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md index afd4431..c7b6c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ Version code 4. SpeechRecognizer interface, so any speech recognition app on the device does the listening. The accessibility service puts the text into the focused field. Password fields are never written to. +- **Two builds.** `play` for Google Play has no Shizuku code and no Shizuku + permission. `full` for direct install keeps shell access. Same app id and + signature, so one can replace the other. +- **Viwoods: AI key goes back.** Inside the AI screen or the crop screen, the AI + key returns to the app that was in use before. Switch on the AI key screen. + Test: `tools/e2e/ai-key-return.sh`. - **Fix: Power always wakes and unlocks.** While the app manages the Power button, a press on a sleeping or locked device is never a gesture. The app also sends the wake and the keyguard dismissal through the shell, so waking diff --git a/README.md b/README.md index 5f796ae..f8a28c0 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,26 @@ Sold on Google Play as a free download with a one-time licence. Publishing, products, pricing and how the beta is run and ended are in [play/CHECKLIST.md](play/CHECKLIST.md). The app holds no `INTERNET` permission. +## Two builds + +One app, one id, one signature, two flavours: + +| Flavour | For | Shizuku | +|---|---|---| +| `play` | Google Play. `./gradlew bundlePlayRelease` | No code, no permission | +| `full` | Direct install. `./gradlew assembleFullRelease` | Yes | + +Shizuku is on Google Play and so are apps that use it, so Play does not forbid +it. The `play` flavour leaves it out anyway: an app that holds an accessibility +service and a shell has a harder review, and the store build must be the safe +one. `shell/Shell.kt` exists once per flavour with the same surface; the `play` +one always answers "not available", and every feature falls back by itself. +`Shell.SUPPORTED` hides the rows that would lead nowhere. + ## Shell access +*`full` flavour only.* + Android keeps several things from every installed app: the Power key, the navigation bar, system gestures, a maker's own key settings, the backlight node. The shell user can reach them. [Shizuku](https://shizuku.rikka.app/) @@ -324,11 +342,12 @@ initialization failed"*; that was confirmed in bytecode and on the device. ## Building ```bash -./gradlew assembleRelease bundleRelease +./gradlew assembleFullRelease assemblePlayRelease bundlePlayRelease ``` -`assembleRelease` makes the APK for testers, `bundleRelease` the `.aab` for -Play. The version comes from `gradle.properties`; `versionCode` must rise with +`bundlePlayRelease` makes the `.aab` for Play. `assembleFullRelease` makes the +APK with shell access for direct install. `assemblePlayRelease` makes the store +build as an APK, for testing it on a device. The version comes from `gradle.properties`; `versionCode` must rise with every upload. Release signing is read from `keystore.properties` at the repo root, which is @@ -377,6 +396,7 @@ store/ persistence, with the key-event hot path precomputed ui/ the configuration screens, built in code tools/ adb helpers: nav-mode (bar and gestures), ai-key (the AI key hook) +tools/e2e/ tests that run on a device, through the kernel input nodes play/ everything for the Play Console: checklist, listing, policy, graphics src/debug/ a debug-only hook that renders each screen to a PNG, because the e-ink panel defeats `adb shell screencap` diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e5c9e38..949cd88 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -69,6 +69,19 @@ android { } } + // Two builds of one app, same id and same signature. + // play: for Google Play. No Shizuku code, no Shizuku permission. + // full: for direct install. Adds shell access through Shizuku, which is + // what reads the Power button and switches the navigation bar. + flavorDimensions += "store" + productFlavors { + create("play") { dimension = "store" } + create("full") { + dimension = "store" + versionNameSuffix = "-full" + } + } + buildTypes { release { // No shrinking: the app is tiny, and R8 would only complicate @@ -122,10 +135,10 @@ dependencies { exclude(group = "com.google.android.datatransport") } - // Shell access. Shizuku (MIT) lets the app run a small service of its own + // Shell access, `full` flavour only. Shizuku (MIT) lets the app run a small service of its own // under the shell uid, which is what reads the Power key and switches the // settings Android keeps from ordinary apps. The user starts Shizuku through // wireless debugging; nothing here needs root or a computer. - implementation("dev.rikka.shizuku:api:13.1.5") - implementation("dev.rikka.shizuku:provider:13.1.5") + "fullImplementation"("dev.rikka.shizuku:api:13.1.5") + "fullImplementation"("dev.rikka.shizuku:provider:13.1.5") } diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml new file mode 100644 index 0000000..597e623 --- /dev/null +++ b/app/src/full/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- The `full` flavour only: everything Shizuku needs. The `play` flavour has none of it. --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <queries> + <package android:name="moe.shizuku.privileged.api" /> + </queries> + + <application> + <!-- How Shizuku hands this app its binder. --> + <provider + android:name="rikka.shizuku.ShizukuProvider" + android:authorities="${applicationId}.shizuku" + android:enabled="true" + android:exported="true" + android:multiprocess="false" + android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" /> + </application> +</manifest> diff --git a/app/src/main/java/dev/equwal/assistkey/shell/Shell.kt b/app/src/full/java/dev/equwal/assistkey/shell/Shell.kt similarity index 98% rename from app/src/main/java/dev/equwal/assistkey/shell/Shell.kt rename to app/src/full/java/dev/equwal/assistkey/shell/Shell.kt index 2f46da6..a902886 100644 --- a/app/src/main/java/dev/equwal/assistkey/shell/Shell.kt +++ b/app/src/full/java/dev/equwal/assistkey/shell/Shell.kt @@ -29,6 +29,9 @@ import java.util.concurrent.Executors object Shell { private const val TAG = "AssistKey" + + /** This build can use shell access. The `play` flavour has a stub with false here. */ + const val SUPPORTED = true const val SHIZUKU_PACKAGE = "moe.shizuku.privileged.api" private const val REQUEST_CODE = 7301 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1972538..9e08b2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -48,8 +48,6 @@ <package android:name="com.viwoods.launcher" /> <package android:name="com.viwoods.viwoodsai" /> <package android:name="com.viwoods.settings" /> - <!-- Shizuku, for shell access. --> - <package android:name="moe.shizuku.privileged.api" /> </queries> <application @@ -223,15 +221,6 @@ android:resource="@xml/quick_access_wallet" /> </service> - <!-- How Shizuku hands this app its binder. --> - <provider - android:name="rikka.shizuku.ShizukuProvider" - android:authorities="${applicationId}.shizuku" - android:enabled="true" - android:exported="true" - android:multiprocess="false" - android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" /> - <!-- The accessibility channel. This is the only component that sees the AI key and the volume keys; Power never reaches it. diff --git a/app/src/main/java/dev/equwal/assistkey/home/RecentsActivity.kt b/app/src/main/java/dev/equwal/assistkey/home/RecentsActivity.kt index fc0b8b1..422afc5 100644 --- a/app/src/main/java/dev/equwal/assistkey/home/RecentsActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/home/RecentsActivity.kt @@ -58,7 +58,9 @@ class RecentsActivity : Activity() { } col.note("Press and hold an app to close just that one.") } - if (!closable) col.note("With shell access this list is exact, and apps can be closed from it.") + if (!closable && Shell.SUPPORTED) { + col.note("With shell access this list is exact, and apps can be closed from it.") + } } private fun entry(col: LinearLayout, app: Apps.App, closable: Boolean) { @@ -88,13 +90,15 @@ class RecentsActivity : Activity() { val col = Ui.page(this) col.title("Recent apps") col.note( - "Android does not tell apps what has been used recently. Either give " + - "AssistKey usage access, or turn on shell access, which gives the " + - "exact list the system keeps." + "Android does not tell apps what has been used recently. Give AssistKey " + + "usage access and it can put your apps in order of last use." + + if (Shell.SUPPORTED) " Shell access gives the exact list the system keeps." else "" ) col.button("Give usage access") { runCatching { startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)) } } - col.button("Shell access") { startActivity(Intent(this, ShellActivity::class.java)) } + if (Shell.SUPPORTED) { + col.button("Shell access") { startActivity(Intent(this, ShellActivity::class.java)) } + } } } diff --git a/app/src/main/java/dev/equwal/assistkey/ui/MainActivity.kt b/app/src/main/java/dev/equwal/assistkey/ui/MainActivity.kt index 39dc78c..bf389c6 100644 --- a/app/src/main/java/dev/equwal/assistkey/ui/MainActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/ui/MainActivity.kt @@ -97,10 +97,12 @@ class MainActivity : Activity() { restrictedSettingsHint(col) } - col.row( - "Shell access: " + dev.equwal.assistkey.shell.Shell.describe(this), - "Optional. Unlocks the Power button, the navigation bar and system gestures" - ) { startActivity(Intent(this, ShellActivity::class.java)) } + if (dev.equwal.assistkey.shell.Shell.SUPPORTED) { + col.row( + "Shell access: " + dev.equwal.assistkey.shell.Shell.describe(this), + "Optional. Unlocks the Power button, the navigation bar and system gestures" + ) { startActivity(Intent(this, ShellActivity::class.java)) } + } } /** @@ -186,7 +188,8 @@ class MainActivity : Activity() { col.row( "Navigation", - "Button bar, swipe gestures, the Power key - in any mix" + if (dev.equwal.assistkey.shell.Shell.SUPPORTED) "Button bar, swipe gestures, the Power key - in any mix" + else "The Power key, and how to switch the button bar and gestures" ) { startActivity(Intent(this, NavigationActivity::class.java)) } col.row( @@ -230,8 +233,10 @@ class MainActivity : Activity() { } col.header("Display and home") - col.row("Extra-dim light", "Below the lowest the system slider allows") { - startActivity(Intent(this, DisplayActivity::class.java)) + if (dev.equwal.assistkey.shell.Shell.SUPPORTED) { + col.row("Extra-dim light", "Below the lowest the system slider allows") { + startActivity(Intent(this, DisplayActivity::class.java)) + } } col.row("Home screen", "A plain, fast launcher made for e-ink") { startActivity(Intent(this, dev.equwal.assistkey.home.HomeSettingsActivity::class.java)) diff --git a/app/src/main/java/dev/equwal/assistkey/ui/NavigationActivity.kt b/app/src/main/java/dev/equwal/assistkey/ui/NavigationActivity.kt index 956ec3d..119ba06 100644 --- a/app/src/main/java/dev/equwal/assistkey/ui/NavigationActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/ui/NavigationActivity.kt @@ -158,7 +158,12 @@ class NavigationActivity : Activity() { col.check("Swipe gestures", "Swipe up for Home; without the bar, swipe in from a side for Back", wantGestures) { choose(wantButtons, it, wantKeys) } - col.check("Power key", "Tap for Back, hold for Home, double tap for Recents", wantKeys) { + col.check( + "Power key", + if (Shell.SUPPORTED) "Tap for Back, hold for Home, double tap for Recents" + else "Hold for Home, double press for Recents", + wantKeys + ) { choose(wantButtons, wantGestures, it) } @@ -189,11 +194,16 @@ class NavigationActivity : Activity() { } // Without shell access only hold and double press have a way in. val reachable = direct || t == Channel.POWER_HOLD || t == Channel.POWER_DOUBLE + if (!reachable && !Shell.SUPPORTED) return@forEach col.row(label + ": " + b.raw(t).describe(), if (reachable) null else "Needs shell access", reachable) { startActivity(ActionPickerActivity.intent(this, t)) } } - col.row("More Power button gestures", "More taps, combinations with other keys, and how it works") { + col.row( + if (Shell.SUPPORTED) "More Power button gestures" else "Set up the Power button", + if (Shell.SUPPORTED) "More taps, combinations with other keys, and how it works" + else "Make AssistKey the assistant for hold, and the camera app for double press" + ) { startActivity(Intent(this, PowerActivity::class.java)) } if (!Channels.isSatisfied(this, Channel.ACCESSIBILITY)) { @@ -201,9 +211,10 @@ class NavigationActivity : Activity() { } if (!direct) { col.note( - "Without shell access a tap cannot reach any app. Hold works once " + - "AssistKey is the digital assistant, and double press once it is " + - "the default camera app - both are set up on the Power button screen." + (if (Shell.SUPPORTED) "Without shell access a tap cannot reach any app. " else "A tap cannot reach any app. ") + + "Hold works once AssistKey is the digital assistant, and double " + + "press once it is the default camera app - both are set up on the " + + "Power button screen." ) } } @@ -229,11 +240,18 @@ class NavigationActivity : Activity() { col.button("Apply") { applySystem(wantButtons, wantGestures) } return } - col.note("Android does not let apps switch these. Shell access lets AssistKey do it from here:") - col.row("Shell access: " + Shell.describe(this), "Set it up once, on the device, with no computer") { - startActivity(Intent(this, ShellActivity::class.java)) + if (Shell.SUPPORTED) { + col.note("Android does not let apps switch these. Shell access lets AssistKey do it from here:") + col.row("Shell access: " + Shell.describe(this), "Set it up once, on the device, with no computer") { + startActivity(Intent(this, ShellActivity::class.java)) + } + col.note("Or, with a computer and USB debugging:") + } else { + col.note( + "Android does not let apps switch these. Do it once from a computer, " + + "with the device plugged in and USB debugging on. It survives restarts." + ) } - col.note("Or, with a computer and USB debugging:") col.code(NavNative.commands(wantButtons, wantGestures).joinToString("\n")) } diff --git a/app/src/main/java/dev/equwal/assistkey/ui/PowerActivity.kt b/app/src/main/java/dev/equwal/assistkey/ui/PowerActivity.kt index c970d64..857decd 100644 --- a/app/src/main/java/dev/equwal/assistkey/ui/PowerActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/ui/PowerActivity.kt @@ -122,10 +122,17 @@ class PowerActivity : Activity() { private fun sideDoors(col: LinearLayout) { col.note( - "Android does not show the Power button to apps. With shell access " + - "AssistKey can read it anyway and every gesture becomes available." + if (Shell.SUPPORTED) { + "Android does not show the Power button to apps. With shell access " + + "AssistKey can read it anyway and every gesture becomes available." + } else { + "Android does not show the Power button to apps. Two side doors are " + + "open: press and hold, and double press." + } ) - if (Shell.ready) { + if (!Shell.SUPPORTED) { + // No shell access in this build: the side doors are everything. + } else if (Shell.ready) { col.check("Let AssistKey handle the Power button", "Tap, double tap, hold and more", false) { PowerControl.setWanted(this, it) (ServiceHolder.service as? KeyFilterService)?.syncPower() diff --git a/app/src/main/java/dev/equwal/assistkey/ui/ReportActivity.kt b/app/src/main/java/dev/equwal/assistkey/ui/ReportActivity.kt index d92fabb..2d4d946 100644 --- a/app/src/main/java/dev/equwal/assistkey/ui/ReportActivity.kt +++ b/app/src/main/java/dev/equwal/assistkey/ui/ReportActivity.kt @@ -70,7 +70,7 @@ class ReportActivity : Activity() { } col.header("The report") col.code(text) - if (!Shell.ready) { + if (Shell.SUPPORTED && !Shell.ready) { col.note("With shell access the report also lists the input devices and their keys, which is the most useful part.") } } diff --git a/app/src/play/java/dev/equwal/assistkey/shell/Shell.kt b/app/src/play/java/dev/equwal/assistkey/shell/Shell.kt new file mode 100644 index 0000000..6d11c4f --- /dev/null +++ b/app/src/play/java/dev/equwal/assistkey/shell/Shell.kt @@ -0,0 +1,51 @@ +package dev.equwal.assistkey.shell + +import android.content.Context +import android.os.Handler +import android.os.Looper + +/** + * Shell access, in the build that has none. + * + * The `play` flavour carries no Shizuku code and no Shizuku permission. This + * object has the same surface as the `full` one, and always answers "not + * available", so the rest of the app needs no flavour checks beyond + * [SUPPORTED]. Everything that depends on shell access falls back by itself. + */ +object Shell { + + const val SUPPORTED = false + const val SHIZUKU_PACKAGE = "moe.shizuku.privileged.api" + + enum class State { NOT_INSTALLED, NOT_RUNNING, NO_PERMISSION, READY } + + data class Result(val exit: Int, val output: String) { + val ok: Boolean get() = exit == 0 + } + + private val main = Handler(Looper.getMainLooper()) + + val ready: Boolean get() = false + + fun state(c: Context): State = State.NOT_INSTALLED + fun describe(c: Context): String = "Not in this build" + + fun onChange(l: () -> Unit) = Unit + fun removeOnChange(l: () -> Unit) = Unit + fun connect(c: Context) = Unit + fun requestPermission() = Unit + + fun run(command: String, done: ((Result) -> Unit)? = null) { + done?.let { main.post { it(Result(-1, "no shell access")) } } + } + + fun runAll(commands: List<String>, done: ((Boolean) -> Unit)? = null) { + done?.let { main.post { it(commands.isEmpty()) } } + } + + fun watchKeys(scanCodes: Set<Int>, onKey: (code: Int, down: Boolean, at: Long) -> Unit, started: (Boolean) -> Unit) { + main.post { started(false) } + } + + fun stopWatching() = Unit +} diff --git a/play/CHECKLIST.md b/play/CHECKLIST.md index 8731276..9ae0d76 100644 --- a/play/CHECKLIST.md +++ b/play/CHECKLIST.md @@ -66,8 +66,9 @@ Neither is in git. Without them you cannot ship another update. ## 4. Upload the build Test and release > Testing > **Internal testing** > Create new release > upload -`AssistKey-0.0.1-alpha.aab` (attached to the GitHub release `v0.0.1-alpha`, or -rebuild with `./gradlew bundleRelease`). +`AssistKey-<version>-play.aab` (attached to the GitHub release, or rebuild with +`./gradlew bundlePlayRelease`). Upload the `play` build only. It has no Shizuku +code and no Shizuku permission. The `full` APK is for direct install. Internal testing needs no review and is live in minutes. Products cannot be created until one build has been uploaded. @@ -130,7 +131,7 @@ account not on the tester list, or the signatures not matching (section 3). ## 9. Running the beta, and ending it **During the beta** hand testers either the Play opt-in link or -`AssistKey-0.0.1-alpha.apk` directly. Both are the same build. While +the `play` APK from the GitHub release. Both are the same build. While `assistkey_beta_open` is Active, every install is fully unlocked for free, and each one quietly marks itself as a tester. @@ -157,5 +158,5 @@ Production and choose countries. 1. Raise `assistkey.versionCode` (must increase every upload) and `assistkey.versionName` in `gradle.properties`. -2. `./gradlew assembleRelease bundleRelease` +2. `./gradlew assembleFullRelease assemblePlayRelease bundlePlayRelease` 3. Tag it, upload the `.aab`, attach both files to a GitHub release. diff --git a/play/DECLARATIONS.md b/play/DECLARATIONS.md index 7a5bbd6..0918ff3 100644 --- a/play/DECLARATIONS.md +++ b/play/DECLARATIONS.md @@ -44,8 +44,9 @@ so it is not a financial feature.) Why "No" is accurate: the app holds no `INTERNET` permission (`aapt2 dump badging` on the release shows `WRITE_SECURE_SETTINGS`, -`PACKAGE_USAGE_STATS`, `QUICK_ACCESS_WALLET`, `com.android.vending.BILLING` and -Shizuku's `API_V23`, none of which is network access). The device report is +`PACKAGE_USAGE_STATS`, `RECORD_AUDIO`, `QUICK_ACCESS_WALLET` and +`com.android.vending.BILLING`, none of which is network access). These answers +are for the `play` build, which is the only one uploaded. The device report is composed on the device and leaves it only if the user sends it, through their own email or share target. Data that never leaves the device is not "collected" in Play's sense. Purchase handling by @@ -89,7 +90,6 @@ pointed at the reader - the reader cannot capture its own screen. | `WRITE_SECURE_SETTINGS` | Cannot be granted to a Play install; it does nothing unless the owner grants it over adb. It then lets the app switch the firmware's power-button behaviour (short press, hold duration). Declared so that the grant is possible at all. | | `RECORD_AUDIO` | Voice typing only. Requested at run time from the Voice typing screen. The speech recognition app the user chose records the audio; Android requires the calling app to hold the permission too. AssistKey receives text only, and stores and sends nothing. | | `PACKAGE_USAGE_STATS` | Special access the user grants from a settings screen. Used only by the recent-apps list to order apps by last use, on the device; nothing is stored or sent. Not needed when shell access is on. | -| `moe.shizuku.manager.permission.API_V23` | Lets the user connect AssistKey to Shizuku, a separate app they install and start themselves, for features that need the shell user. | | `QUICK_ACCESS_WALLET` | Required of any app offered as the wallet app. AssistKey serves an empty card list; it exists so a press of the wallet shortcut reaches the user's chosen action. | | `com.android.vending.BILLING` | Play Billing. | diff --git a/play/LISTING.md b/play/LISTING.md index d5779ad..a66c0f5 100644 --- a/play/LISTING.md +++ b/play/LISTING.md @@ -19,7 +19,7 @@ Remap volume keys, the power button and more. Made for e-ink readers. ``` AssistKey remaps your device's hardware keys: volume keys, the power button, page-turn buttons, camera and assistant keys, and on the Viwoods AiPaper the AI key. -Turn pages with the volume keys. Tap the power button for Back, hold it for Home, double tap it for Recents - and hide the navigation bar for good. Put three different actions on one key with a tap, a double tap and a hold. +Turn pages with the volume keys. Hold the power button for Home, double press it for Recents. Put three different actions on one key with a tap, a double tap and a hold. Built for e-ink readers, and it works on any Android 12+ phone or tablet. @@ -32,7 +32,7 @@ AI key and volume keys • A key with only a single-tap binding fires instantly - you only wait out a double-tap window on keys where you asked for a double tap Power button -• Hold Power, then press another key: three extra combinations - Back, Home and Recents by default, so you can hide the navigation bar and keep the whole screen for reading +• Hold Power, then press another key: three extra combinations • Press and hold: run any action (AssistKey becomes your digital assistant app) • Double press: run any action (AssistKey becomes your default camera app) • Short press: choose the firmware behaviour @@ -51,13 +51,9 @@ VOICE TYPING Bind a key to Voice typing: press, speak, and the words appear where the cursor is - in any app, with the keyboard you already have. AssistKey has no speech engine and no internet access; a speech recognition app on your device does the listening. MORE FOR E-INK -• Extra-dim light: go below the lowest brightness the system allows (needs shell access with root) • A plain text home screen: a clock, a few apps, type to find the rest • Recent apps as a list instead of a slow screenshot carousel -SHELL ACCESS (OPTIONAL) -Some things Android keeps from every app: the power button, the navigation bar, system gestures. AssistKey can use the free Shizuku app to reach them, set up on the device itself through wireless debugging - no computer, no root. Without it AssistKey still remaps keys, and the power button still gets hold and double press. - BUILT FOR E-INK Black on white, large text, no animation. No ads. No account. diff --git a/play/PRIVACY.md b/play/PRIVACY.md index 5c6ac4c..91c21b8 100644 --- a/play/PRIVACY.md +++ b/play/PRIVACY.md @@ -39,7 +39,8 @@ reads the usage log Android keeps, with the usage access you grant, to put apps in order of last use. This happens on the device each time the list opens; nothing from it is stored or sent. -**Shell access.** If you connect AssistKey to Shizuku, it runs commands on your +**Shell access.** Not in the Google Play build. In the build for direct +install, if you connect AssistKey to Shizuku, it runs commands on your device as the shell user to read the Power button, switch the navigation bar and gestures, and set the light. These act on your device only.