A root app for the BlackBerry Key2 (FolkPatch/APatch, LineageOS 22.2, 4.19 kernel) that bundles eight previously-separate tweaks into one toggle UI:
- Convenience key → Ctrl remap (
stmpe.klkey 110) - ZRAM compression algorithm + size (Off / 2GB / 3GB / 4GB)
- Adaptive keyboard backlight daemon
- Persistent wireless ADB on a user-chosen static port
- Double-Tap to Wake (DT2W)
- Keyboard Nav Lock - stops accidental Back/Home/Recents while typing
- Lockscreen PIN on Keyboard - type your PIN on the physical keyboard
- Audio FX - system-wide EQ, bass boost, and loudness, auto-profiled by output
Each module lives on its own screen, navigated from a home menu. The first
five are stateless: they fire root commands on demand and persist by
installing a script to /data/adb/service.d/. The last three (ported from
nozerorma/key2-tweaks) are
different in kind - they depend on a long-lived Key2AccessibilityService
that watches for IME visibility, intercepts physical key events, and tracks
audio sessions, since none of that is observable from a one-shot root
command. Settings for these three are stored in their own SharedPreferences
file (key2tweaks) rather than going through AssetInstaller.
The three accessibility-service modules only work once Key2 Toolbox is enabled under Settings → Accessibility - each of their screens shows a banner with a direct link there if it isn't. Reinstalling the app resets this, so it needs re-enabling after every fresh install.
app/build.gradle.kts has a commented-out signingConfigs { create("release") {...} }
block referencing kgr_signing.keystore / alias kgr. Uncomment it, point
storeFile at your keystore path, and wire signingConfig = signingConfigs.getByName("release")
into the release build type. Consider passing the password via
gradle.properties (gitignored) or an env var rather than committing it in
the build script.
- Persist: installs
assets/ctrl_key.sh(the exact known-working script) to/data/adb/service.d/ctrl_key.shviainstall -m 755. - Live apply: runs the same
setenforce 0→nsenter -t 1 -m -- mount -o rw,remount /vendor→sed -i s/FUNCTION/CTRL_LEFT/→setenforce 1sequence directly (and the reverse to disable). - Status display reads back
key 110from/vendor/usr/keylayout/stmpe.kllive, separately from whether the boot script is installed.
- Compression algorithm: read dynamically from
/sys/block/zram0/comp_algorithm, so the screen only offers algorithms this kernel actually supports (confirmed:lzo,lz4,zstd,deflate). - Size: Off / 2GB / 3GB / 4GB.
- Persist: installs
assets/zram_template.sh(with__ALGO__and__SIZE_MB__substituted) to/data/adb/service.d/zram_size.sh. Selecting "Off" removes the script. - Live apply (behind a confirmation dialog):
swapoff→ reset → setcomp_algorithm→ setdisksize→mkswap→swapon. This briefly disables swap and can cause background apps to be killed - the dialog warns about this, default is reboot-to-apply.
- This script is a persistent loop, not a one-shot config, so "enabled" =
install
assets/kbd_light.shtoservice.dfor next boot, and optionally launch it right now (nohup sh ... &) / kill it (pkill -f kbd_light.sh) so you don't need to reboot to test.
- User enters a port; persist installs
assets/adb_wireless_template.sh(with__PORT__substituted) to/data/adb/service.d/adb_wireless.sh, which setsadb_wifi_enabledand pinspersist.adb.tcp.port/service.adb.tcp.portat boot. - Live apply sets the same properties immediately.
- The screen also shows the device's current WLAN IP (via
ip route get, checked against awlan*-named interface so it doesn't report a cellular IP when WiFi is down) and the live port, so you can confirm theadb connect <ip>:<port>target at a glance. - If you previously had a separate static-port script, remove it once this module's persistence is confirmed working, so two boot scripts aren't racing to set the same property.
- Toggles the
wake_gesturesysfs node on the main touchscreen (synaptics_dsx_2.7, I2C 4-0070). - Must be applied while the screen is on - the driver only picks up the gesture-mode setting as part of its normal suspend sequence, so enabling it with the screen already off won't take effect until the next time the screen turns on and back off.
- Persist: installs
assets/dt2w.shto/data/adb/service.d/, which sleeps briefly after boot (screen is assumed on) then re-applies the write, since the value doesn't survive reboot on its own. - If the live state doesn't actually change after toggling, this may be the unresolved driver/HAL-level issue from earlier debugging (sysfs write appears to succeed but the gesture doesn't engage) rather than a bug in the app itself.
Ported from nozerorma/key2-tweaks. Stops accidental Back / Home / Recents
presses while the on-screen keyboard (IME) is visible. Three independent
toggles, all stored as booleans in the key2tweaks SharedPreferences file:
- Keyboard Nav Lock (
nav_lock_enabled): master switch for the disable-while-typing behavior. - Double-tap Back (
nav_gesture_mode, no root): keeps the buttons live; the service gatesKEYCODE_BACKinonKeyEventso a single tap is swallowed and only a double-tap (within 300ms, and only if the tap was shorter than a 350ms long-press) fires it. Home/Recents can't be gated this way - Android's window policy acts on them regardless of what an accessibility service consumes. - Disable nav buttons ALWAYS (
nav_always_off, root): permanently cuts all three capacitive buttons via the sysfs node/sys/class/input/eventN/device/0dbutton(1=on,0=off), resolved by device name (synaptics_dsx_2) each time rather than a fixed event number, so it survives reboots even if the event index shifts. The root write goes throughRootShell(aforloop over/sys/class/input/event*in one shell invocation, matching the FolkPatch one-command-per--cquirk already documented for the other modules).
The service recomputes the desired button state (reconcileNav()) whenever
an accessibility event fires or any of the three prefs change, and always
re-enables the buttons in onUnbind/onDestroy so a crash or disable never
leaves them stuck off.
Ported from nozerorma/key2-tweaks. No root needed. While the keyguard is
locked, maps physical key presses to taps on SystemUI's PIN pad via
AccessibilityNodeInfo, so the PIN can be entered on the hardware keyboard
instead of the touchscreen. Digits map phone-dialpad style onto QWERTY:
W E R = 1 2 3, S D F = 4 5 6, Z X C = 7 8 9, Q = 0 (number
row and numpad keys also work directly). Enter/D-pad-center confirms,
Delete/Backspace deletes. Button lookup tries known SystemUI view IDs first
(key0-key9, delete_button, key_enter, etc.) and falls back to a
recursive node search by visible text or content description if those IDs
don't match on this build.
Ported from nozerorma/key2-tweaks. System-wide Equalizer + BassBoost +
LoudnessEnhancer chain, no root required for the EQ itself. Effects are
attached per audio session (tracked via the
OPEN/CLOSE_AUDIO_EFFECT_CONTROL_SESSION broadcasts, plus a session-0
fallback) rather than only the global mix, since per-session attachment is
what actually processes playback on this Qualcomm ROM. Four tuning profiles
spk(speaker),wired,bt(Bluetooth A2DP),usb- auto-selected by the currently active output (AudioFx.profileFor, checked viaAudioDeviceCallbackso it re-evaluates whenever the output changes) with separate recommended-default EQ curves, bass, and loudness per profile. Hosted inside the accessibility service rather than its own service so it gets a long-lived process without a separate foreground-service notification.
LineageOS AudioFX conflict: two effect engines fighting over the same
audio sessions cancel or muddy each other, so org.lineageos.audiofx needs
to be disabled for this module's EQ to actually take effect cleanly. The
screen shows its install/enabled status and a button to toggle it via
pm disable-user --user 0 / pm enable, run through RootShell - this is
the only part of Audio FX that needs root. Turning the master Audio FX
toggle on also auto-disables LineageOS AudioFX if it's currently enabled.
In a previous session, every attempt to write to /data/adb/service.d/
from a root shell post-boot failed with "Permission denied" - including
su -c over ADB using >, dd, etc. The only thing that worked was an
install -m 755 from an interactive Termux su session. The likely
cause is a filesystem-encryption-context mismatch between the shell session
that originally created files there (the initial adb shell session at
setup time) and any shell spawned afterwards.
This app's root shell (via libsu) is yet another shell context, spawned at
app-runtime, so it may hit the same wall. Each module's screen shows a
"Persisted: Yes/No" status that's read back from disk after every write, so
you'll see immediately if a persist operation silently failed.
If persistence writes fail from the app:
- The app's live-apply / enable-now actions still work (they don't touch
service.d), so the toggles remain useful for testing. - For persistence, fall back to the Termux method: copy the relevant script
from
app/src/main/assets/(or pull it from the app'sfilesDir- the app writes a staging copy there before attempting theinstall), then from Termux:su install -m 755 /path/to/script.sh /data/adb/service.d/script.sh - If you find a write path that does work from an app-spawned root shell,
it's worth updating
AssetInstaller.installFromAssethere so the app can self-persist reliably - that's the main open question for this build.
- For stateless root-command modules, add a
core-style controller inmodules/, following the pattern ofCtrlKeyController/ZramController/KbdLightController/WirelessAdbController/Dt2wController(persist viaAssetInstaller, live-apply viaRootShell.run). - For features that need to observe ongoing state (window/IME visibility,
key events, audio sessions) rather than just fire a command, that
observation has to happen inside
Key2AccessibilityService- root has no API for "tell me when X happens," only for executing commands. Add the logic there (or as a class it hosts, likeAudioFx), store settings as SharedPreferences booleans/ints in thekey2tweaksprefs file, and write the corresponding screen to read/write those same keys directly rather than going throughAssetInstaller. - Either way, add a corresponding screen in
ui/(following e.g.CtrlKeyScreen.ktfor the simple case orNavLockScreen.kt/AudioFxScreen.ktfor the prefs-based case, all built on the sharedScreenScaffold), and wire it into the navigation host and home menu inui/HomeScreen.ktandui/Screen.kt. - Drop any new boot scripts in
app/src/main/assets/.