Steam Controller
2.0 · GitHub
More about this app
Uses the Steam Controller 2026 as a real Android gamepad via Shizuku-backed Linux uinput; USB, dongle or BLE.
Steam Controller for Android
Use the Steam Controller 2026 (Valve, codename Ibex) as a standard Android gamepad — no root required. Connect via USB OTG / wireless Puck, or directly via Bluetooth.
The app reads the controller's proprietary HID protocol and exposes it to Android as a virtual gamepad through Linux uinput (accessed via Shizuku), so any game that supports controllers sees a real input device — Xbox 360, Xbox One, DualShock 4 or DualSense, your choice. A Desktop mode turns the same controller into a virtual mouse + keyboard, ideal for Android TV boxes.
Features
Connection
- USB OTG — wired or via the wireless Puck dongle
- Bluetooth LE — direct pairing with the controller, no dongle needed
- Live transport switching in the UI (toggle group with USB and Bluetooth icons)
- Refresh paired BT devices without restarting the app
- In-app help dialog explaining the controller's wireless mode combos (Steam+A+R1, Steam+B+R1, etc.)
Emulation
- Five virtual profiles: Xbox 360 (default), Xbox One, Sony DualShock 4, Sony DualSense, and Desktop (mouse + keyboard)
- Cycle gamepad profiles directly from the notification (
↻ → next profile) without opening the app - Real
InputDevicevia Linuxuinput(UID shell via Shizuku UserService) — recognised by games as a real gamepad, not filtered like injected events - Automatic fallback to
IInputManager.injectInputEventif/dev/uinputis denied (less compatible, kept as safety net)
Desktop mode
- Turns the controller into a virtual mouse + keyboard — right trackpad drives the cursor, left trackpad scrolls, buttons map to common keys (volume, play/pause, back, home, enter, escape, tab, space, etc.)
- Even while a gamepad profile is active, the trackpads can double as a mouse sidecar (toggle in Calibration) so you can still navigate menus without switching profiles
- Full Android TV support: dedicated banner/leanback UI, D-pad focus navigation, on-screen keyboard shows up correctly when a text field is focused
Game Profiles
- Save the current calibration, button mapping, rumble intensity and mouse sensitivity as a named preset
- Load, rename, duplicate or delete presets from a dedicated screen
- Bind a preset to one or more apps — the service automatically switches profile when you launch a bound game (foreground-app detection, no manual step)
Tuning
- Per-stick calibration — radial dead zone (0–30%), center offset capture, Y-axis inversion, live 2D preview
- Custom button mapping — categorised list (Face / Bumpers / Triggers full-press / Stick clicks / System / Back paddles / Grips), using the official Steam Input button icons. Any source button to any target, including back paddles L4/L5/R4/R5, the Quick Access Menu button, and forcing a trigger to "fully pulled"
- Special actions — map any button to 📸 Take screenshot (saved in Pictures/Screenshots, visible immediately in the gallery)
- Rumble forwarding pipeline with adjustable intensity (0–100%) and a manual "Test rumble" button (Bluetooth only for now — see Known limitations)
Backup & restore
- Export every live setting and all Game Profiles to a single JSON file via the system file picker
- Import that file back at any time — handy after reinstalling the app or moving to a new phone
Auto-update
- Checks GitHub Releases for a newer version at launch (once every 24h) or on demand
- Shows the release notes and downloads the signed APK straight from GitHub, then hands off to the system installer
Debug & status
- HID debug view — every button, stick, trigger, trackpad, IMU quaternion and raw hex dump, updated at the controller's ~300 Hz
- Battery indicator in the status card and in the notification — works over both USB and Bluetooth
- Persistent foreground service notification with the active emulation profile, battery, profile-cycle action, and stop action
Platform
- Material 3 design with Steam blue accents
- Adaptive layouts — phone (max-width 520dp), tablet (
sw600dp, two-column layouts) and Android TV (television, leanback navigation)
Requirements
- Android 8.0+ (API 26) — phone, tablet, or Android TV
- Shizuku installed and running
- For USB: USB Host (OTG) support on the phone/tablet
- For Bluetooth: standard BLE (available on every modern Android)
- A Steam Controller 2026 (Valve Ibex). The older Steam Controller is not yet supported.
Setup
- Install Shizuku and start it (ADB Wireless on Android 11+, or one-time ADB cable for older versions).
- Install this app and grant it the Shizuku permission when prompted.
- Grant the POST_NOTIFICATIONS permission when asked (Android 13+) so the foreground status notification shows up.
- For USB: plug the Puck (or the controller directly) into the OTG port. Android will ask for USB permission.
- For Bluetooth: pair the controller via Android Settings → Bluetooth first, then select it from the Bluetooth device dropdown in the app. Use the
↻refresh button if you just paired it. - Pick the emulated controller profile (Xbox 360 is the safest default for games — broadest compatibility. Pick Desktop for mouse + keyboard, e.g. on Android TV).
- Hit Start Service. The status card shows
Mode: <profile> (uinput) ✓when everything is up. - Optional: tune everything to your liking (calibration, mapping, rumble) and save it as a Game Profile — bind it to a game so it auto-loads next time you launch it.
If uinput is blocked by SELinux on your device (rare on stock Android, possible on some hardened ROMs), the app falls back to injectInputEvent, which works in most apps but is filtered by many games.
How it works
Steam Controller (USB or BT)
│
▼
HID report parser (report 0x45 state, 53B USB / 45B BLE — plus a
dedicated 0x43 battery status report)
│ validated against SteamlessController.h + hardware capture
▼
ControllerService
• debounce (15-bit injectable mask, 3 frames)
• baseline state (ignore buttons held at startup)
• mapping (Steam buttons → Xbox buttons or special actions)
• per-stick calibration
• rumble intensity scaling
• foreground-app polling → Game Profile auto-switch
│
▼
UInputGamepad → AIDL/Binder → UInputService (UID shell via Shizuku)
│
▼
JNI uinput_jni.cpp
│
┌────────────┴────────────┐
▼ ▼
gamepad device mouse+keyboard sidecar
(Xbox/DS4/DualSense) (Desktop mode, or trackpad-
as-mouse alongside a gamepad)
│ │
└────────────┬─────────────┘
▼
/dev/uinput → kernel
│
▼
Android sees a real "Microsoft
X-Box 360 pad" (or DS4, mouse, etc.)
The HID protocol is parsed natively and translated into the chosen profile's button/axis layout before being written to a virtual gamepad created via Linux uinput. The Shizuku UserService runs as the shell user (UID 2000) which has access to /dev/uinput on most Android builds.
For BLE, the standard HID service (0x1812) is claimed by the OS, so the app uses Valve's vendor service (100f6c32-1735-4313-b402-38567131e5f3) directly. Connection priority is bumped to HIGH after connect to bring the BLE interval from ~50 ms down to ~11 ms.
Build
Standard Android Gradle build, requires:
- Android Studio Hedgehog or newer
- Android Gradle Plugin 8.5+
- Kotlin 2.0+
- NDK + CMake 3.22.1 (for the native
uinputJNI library)
First time: open the project in Android Studio and let it sync — this regenerates the Gradle wrapper. After that:
./gradlew assembleDebug
# APK lands in app/build/outputs/apk/debug/
The app supports arm64-v8a, armeabi-v7a and x86_64 ABIs.
For signed release builds and publishing to GitHub Releases, see RELEASING.md.
Configuration
Live settings and Game Profiles are persisted in SharedPreferences:
- Selected transport (USB or Bluetooth) and paired BT device address
- Emulated profile (Xbox 360 / Xbox One / DS4 / DualSense / Desktop)
- Per-stick calibration (dead zone, center offset, invert Y)
- Per-button mapping (source buttons → Xbox targets, keyboard keys, or special actions like screenshot)
- Rumble intensity (0–100%) and mouse sensitivity (Desktop mode / trackpad sidecar)
- Named Game Profiles, each with its own copy of the settings above plus the list of apps it auto-switches on
You can tweak everything live — most changes apply within ~250 ms (next mapping cache refresh) without restarting the service. Changing transport or emulated profile requires restarting the service (or use the notification's profile cycle action). Use Export backup / Import backup in the Game Profiles screen to move all of this to a JSON file — useful before uninstalling the app or when setting it up on a new device.
Known limitations
shellUID access to/dev/uinputdepends on the device's SELinux policy. Most stock Android builds allow it; some hardened ROMs may not. The app falls back toinjectInputEventautomatically in that case.- Steam button passes through as
KEYCODE_BUTTON_MODE. Android handles it as the system "Guide" key which may open the launcher in some setups. - Rumble byte format is an empirically-tuned best guess based on the Linux
hid-steamdriver. Works over Bluetooth. USB rumble is not implemented yet — the controller only vibrates when connected over BT. - Trackpads: usable as a mouse (Desktop mode, or as an optional sidecar cursor alongside a gamepad profile). Not yet exposed as a DualShock 4/DualSense touchpad to games that support one natively.
- Gyroscope (quaternion IMU) is parsed but not yet routed anywhere. Gyro aiming is planned for a future release, fits best with the DualShock 4 / DualSense profiles.
- Bluetooth auto-reconnect: if the controller powers off, the app does not retry the GATT connection.
- Shizuku at reboot: the user must restart Shizuku after each reboot of the device (an Android limitation, not the app's).
Roadmap
- USB rumble implementation
- Trackpad as a real touchpad input (DS4/DualSense profile) for games that support it
- Gyro aiming for DS4 / DualSense profiles
- USB / BT auto-reconnect
- HID debug log export ("Log to File")
Credits
The HID protocol reverse engineering credit goes to:
- SteamlessController by ddeverill — the definitive
SteamController.hbyte layout reference for the SC2026 - The Linux kernel
hid-steamdriver for additional validation of button bit positions and the rumble command structure
Other key dependencies:
- Shizuku by RikkaApps — the
uinputaccess path without root - Android USB Host API and the BLE GATT stack
- Material Components for Android for the Material 3 UI
License
MIT — see LICENSE.
How Shizuku is used
Can use the Steam Controller as a real gamepad, mouse and rumble device via Shizuku `UserService` with Linux `uinput` and input injection fallback.
This is an AI-assisted analysis of Shizuku-related usages in the app's public source code. It is best effort, so it may not catch every single usage.
How this app uses Shizuku
Shizuku lets the app run a privileged helper as the shell user so the Steam Controller appears to Android and games as a real controller.
- Play as virtual gamepad: controller buttons, sticks, triggers and D-pad are translated to an Xbox style layout and written to a Linux virtual gamepad device created through the Shizuku helper, so games see a standard controller.
- Control mouse and keyboard: in desktop mode trackpad movement, scroll and mouse clicks plus mapped keyboard keys are sent through the same virtual device, letting the controller drive the cursor and typing in apps.
- Feel game rumble: force feedback events from games are polled through the helper and forwarded to the controller for vibration.
- Take controller screenshots: a mapped button saves a screenshot with the
screencapshell command through Shizuku and adds it to Pictures/Screenshots. - Keep on-screen keyboard working: the helper reads and sets the secure keyboard setting with the
settingsshell command so the soft keyboard still shows while the controller is connected, restored on disconnect. - Fallback input injection: if the virtual device cannot be created on the device, controller input is instead injected as key and motion events through the system input service via Shizuku.
Android APIs or commands used
/dev/uinput
IInputManager.injectInputEventscreencap -psettings get secure show_ime_with_hard_keyboardsettings put secure show_ime_with_hard_keyboardsettings delete secure show_ime_with_hard_keyboardmkdir -p
Notable details
If the device blocks shell access to the virtual input device, the app automatically uses system input injection instead, which some games may ignore. All privileged operations use fixed commands (gamepad, mouse, screenshot, keyboard setting) and do not forward arbitrary user typed commands. App updates use a normal install intent with the unknown sources setting and work without Shizuku.
Changelog
What's new for version 2.0
What's Changed
- V2.0 — refonte UI, Game Profiles, Desktop Mode, module update
A major release: complete UI redesign, Game Profiles with auto-switch, Desktop mode, Android TV support, an in-app update checker, and a backup/restore system — on top of several hardware-validated bug fixes.
New Features
- Game Profiles — save the current calibration, button mapping, rumble intensity and mouse sensitivity as a named preset. Load, rename, duplicate or delete presets from a dedicated screen, and bind a preset to one or more apps so the service automatically switches profile when you launch a bound game.
- Desktop mode — a fifth emulation profile that turns the controller into a virtual mouse + keyboard: right trackpad drives the cursor, left trackpad scrolls, buttons map to common keys. Even while a gamepad profile is active, the trackpads can optionally double as a mouse sidecar.
- Android TV support — dedicated banner/leanback UI, D-pad focus navigation across every screen, on-screen keyboard now shows up correctly when a text field is focused.
- Auto-update checker — checks GitHub Releases for a newer version at launch (once every 24h) or on demand, shows release notes, downloads the signed APK, and hands off to the system installer.
- Backup & restore — export every live setting and all Game Profiles to a single JSON file, import it back at any time.
- Full UI redesign — all four screens rebuilt with Material 3, official Steam Input button icons, adaptive layouts for phone/tablet/TV.
- Trigger full-press mapping — L2/R2 "fully pulled" is now mappable, self-mapped by default.
- App version now shown in the UI and used by the update checker.
Improvements
- Rumble tuning (Bluetooth): frequency/duty cycle retuned for noticeably stronger feedback; fixed a magnitude-0 "stop" command being silently dropped.
- Button mapping screen reorganized into clearer categories.
Bug Fixes
- Virtual keyboard not appearing while the controller is connected (USB) — fixed by claiming every USB interface on connect.
- Virtual keyboard not appearing while the controller is connected (Bluetooth) — mitigated via a Settings override + explicit forced show.
- Battery flickering between 0%/99% (USB) — removed the unreliable read, now uses the dedicated battery report.
- Battery stuck at a low, wrong value (USB) — fixed the byte offset for the real 15-byte battery report.
- Battery stuck on "—" (Bluetooth) — added an explicit read to seed the initial value on a notify-only characteristic.
- Adaptive launcher icon clipped on some OEM icon themes — replaced with a proper adaptive icon.
- Minor header alignment fixes on the main screen.
Known Limitations
- USB rumble not implemented yet (Bluetooth only).
- Trackpads work as a mouse but not as a native DS4/DualSense touchpad.
- Gyroscope parsed but not routed anywhere yet.
- No Bluetooth auto-reconnect.
- Shizuku must be restarted manually after every reboot.
New Contributors
- @SonicDX12 made their first contribution in https://github.com/SonicDX12/SteamController-Android/pull/1
Full Changelog: https://github.com/SonicDX12/SteamController-Android/compare/v1.0.0...v2.0.0
Permissions
13 permissions requested