An Android app for the NVIDIA Shield Portable that disables and re-enables the device's built-in controller at the press of a button.
When the Shield Portable is connected to a TV, the built-in controller is still active and occupies player slot 1. This prevents an external Bluetooth gamepad from being recognised as the primary controller in games. There is no built-in OS option to disable the internal controller.
The app uses a small root-owned native daemon (grab_ctrl) that calls the Linux EVIOCGRAB ioctl on the controller's input event device. This gives the daemon exclusive ownership of the device — Android's InputManagerService stops receiving any events from it. Killing the daemon instantly releases the grab and the controller works again, with no reboot required.
- NVIDIA Shield Portable (Tegra 4, Android 5.1)
- Root access (Magisk or SuperSU)
- Android Studio with NDK installed (for building)
- The app scans
/proc/bus/input/devicesto find the event node forNVIDIA Corporation NVIDIA Controller v01.01(e.g./dev/input/event3). - It extracts a compiled native binary (
grab_ctrl) from the APK assets to the app's private data directory. - It runs
grab_ctrlas root viasu. The binary:- Calls
setsid()and ignoresSIGHUPimmediately so it survives thesushell exiting. - Opens the event device and calls
ioctl(fd, EVIOCGRAB, 1)to take exclusive ownership. - Writes its PID to
grab_pidin the app's data directory. - Sleeps indefinitely, holding the grab.
- Calls
- The app polls for the PID file to confirm the grab succeeded, then shows Disabled.
- The app reads the saved PID and runs
su -c "kill <PID>"via root shell. grab_ctrlreceivesSIGTERM, callsEVIOCGRAB(0)to release the grab, closes the fd, and exits.- Android's
InputManagerServiceimmediately resumes receiving events from the controller.
On startup the app checks whether the PID stored in grab_pid corresponds to a running process via /proc/<PID>. If the process is dead (e.g. the device was rebooted), the stale file is cleaned up and the app shows Connected.
Every standard method was tried and failed on this specific device (Linux 3.10 / Tegra 4):
| Approach | Result |
|---|---|
/sys/class/input/inputN/inhibited |
Not present — added in Linux 4.11 |
| Unbind usbhid driver | Kernel immediately rebinds the interface |
echo 0 > .../authorized |
Disables correctly, but echo 1 triggers a kernel panic on re-authorization |
echo suspend > .../power/level |
Write rejected (exit 1) by the Tegra USB PM driver |
| EVIOCGRAB from app process | SELinux blocks untrusted_app from searching /dev/input/ |
/sys/class/input/inputN/enabled (NVIDIA-specific) |
Field exists but has no effect on event delivery |
Running grab_ctrl as a root process bypasses the SELinux restriction and is the only approach that supports both disable and re-enable without a reboot.
- Open the project in Android Studio.
- Install the NDK via SDK Manager → SDK Tools → NDK (Side by side).
- Build the project (Build → Make Project). CMake compiles
grab_ctrland outputs it directly toapp/src/main/assets/where it is bundled into the APK. - Deploy to the Shield Portable.
- Grant root access when prompted on first launch.
app/src/main/
├── cpp/
│ ├── CMakeLists.txt # Builds grab_ctrl as a standalone PIE executable
│ └── grab_ctrl.c # Root daemon: opens event device, holds EVIOCGRAB
├── java/com/example/shieldcontroller/
│ ├── MainActivity.java # UI, gamepad input handling
│ └── ControllerManager.java # Device discovery, binary extraction, root commands
├── assets/
│ └── grab_ctrl # Compiled ARM binary (generated by build)
└── res/
├── drawable/
│ ├── btn_disconnect.xml # Button selector with visible gamepad focus state
│ └── btn_reconnect.xml
└── layout/
└── activity_main.xml
The app is designed to be used in landscape orientation on the Shield Portable. It can be navigated entirely with an external gamepad — the active button always has focus and responds to the A button.
- Connect the Shield Portable to a TV.
- Connect an external Bluetooth gamepad.
- Open Shield Console Mode.
- Press Disable Built-in Controller — the internal controller goes silent.
- Use the external gamepad normally.
- When done, open the app and press Re-enable Built-in Controller — the internal controller is restored instantly.