Modern super user interface (SUI) implementation on Android. The name, Sui, also comes from a character.
Sui provides Java APIs, namely Shizuku API, for root / shell apps. It mainly provides two abilities:
- Use Android Framework APIs directly, almost as if calling system APIs from Java as root or shell.
- Start an app-defined AIDL-style Java service under root or shell.
This makes privileged Android app development much more comfortable.
Another advantage is that Sui does not add binaries to PATH and does not install a standalone manager app. This means we no longer need to spend a huge amount of time fighting apps that detect them.
To be clear, the full implementation of "root" is far more than su itself. There is a lot of hard work to be done before it. Sui is not a full root solution. It requires an existing root environment and runs as a Zygisk module.
Why "su" is unfriendly for app development
su, a "shell" running as root, is too far from the Android world.
To explain this, we need to briefly talk about how system APIs work. For example, we can use PackageManager#getInstalledApplications to get the app list. This is actually an inter-process communication (IPC) process between the app process and the system_server process. The Android Framework just hides the details for us.
Android uses Binder for this type of IPC. Binder allows the server side to learn the uid and pid of the client side, so system_server can check whether the app has permission to perform the operation.
Back to su. In a su environment, we usually only have commands provided by the Android system. In the same example, to get the app list with su, we have to run pm list. This is painful:
- Text-based output: there is no structured data like
PackageInfoin Java. You have to parse text. - Slow: running a command means at least one new process is started, and
PackageManager#getInstalledApplicationsis still called insidepm list. - Limited ability: commands only cover a small part of Android APIs.
Although it is possible to use Java APIs as root with app_process through libraries such as libsu or librootjava, transferring Binder objects between the app process and the root process is painful. If you want the root process to run as a daemon, once the app process restarts, there is no cheap way to get the Binder of the root process again.
In fact, for Magisk and other root solutions, making su work is not as easy as some people think. Both su itself and the communication between su and the manager app involve a lot of unpleasant work behind the scenes.
Note: the behavior of existing apps that only support su will NOT change.
You can install Sui directly in KernelSU or another compatible root manager such as Magisk or APatch. Or download the zip from release and use Install from storage in your root manager.
Sui requires a compatible root environment. On Magisk, this means Magisk 24.0+ with Zygisk enabled. On KernelSU or APatch, it additionally requires a separate Zygisk implementation such as Zygisk Next, ReZygisk, or NeoZygisk. Do not add SystemUI or Settings to Zygisk DenyList, otherwise the injected management UI may not work properly.
- Long press the System Settings icon on the home screen to see the Sui shortcut
- In the Sui management interface, tap the menu button in the top-right corner and select Add shortcut to home screen
- Enter
*#*#784784#*#*in the default dialer app - Open the Sui management interface via the Action button in KernelSU/Magisk manager
Note: On some systems, the Sui shortcut may not appear when long-pressing Settings.
Additionally, to avoid disturbing users, newer versions have removed the feature that automatically prompts to add a shortcut when entering Developer options.
Sui stores permission states by UID. The main modes are:
- Ask / default: the app can connect to Sui and request permission through the normal flow.
- Allow root: the app will be routed to the root backend.
- Allow shell: the app will be routed to the shell backend.
- Deny: deny the app from using Sui.
- Hide: hide Sui from the target app. When Hide is enabled, the target app UID is intercepted in the Native Binder
ExecTransactstage. Its Sui bridge transaction is swallowed before it can enter BridgeService and obtain the Sui Binder.
When the permission state changes, Sui may force-stop affected apps to cut off old Binder handles and make them obtain the correct backend on the next launch.
Sui provides an interactive shell.
Since Sui does not add files to PATH, the required files need to be copied manually. See /data/adb/sui/post-install.example.sh to learn how to do this automatically.
After the files are correctly copied, use rish as sh to start an interactive shell.
Sui app development should still primarily follow the upstream Shizuku API documentation:
https://github.com/RikkaApps/Shizuku-API
Apps are recommended to use rikka.shizuku.Shizuku as the unified compatibility layer. Do not maintain a Sui-only code path. In this way, one wrapper can support both Shizuku and Sui.
In the normal integration pattern, you only need ShizukuProvider plus the regular Shizuku API flow. ShizukuProvider already attempts Sui initialization automatically, so app code usually does not need to import or call rikka.sui.Sui directly.
If you intentionally disable ShizukuProvider's automatic Sui initialization, you can still call Sui.init(packageName) manually inside your wrapper. If it receives a Binder, it passes it to the Shizuku API layer; if not, the app can continue with the normal Shizuku flow.
Example pattern with the normal auto-initialization flow:
import android.content.pm.PackageManager
import android.content.pm.IPackageManager
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
fun initPrivilegedApi() {
Shizuku.addBinderReceivedListener {
checkShizukuPermission()
}
if (Shizuku.pingBinder()) {
checkShizukuPermission()
}
}
fun checkShizukuPermission() {
if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
val binder = SystemServiceHelper.getSystemService("package")
?: return
val pm = IPackageManager.Stub.asInterface(
ShizukuBinderWrapper(binder)
)
pm.isPackageAvailable("android", 0)
} else {
Shizuku.requestPermission(0)
}
}If you want manual initialization instead, add import rikka.sui.Sui and call Sui.init(packageName) before waiting for the binder.
Common APIs include:
Shizuku.pingBinder()Shizuku.checkSelfPermission()Shizuku.requestPermission(requestCode)Shizuku.getUid(), which can be used to check the current backend identity, for example0for root and2000for shellSystemServiceHelper.getSystemService(name)ShizukuBinderWrapper, used to wrap Android Framework service bindersbindUserService(), used to start an app-defined Java service running as root or shell
Clone with submodules:
git clone --recurse-submodules https://github.com/XiaoTong6666/Sui.gitGradle tasks:
BuildType could be Debug or Release.
-
:module:assemble<BuildType>Build the module. After assemble finishes, the flashable module zip will be generated to
out. -
:module:zip<BuildType>Generate the flashable module zip to
out. -
:module:push<BuildType>Push the zip with adb to
/data/local/tmp. -
:module:flash<BuildType>Install the zip with
adb shell su -c magisk --install-module. -
:module:flashWithKsud<BuildType>Install the zip with
adb shell su -c ksud module install. -
:module:flashAndReboot<BuildType>Install the zip and reboot the device.
-
:module:flashWithKsudAndReboot<BuildType>Install the zip with ksud and reboot the device.
For example:
./gradlew :module:assembleRelease
./gradlew :module:zipRelease
./gradlew :module:flashReleaseSui requires Zygisk. Zygisk allows us to inject into system_server, SystemUI, Settings and related app processes.
In short, there are five parts:
-
Root process
This is a root process started by the root implementation during the post-fs-data stage. It starts a Java server that implements Shizuku API and private APIs used by other parts.
The root server is the main source of permission configuration. It maintains the UID permission database and syncs hidden, root allowed, shell allowed, denied and default mode states to system_server.
-
Shell process
The shell server runs as shell and serves apps granted with shell permission.
It loads UID permission states from the configuration file mirrored by the root server. When the shell backend needs to show a permission confirmation window, it delegates the request to the root server, which then triggers the SystemUI confirmation UI.
-
SystemServer inject
- Hooks
Binder#execTransactto intercept the dedicated Binder transaction used by Sui insidesystem_server - Keeps the root binder, shell binder, and permission caches for hidden/root allowed/shell allowed/denied/default mode
- Chooses which backend Binder to return based on the UID's effective permission: root gets the root binder, shell gets the shell binder
- For hidden UIDs, blocks the Sui bridge request directly; for ask/deny, still returns the root binder so the client can continue through the normal permission or denial result flow
- Hooks
-
SystemUI inject
- Opens the Sui APK fd from Sui service and loads Sui
Resourcesplus the permission dialog class - Attaches to the service and shows permission confirmation dialogs on callback
- Registers secret-code style entry points and, when triggered, launches the Sui management UI hosted in the Settings process
- Opens the Sui APK fd from Sui service and loads Sui
-
Settings inject
- Opens the Sui APK fd from Sui service and loads Sui
ResourcesplusSuiActivity - Replaces
ActivityThreadinstrumentation during Settings process startup - Maintains dynamic/pinned shortcuts and handles pinned-shortcut requests relayed from SystemUI
- When the target
Activityintent carries the Sui extra and token, instantiates and displaysSuiActivityinstead
- Opens the Sui APK fd from Sui service and loads Sui
Sui is licensed under GPL-3.0-or-later.