Skip to content
This repository was archived by the owner on Apr 13, 2026. It is now read-only.

Commit 55833b3

Browse files
authored
feat(service-auto-start): add auto-start support for rootless devices (#11)
1 parent 5e1a1d1 commit 55833b3

14 files changed

Lines changed: 597 additions & 140 deletions

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Shizuku
22

3+
## Disclaimer
4+
5+
THIS IS A **FORK** OF SHIZUKU. IF YOU'RE LOOKING FOR SHIZUKU FROM RIKKA, THIS IS NOT THE PLACE.
6+
VISIT THE OFFICIAL REPO [**_HERE_**](https://github.com/RikkaApps/Shizuku)
7+
8+
### Usage of auto-start
9+
10+
- Follow the instructions for setting up Shizuku through Wireless ADB by pairing the app
11+
- From the `Settings`, enable `Start on boot (wireless ADB)`
12+
- `WRITE_SECURE_SETTINGS` permission needs to be granted prior to enabling this setting and this can be enabled either by `rish` or by connecting the device to the machine
13+
- Run the following command:
14+
```bash
15+
adb shell pm grant moe.shizuku.privileged.api android.permission.WRITE_SECURE_SETTINGS
16+
```
17+
18+
> [!WARNING]
19+
> `WRITE_SECURE_SETTINGS` is a very sensitive permission and enable it only if you know what you're doing.
20+
> The developer of this fork is not responsible for whatever may happen later on.
21+
22+
> [!NOTE]
23+
> Auto restart service is untested
24+
325
## Background
426
527
When developing apps that requires root, the most common method is to run some commands in the su shell. For example, there is an app that uses the `pm enable/disable` command to enable/disable components.

manager/src/main/AndroidManifest.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44

55
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
66
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
7+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
78
<uses-permission android:name="moe.shizuku.manager.permission.MANAGER" />
89
<uses-permission android:name="android.permission.INTERNET" />
910
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
11+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"
12+
tools:ignore="LeanbackUsesWifi" />
13+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
14+
<uses-permission
15+
android:name="android.permission.WRITE_SECURE_SETTINGS"
16+
tools:ignore="ProtectedPermissions" />
1017
<uses-permission
1118
android:name="moe.shizuku.manager.permission.API_V23"
1219
tools:node="remove" />
@@ -130,6 +137,11 @@
130137
android:exported="false"
131138
android:foregroundServiceType="connectedDevice" />
132139

140+
<service
141+
android:name=".starter.SelfStarterService"
142+
android:enabled="true"
143+
android:exported="false" />
144+
133145
<receiver
134146
android:name=".receiver.BootCompleteReceiver"
135147
android:directBootAware="true"

manager/src/main/java/moe/shizuku/manager/ShizukuSettings.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class ShizukuSettings {
2525
public static final String NIGHT_MODE = "night_mode";
2626
public static final String LANGUAGE = "language";
2727
public static final String KEEP_START_ON_BOOT = "start_on_boot";
28+
public static final String KEEP_START_ON_BOOT_WIRELESS = "start_on_boot_wireless";
2829

2930
private static SharedPreferences sPreferences;
3031

@@ -35,11 +36,7 @@ public static SharedPreferences getPreferences() {
3536
@NonNull
3637
private static Context getSettingsStorageContext(@NonNull Context context) {
3738
Context storageContext;
38-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
39-
storageContext = context.createDeviceProtectedStorageContext();
40-
} else {
41-
storageContext = context;
42-
}
39+
storageContext = context.createDeviceProtectedStorageContext();
4340

4441
storageContext = new ContextWrapper(storageContext) {
4542
@Override
@@ -58,16 +55,11 @@ public SharedPreferences getSharedPreferences(String name, int mode) {
5855

5956
public static void initialize(Context context) {
6057
if (sPreferences == null) {
61-
sPreferences = getSettingsStorageContext(context)
62-
.getSharedPreferences(NAME, Context.MODE_PRIVATE);
58+
sPreferences = getSettingsStorageContext(context).getSharedPreferences(NAME, Context.MODE_PRIVATE);
6359
}
6460
}
6561

66-
@IntDef({
67-
LaunchMethod.UNKNOWN,
68-
LaunchMethod.ROOT,
69-
LaunchMethod.ADB,
70-
})
62+
@IntDef({LaunchMethod.UNKNOWN, LaunchMethod.ROOT, LaunchMethod.ADB,})
7163
@Retention(SOURCE)
7264
public @interface LaunchMethod {
7365
int UNKNOWN = -1;
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package moe.shizuku.manager.adb
2+
3+
import android.content.ContentResolver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.net.ConnectivityManager
7+
import android.net.NetworkCapabilities
8+
import android.provider.Settings
9+
import android.util.Log
10+
import android.widget.Toast
11+
import kotlinx.coroutines.CoroutineScope
12+
import kotlinx.coroutines.Dispatchers
13+
import kotlinx.coroutines.launch
14+
import moe.shizuku.manager.AppConstants
15+
import moe.shizuku.manager.BuildConfig
16+
import moe.shizuku.manager.ShizukuSettings
17+
import moe.shizuku.manager.starter.Starter
18+
import moe.shizuku.manager.starter.StarterActivity
19+
20+
class AdbWirelessHelper {
21+
22+
private val adbWifiKey: String = "adb_wifi_enabled"
23+
24+
fun validateThenEnableWirelessAdb(contentResolver: ContentResolver, context: Context): Boolean {
25+
val connectivityManager =
26+
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
27+
val networkCapabilities =
28+
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
29+
if (networkCapabilities != null && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
30+
enableWirelessADB(contentResolver, context)
31+
return true
32+
} else {
33+
Log.w(AppConstants.TAG, "Wireless ADB auto-start condition not met: Not on Wi-Fi.")
34+
}
35+
return false
36+
}
37+
38+
private fun enableWirelessADB(contentResolver: ContentResolver, context: Context) {
39+
// Enable wireless ADB
40+
try {
41+
Settings.Global.putInt(contentResolver, adbWifiKey, 1)
42+
Log.i(AppConstants.TAG, "Wireless Debugging enabled via secure setting.")
43+
Toast.makeText(context, "Wireless Debugging enabled", Toast.LENGTH_SHORT).show()
44+
} catch (se: SecurityException) {
45+
Log.e(AppConstants.TAG, "Permission denied trying to enable wireless debugging.", se)
46+
throw se
47+
} catch (e: Exception) {
48+
Log.e(AppConstants.TAG, "Error enabling wireless debugging.", e)
49+
throw e
50+
}
51+
}
52+
53+
fun launchStarterActivity(context: Context, host: String, port: Int) {
54+
val intent = Intent(context, StarterActivity::class.java).apply {
55+
putExtra(StarterActivity.EXTRA_IS_ROOT, false)
56+
putExtra(StarterActivity.EXTRA_HOST, host)
57+
putExtra(StarterActivity.EXTRA_PORT, port)
58+
}
59+
context.startActivity(intent)
60+
}
61+
62+
fun startShizukuViaAdb(
63+
context: Context,
64+
host: String,
65+
port: Int,
66+
coroutineScope: CoroutineScope,
67+
onOutput: (String) -> Unit,
68+
onError: (Throwable) -> Unit,
69+
onSuccess: () -> Unit = {}
70+
) {
71+
coroutineScope.launch(Dispatchers.IO) {
72+
try {
73+
Log.d(AppConstants.TAG, "Attempting to start Shizuku via ADB on $host:$port")
74+
Starter.writeSdcardFiles(context)
75+
76+
val key = try {
77+
AdbKey(
78+
PreferenceAdbKeyStore(ShizukuSettings.getPreferences()), "shizuku"
79+
)
80+
} catch (e: Throwable) {
81+
Log.e(AppConstants.TAG, "ADB Key error", e)
82+
onError(AdbKeyException(e))
83+
return@launch
84+
}
85+
86+
val commandOutput = StringBuilder()
87+
88+
AdbClient(host, port, key).use { client ->
89+
try {
90+
client.connect()
91+
Log.i(
92+
AppConstants.TAG,
93+
"ADB connected to $host:$port. Executing starter command..."
94+
)
95+
96+
client.shellCommand(Starter.sdcardCommand) { output ->
97+
val outputString = String(output)
98+
commandOutput.append(outputString)
99+
onOutput(outputString)
100+
Log.d(AppConstants.TAG, "Shizuku start output chunk: $outputString")
101+
}
102+
103+
/* Adb on MIUI Android 11 has no permission to access Android/data.
104+
Before MIUI Android 12, we can temporarily use /data/user_de.
105+
After that, is better to implement "adb push" and push files directly to /data/local/tmp.
106+
*/
107+
if (commandOutput.contains(
108+
"/Android/data/${BuildConfig.APPLICATION_ID}/start.sh: Permission denied"
109+
)
110+
) {
111+
Log.w(
112+
AppConstants.TAG,
113+
"Detected permission issue, attempting fallback using /data/user_de"
114+
)
115+
116+
val miuiMessage =
117+
"\n" + "adb have no permission to access Android/data, how could this possible ?!\n" + "try /data/user_de instead...\n" + "\n"
118+
onOutput(miuiMessage)
119+
120+
Starter.writeDataFiles(context, true) // Write to data with permissions
121+
122+
AdbClient(host, port, key).use { fallbackClient ->
123+
fallbackClient.connect()
124+
Log.i(
125+
AppConstants.TAG,
126+
"ADB reconnected for fallback. Executing data command..."
127+
)
128+
129+
fallbackClient.shellCommand(Starter.dataCommand) { output ->
130+
val outputString = String(output)
131+
onOutput(outputString)
132+
Log.d(
133+
AppConstants.TAG,
134+
"Shizuku fallback start output chunk: $outputString"
135+
)
136+
}
137+
138+
Log.i(AppConstants.TAG, "Shizuku fallback start command finished.")
139+
}
140+
}
141+
} catch (e: Throwable) {
142+
Log.e(AppConstants.TAG, "Error during ADB connection/command execution", e)
143+
onError(e)
144+
return@launch
145+
}
146+
}
147+
148+
Log.i(AppConstants.TAG, "Shizuku start via ADB completed successfully")
149+
onSuccess()
150+
} catch (e: Throwable) {
151+
Log.e(AppConstants.TAG, "Error in startShizukuViaAdb", e)
152+
onError(e)
153+
}
154+
}
155+
}
156+
}

manager/src/main/java/moe/shizuku/manager/home/AdbDialogFragment.kt

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import android.content.DialogInterface
66
import android.content.Intent
77
import android.os.Build
88
import android.os.Bundle
9-
import android.os.SystemProperties
109
import android.provider.Settings
1110
import android.view.LayoutInflater
1211
import androidx.annotation.RequiresApi
@@ -17,24 +16,24 @@ import androidx.lifecycle.MutableLiveData
1716
import com.google.android.material.dialog.MaterialAlertDialogBuilder
1817
import moe.shizuku.manager.R
1918
import moe.shizuku.manager.adb.AdbMdns
19+
import moe.shizuku.manager.adb.AdbWirelessHelper
2020
import moe.shizuku.manager.databinding.AdbDialogBinding
21-
import moe.shizuku.manager.starter.StarterActivity
22-
import java.net.InetAddress
21+
import moe.shizuku.manager.utils.EnvironmentUtils
2322

2423
@RequiresApi(Build.VERSION_CODES.R)
2524
class AdbDialogFragment : DialogFragment() {
2625

2726
private lateinit var binding: AdbDialogBinding
2827
private lateinit var adbMdns: AdbMdns
2928
private val port = MutableLiveData<Int>()
29+
private val adbWirelessHelper = AdbWirelessHelper()
3030

3131
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
3232
val context = requireContext()
3333
binding = AdbDialogBinding.inflate(LayoutInflater.from(context))
3434
adbMdns = AdbMdns(context, AdbMdns.TLS_CONNECT, port)
3535

36-
var port = SystemProperties.getInt("service.adb.tcp.port", -1)
37-
if (port == -1) port = SystemProperties.getInt("persist.adb.tcp.port", -1)
36+
val port = EnvironmentUtils.getAdbTcpPort()
3837

3938
val builder = MaterialAlertDialogBuilder(context).apply {
4039
setTitle(R.string.dialog_adb_discovery)
@@ -70,8 +69,7 @@ class AdbDialogFragment : DialogFragment() {
7069
}
7170

7271
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener {
73-
var port = SystemProperties.getInt("service.adb.tcp.port", -1)
74-
if (port == -1) port = SystemProperties.getInt("persist.adb.tcp.port", -1)
72+
val port = EnvironmentUtils.getAdbTcpPort()
7573
startAndDismiss(port)
7674
}
7775

@@ -83,13 +81,7 @@ class AdbDialogFragment : DialogFragment() {
8381

8482
private fun startAndDismiss(port: Int) {
8583
val host = "127.0.0.1"
86-
val intent = Intent(context, StarterActivity::class.java).apply {
87-
putExtra(StarterActivity.EXTRA_IS_ROOT, false)
88-
putExtra(StarterActivity.EXTRA_HOST, host)
89-
putExtra(StarterActivity.EXTRA_PORT, port)
90-
}
91-
requireContext().startActivity(intent)
92-
84+
adbWirelessHelper.launchStarterActivity(requireContext(), host, port)
9385
dismissAllowingStateLoss()
9486
}
9587

manager/src/main/java/moe/shizuku/manager/home/StartWirelessAdbViewHolder.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import androidx.fragment.app.FragmentActivity
1414
import moe.shizuku.manager.Helps
1515
import moe.shizuku.manager.R
1616
import moe.shizuku.manager.adb.AdbPairingTutorialActivity
17+
import moe.shizuku.manager.adb.AdbWirelessHelper
1718
import moe.shizuku.manager.databinding.HomeItemContainerBinding
1819
import moe.shizuku.manager.databinding.HomeStartWirelessAdbBinding
1920
import moe.shizuku.manager.ktx.toHtml
@@ -29,6 +30,8 @@ import java.net.Inet4Address
2930
class StartWirelessAdbViewHolder(binding: HomeStartWirelessAdbBinding, root: View) :
3031
BaseViewHolder<Any?>(root) {
3132

33+
private val adbWirelessHelper = AdbWirelessHelper()
34+
3235
companion object {
3336
val CREATOR = Creator<Any> { inflater: LayoutInflater, parent: ViewGroup? ->
3437
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
@@ -73,20 +76,15 @@ class StartWirelessAdbViewHolder(binding: HomeStartWirelessAdbBinding, root: Vie
7376
val port = EnvironmentUtils.getAdbTcpPort()
7477
if (port > 0) {
7578
val host = "127.0.0.1"
76-
val intent = Intent(context, StarterActivity::class.java).apply {
77-
putExtra(StarterActivity.EXTRA_IS_ROOT, false)
78-
putExtra(StarterActivity.EXTRA_HOST, host)
79-
putExtra(StarterActivity.EXTRA_PORT, port)
80-
}
81-
context.startActivity(intent)
79+
adbWirelessHelper.launchStarterActivity(context, host, port)
8280
} else {
8381
WadbNotEnabledDialogFragment().show(context.asActivity<FragmentActivity>().supportFragmentManager)
8482
}
8583
}
8684

8785
@RequiresApi(Build.VERSION_CODES.R)
8886
private fun onPairClicked(context: Context) {
89-
if ((context.display?.displayId ?: -1) > 0) {
87+
if (context.display.displayId > 0) {
9088
// Running in a multi-display environment (e.g., Windows Subsystem for Android),
9189
// pairing dialog can be displayed simultaneously with Shizuku.
9290
// Input from notification is harder to use under this situation.

0 commit comments

Comments
 (0)