From f019060e03182102053c4849037e33f141185f0e Mon Sep 17 00:00:00 2001 From: PiX <69745008+pixincreate@users.noreply.github.com> Date: Thu, 21 Mar 2024 00:59:24 +0530 Subject: [PATCH] feat(service-auto-start): add auto-start support for rootless devices This feature is tested working on GrapheneOS Android 14, Bluejay --- README.md | 19 +++ manager/src/main/AndroidManifest.xml | 10 ++ .../moe/shizuku/manager/ShizukuSettings.java | 8 +- .../shizuku/manager/adb/AdbWirelessHelper.kt | 46 ++++++ .../shizuku/manager/home/AdbDialogFragment.kt | 18 +-- .../home/StartWirelessAdbViewHolder.kt | 11 +- .../manager/receiver/BootCompleteReceiver.kt | 40 ++++- .../manager/receiver/ShizukuReceiver.kt | 14 ++ .../manager/settings/SettingsFragment.kt | 26 +++- .../manager/starter/SelfStarterService.kt | 147 ++++++++++++++++++ manager/src/main/res/values/strings.xml | 7 +- manager/src/main/res/xml/settings.xml | 5 + 12 files changed, 316 insertions(+), 35 deletions(-) create mode 100644 manager/src/main/java/moe/shizuku/manager/adb/AdbWirelessHelper.kt create mode 100644 manager/src/main/java/moe/shizuku/manager/starter/SelfStarterService.kt diff --git a/README.md b/README.md index 2fd7d3300..80f3edf29 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,24 @@ # Shizuku +## DISCLAIMER + +THIS IS A **FORK** OF SHIZUKU. IF YOU'RE LOOKING FOR SHIZUKU FROM RIKKA, THIS IS NOT THE PLACE. +VISIT THE OFFICIAL REPO [**_HERE_**](https://github.com/RikkaApps/Shizuku) + +THIS FORK SOLELY EXISTS FOR PERSONAL USE CASE. + +### Usage of auto-start + +- Follow the instructions for setting up Shizuku through Wireless ADB by pairing the app + - From the `Settings`, enable `Start on boot (wireless ADB)` + - `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 + +> [!CAUTION] +> `WRITE_SECURE_SETTINGS` is a very sensitive permission and enable it only if you know what you're doing. I'm not responsible for whatever may happen later on. + +> [!NOTE] +> Auto restart service is untested + ## Background 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. diff --git a/manager/src/main/AndroidManifest.xml b/manager/src/main/AndroidManifest.xml index da059304b..8e4dfb301 100644 --- a/manager/src/main/AndroidManifest.xml +++ b/manager/src/main/AndroidManifest.xml @@ -7,6 +7,11 @@ + + + @@ -130,6 +135,11 @@ android:exported="false" android:foregroundServiceType="connectedDevice" /> + + = Build.VERSION_CODES.N) { - storageContext = context.createDeviceProtectedStorageContext(); - } else { - storageContext = context; - } + storageContext = context.createDeviceProtectedStorageContext(); storageContext = new ContextWrapper(storageContext) { @Override diff --git a/manager/src/main/java/moe/shizuku/manager/adb/AdbWirelessHelper.kt b/manager/src/main/java/moe/shizuku/manager/adb/AdbWirelessHelper.kt new file mode 100644 index 000000000..3df571a8e --- /dev/null +++ b/manager/src/main/java/moe/shizuku/manager/adb/AdbWirelessHelper.kt @@ -0,0 +1,46 @@ +package moe.shizuku.manager.adb + +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.provider.Settings +import android.util.Log +import android.widget.Toast +import moe.shizuku.manager.AppConstants +import moe.shizuku.manager.starter.StarterActivity + +object WirelessADBHelper { + + fun validateThenEnableWirelessAdb(contentResolver: ContentResolver, context: Context): Boolean { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + if (networkCapabilities != null && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + enableWirelessADB(contentResolver, context) + return true + } + return false + } + + private fun enableWirelessADB(contentResolver: ContentResolver, context: Context) { + // Enable wireless ADB + Settings.Global.putInt( + contentResolver, + "adb_wifi_enabled", + 1 + ) + + Log.i(AppConstants.TAG, "Wireless Debugging enabled") + Toast.makeText(context, "Wireless Debugging enabled", Toast.LENGTH_SHORT).show() + } + + fun callStartAdb(context: Context, host: String, port: Int) { + val intent = Intent(context, StarterActivity::class.java).apply { + putExtra(StarterActivity.EXTRA_IS_ROOT, false) + putExtra(StarterActivity.EXTRA_HOST, host) + putExtra(StarterActivity.EXTRA_PORT, port) + } + context.startActivity(intent) + } +} diff --git a/manager/src/main/java/moe/shizuku/manager/home/AdbDialogFragment.kt b/manager/src/main/java/moe/shizuku/manager/home/AdbDialogFragment.kt index 7de01ac6d..94cc73b90 100644 --- a/manager/src/main/java/moe/shizuku/manager/home/AdbDialogFragment.kt +++ b/manager/src/main/java/moe/shizuku/manager/home/AdbDialogFragment.kt @@ -17,9 +17,9 @@ import androidx.lifecycle.MutableLiveData import com.google.android.material.dialog.MaterialAlertDialogBuilder import moe.shizuku.manager.R import moe.shizuku.manager.adb.AdbMdns +import moe.shizuku.manager.adb.WirelessADBHelper.callStartAdb import moe.shizuku.manager.databinding.AdbDialogBinding -import moe.shizuku.manager.starter.StarterActivity -import java.net.InetAddress +import moe.shizuku.manager.utils.EnvironmentUtils @RequiresApi(Build.VERSION_CODES.R) class AdbDialogFragment : DialogFragment() { @@ -33,8 +33,7 @@ class AdbDialogFragment : DialogFragment() { binding = AdbDialogBinding.inflate(LayoutInflater.from(context)) adbMdns = AdbMdns(context, AdbMdns.TLS_CONNECT, port) - var port = SystemProperties.getInt("service.adb.tcp.port", -1) - if (port == -1) port = SystemProperties.getInt("persist.adb.tcp.port", -1) + val port = EnvironmentUtils.getAdbTcpPort() val builder = MaterialAlertDialogBuilder(context).apply { setTitle(R.string.dialog_adb_discovery) @@ -70,8 +69,7 @@ class AdbDialogFragment : DialogFragment() { } dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener { - var port = SystemProperties.getInt("service.adb.tcp.port", -1) - if (port == -1) port = SystemProperties.getInt("persist.adb.tcp.port", -1) + val port = EnvironmentUtils.getAdbTcpPort() startAndDismiss(port) } @@ -83,13 +81,7 @@ class AdbDialogFragment : DialogFragment() { private fun startAndDismiss(port: Int) { val host = "127.0.0.1" - val intent = Intent(context, StarterActivity::class.java).apply { - putExtra(StarterActivity.EXTRA_IS_ROOT, false) - putExtra(StarterActivity.EXTRA_HOST, host) - putExtra(StarterActivity.EXTRA_PORT, port) - } - requireContext().startActivity(intent) - + callStartAdb(requireContext(), host, port) dismissAllowingStateLoss() } diff --git a/manager/src/main/java/moe/shizuku/manager/home/StartWirelessAdbViewHolder.kt b/manager/src/main/java/moe/shizuku/manager/home/StartWirelessAdbViewHolder.kt index 6e6dff557..8b10fe370 100644 --- a/manager/src/main/java/moe/shizuku/manager/home/StartWirelessAdbViewHolder.kt +++ b/manager/src/main/java/moe/shizuku/manager/home/StartWirelessAdbViewHolder.kt @@ -3,7 +3,6 @@ package moe.shizuku.manager.home import android.content.Context import android.content.Intent import android.os.Build -import android.os.SystemProperties import android.text.method.LinkMovementMethod import android.view.LayoutInflater import android.view.View @@ -14,17 +13,16 @@ import androidx.fragment.app.FragmentActivity import moe.shizuku.manager.Helps import moe.shizuku.manager.R import moe.shizuku.manager.adb.AdbPairingTutorialActivity +import moe.shizuku.manager.adb.WirelessADBHelper.callStartAdb import moe.shizuku.manager.databinding.HomeItemContainerBinding import moe.shizuku.manager.databinding.HomeStartWirelessAdbBinding import moe.shizuku.manager.ktx.toHtml -import moe.shizuku.manager.starter.StarterActivity import moe.shizuku.manager.utils.CustomTabsHelper import moe.shizuku.manager.utils.EnvironmentUtils import rikka.core.content.asActivity import rikka.html.text.HtmlCompat import rikka.recyclerview.BaseViewHolder import rikka.recyclerview.BaseViewHolder.Creator -import java.net.Inet4Address class StartWirelessAdbViewHolder(binding: HomeStartWirelessAdbBinding, root: View) : BaseViewHolder(root) { @@ -73,12 +71,7 @@ class StartWirelessAdbViewHolder(binding: HomeStartWirelessAdbBinding, root: Vie val port = EnvironmentUtils.getAdbTcpPort() if (port > 0) { val host = "127.0.0.1" - val intent = Intent(context, StarterActivity::class.java).apply { - putExtra(StarterActivity.EXTRA_IS_ROOT, false) - putExtra(StarterActivity.EXTRA_HOST, host) - putExtra(StarterActivity.EXTRA_PORT, port) - } - context.startActivity(intent) + callStartAdb(context, host, port) } else { WadbNotEnabledDialogFragment().show(context.asActivity().supportFragmentManager) } diff --git a/manager/src/main/java/moe/shizuku/manager/receiver/BootCompleteReceiver.kt b/manager/src/main/java/moe/shizuku/manager/receiver/BootCompleteReceiver.kt index 879fbf9b2..ec3e4152b 100644 --- a/manager/src/main/java/moe/shizuku/manager/receiver/BootCompleteReceiver.kt +++ b/manager/src/main/java/moe/shizuku/manager/receiver/BootCompleteReceiver.kt @@ -1,22 +1,30 @@ package moe.shizuku.manager.receiver +import android.Manifest import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.os.Process import android.util.Log +import android.widget.Toast +import androidx.core.content.ContextCompat import com.topjohnwu.superuser.Shell import moe.shizuku.manager.AppConstants import moe.shizuku.manager.ShizukuSettings +import moe.shizuku.manager.ShizukuSettings.KEEP_START_ON_BOOT_WIRELESS import moe.shizuku.manager.ShizukuSettings.LaunchMethod +import moe.shizuku.manager.ShizukuSettings.getPreferences +import moe.shizuku.manager.adb.WirelessADBHelper.validateThenEnableWirelessAdb import moe.shizuku.manager.starter.Starter +import moe.shizuku.manager.starter.SelfStarterService import rikka.shizuku.Shizuku class BootCompleteReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_LOCKED_BOOT_COMPLETED != intent.action - && Intent.ACTION_BOOT_COMPLETED != intent.action) { + && Intent.ACTION_BOOT_COMPLETED != intent.action) { return } @@ -29,14 +37,38 @@ class BootCompleteReceiver : BroadcastReceiver() { Log.i(AppConstants.TAG, "service is running") return } - start(context) + start(context, false) + } else if (ShizukuSettings.getLastLaunchMode() == LaunchMethod.ADB) { + Log.i(AppConstants.TAG, "start on boot, action=" + intent.action) + if (Shizuku.pingBinder()) { + Log.i(AppConstants.TAG, "service is running") + return + } + if (intent.action == Intent.ACTION_BOOT_COMPLETED) { + val startOnBootWirelessIsEnabled = getPreferences().getBoolean(KEEP_START_ON_BOOT_WIRELESS, false) + start(context, startOnBootWirelessIsEnabled) + } else return } } - private fun start(context: Context) { + private fun start(context: Context, startOnBootWirelessIsEnabled: Boolean) { + if (!Shell.rootAccess()) { + if (startOnBootWirelessIsEnabled && ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED) { + Log.i(AppConstants.TAG, "WRITE_SECURE_SETTINGS is enabled and user has Start on boot is enabled for wireless ADB") + try { + val wirelessAdbStatus = validateThenEnableWirelessAdb(context.contentResolver, context) + if (wirelessAdbStatus) { + val intentService = Intent(context, SelfStarterService::class.java) + context.startService(intentService) + } + } catch (e: SecurityException) { + e.printStackTrace() + Toast.makeText(context, "Permission denied", Toast.LENGTH_SHORT).show() + } + } else //NotificationHelper.notify(context, AppConstants.NOTIFICATION_ID_STATUS, AppConstants.NOTIFICATION_CHANNEL_STATUS, R.string.notification_service_start_no_root) - return + return } Starter.writeDataFiles(context) diff --git a/manager/src/main/java/moe/shizuku/manager/receiver/ShizukuReceiver.kt b/manager/src/main/java/moe/shizuku/manager/receiver/ShizukuReceiver.kt index c3b9199fa..23db11da7 100644 --- a/manager/src/main/java/moe/shizuku/manager/receiver/ShizukuReceiver.kt +++ b/manager/src/main/java/moe/shizuku/manager/receiver/ShizukuReceiver.kt @@ -3,7 +3,11 @@ package moe.shizuku.manager.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import moe.shizuku.manager.ShizukuSettings +import moe.shizuku.manager.adb.WirelessADBHelper +import moe.shizuku.manager.model.ServiceStatus import moe.shizuku.manager.shell.ShellBinderRequestHandler +import moe.shizuku.manager.starter.SelfStarterService class ShizukuReceiver : BroadcastReceiver() { @@ -11,5 +15,15 @@ class ShizukuReceiver : BroadcastReceiver() { if ("rikka.shizuku.intent.action.REQUEST_BINDER" == intent.action) { ShellBinderRequestHandler.handleRequest(context, intent) } + if (!ServiceStatus().isRunning) { + val startOnBootWirelessIsEnabled = ShizukuSettings.getPreferences().getBoolean(ShizukuSettings.KEEP_START_ON_BOOT_WIRELESS, false) + if (startOnBootWirelessIsEnabled) { + val wirelessAdbStatus = WirelessADBHelper.validateThenEnableWirelessAdb(context.contentResolver, context) + if (wirelessAdbStatus) { + val intentService = Intent(context, SelfStarterService::class.java) + context.startService(intentService) + } + } + } } } diff --git a/manager/src/main/java/moe/shizuku/manager/settings/SettingsFragment.kt b/manager/src/main/java/moe/shizuku/manager/settings/SettingsFragment.kt index 2aaedfc51..30cb25677 100644 --- a/manager/src/main/java/moe/shizuku/manager/settings/SettingsFragment.kt +++ b/manager/src/main/java/moe/shizuku/manager/settings/SettingsFragment.kt @@ -1,20 +1,26 @@ package moe.shizuku.manager.settings +import android.Manifest +import android.content.pm.PackageManager +import android.widget.Toast import android.content.ComponentName import android.content.Context import android.os.Build import android.os.Bundle import android.text.TextUtils +import android.util.Log import android.util.TypedValue import android.view.LayoutInflater import android.view.ViewGroup import android.widget.FrameLayout import androidx.appcompat.app.AppCompatDelegate +import androidx.core.content.ContextCompat import androidx.preference.* import androidx.recyclerview.widget.RecyclerView import moe.shizuku.manager.R import moe.shizuku.manager.ShizukuSettings import moe.shizuku.manager.ShizukuSettings.KEEP_START_ON_BOOT +import moe.shizuku.manager.ShizukuSettings.KEEP_START_ON_BOOT_WIRELESS import moe.shizuku.manager.app.ThemeHelper import moe.shizuku.manager.app.ThemeHelper.KEY_BLACK_NIGHT_THEME import moe.shizuku.manager.app.ThemeHelper.KEY_USE_SYSTEM_COLOR @@ -39,6 +45,7 @@ class SettingsFragment : PreferenceFragmentCompat() { private lateinit var nightModePreference: IntegerSimpleMenuPreference private lateinit var blackNightThemePreference: TwoStatePreference private lateinit var startOnBootPreference: TwoStatePreference + private lateinit var startOnBootWirelessPreference: TwoStatePreference private lateinit var startupPreference: PreferenceCategory private lateinit var translationPreference: Preference private lateinit var translationContributorsPreference: Preference @@ -56,6 +63,7 @@ class SettingsFragment : PreferenceFragmentCompat() { nightModePreference = findPreference(KEY_NIGHT_MODE)!! blackNightThemePreference = findPreference(KEY_BLACK_NIGHT_THEME)!! startOnBootPreference = findPreference(KEEP_START_ON_BOOT)!! + startOnBootWirelessPreference = findPreference(KEEP_START_ON_BOOT_WIRELESS)!! startupPreference = findPreference("startup")!! translationPreference = findPreference("translation")!! translationContributorsPreference = findPreference("translation_contributors")!! @@ -63,14 +71,30 @@ class SettingsFragment : PreferenceFragmentCompat() { val componentName = ComponentName(context.packageName, BootCompleteReceiver::class.java.name) - startOnBootPreference.isChecked = context.packageManager.isComponentEnabled(componentName) startOnBootPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> if (newValue is Boolean) { + startOnBootWirelessPreference.isChecked = false context.packageManager.setComponentEnabled(componentName, newValue) context.packageManager.isComponentEnabled(componentName) == newValue } else false } + + startOnBootWirelessPreference.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener{ _: Preference?, newValue: Any -> + if (newValue is Boolean) { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { + Log.i(ShizukuSettings.NAME, "Start on Boot Wireless without granting WRITE_SECURE_SETTINGS permission") + Toast.makeText(context, "WRITE_SECURE_SETTINGS permission is not granted for Shizuku", Toast.LENGTH_SHORT).show() + startOnBootWirelessPreference.isChecked = false + return@OnPreferenceChangeListener false + } + startOnBootPreference.isChecked = false + context.packageManager.setComponentEnabled(componentName, newValue) + context.packageManager.isComponentEnabled(componentName) == newValue + } else false + } + languagePreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> if (newValue is String) { diff --git a/manager/src/main/java/moe/shizuku/manager/starter/SelfStarterService.kt b/manager/src/main/java/moe/shizuku/manager/starter/SelfStarterService.kt new file mode 100644 index 000000000..6b037b62b --- /dev/null +++ b/manager/src/main/java/moe/shizuku/manager/starter/SelfStarterService.kt @@ -0,0 +1,147 @@ +package moe.shizuku.manager.starter + +import android.app.Service +import android.content.Intent +import android.os.Build +import android.os.IBinder +import android.provider.Settings +import android.util.Log +import android.widget.Toast +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import moe.shizuku.manager.BuildConfig +import moe.shizuku.manager.ShizukuSettings +import moe.shizuku.manager.adb.AdbClient +import moe.shizuku.manager.adb.AdbKey +import moe.shizuku.manager.adb.AdbKeyException +import moe.shizuku.manager.adb.AdbMdns +import moe.shizuku.manager.adb.PreferenceAdbKeyStore +import moe.shizuku.manager.utils.EnvironmentUtils +import rikka.lifecycle.Resource + +class SelfStarterService : Service(), LifecycleOwner { + + private val sb = StringBuilder() + private lateinit var adbMdns: AdbMdns + private val port = MutableLiveData() + private val lifecycleOwner = LifecycleRegistry(this) + private val _output = MutableLiveData>() + val output = _output as LiveData> + + override fun onCreate() { + super.onCreate() + + val host = "127.0.0.1" + val startOnBootWirelessIsEnabled = ShizukuSettings.getPreferences().getBoolean(ShizukuSettings.KEEP_START_ON_BOOT_WIRELESS, false) + if (startOnBootWirelessIsEnabled && Settings.Global.getInt(this.contentResolver, "adb_wifi_enabled") == 1) { + Starter.writeSdcardFiles(applicationContext) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + adbMdns = AdbMdns(this, AdbMdns.TLS_CONNECT, port) + adbMdns.start() + + // Observe changes in the port + port.observeForever { it -> + if (it > 65535 || it < 1) + return@observeForever + try { + startAdb(host, it) + adbMdns.stop() + } catch (e: Exception) { + adbMdns.stop() + e.printStackTrace() + } + } + Toast.makeText(this, "Shizuku service has been started!", Toast.LENGTH_SHORT).show() + return + } else { + val port = EnvironmentUtils.getAdbTcpPort() + if (port > 0) { + startAdb(host, port) + } + } + } + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + + override fun getLifecycle(): Lifecycle { + return lifecycleOwner + } + + private fun postResult(throwable: Throwable? = null) { + if (throwable == null) + _output.postValue(Resource.success(sb)) + else + _output.postValue(Resource.error(throwable, sb)) + } + + private fun startAdb(host: String, port: Int) { + sb.append("Starting with wireless adb...").append('\n').append('\n') + postResult() + + GlobalScope.launch(Dispatchers.IO) { + val key = try { + AdbKey(PreferenceAdbKeyStore(ShizukuSettings.getPreferences()), "shizuku") + } catch (e: Throwable) { + e.printStackTrace() + sb.append('\n').append(Log.getStackTraceString(e)) + + postResult(AdbKeyException(e)) + return@launch + } + + AdbClient(host, port, key).runCatching { + connect() + + shellCommand(Starter.sdcardCommand) { + sb.append(String(it)) + postResult() + } + close() + }.onFailure { + it.printStackTrace() + + sb.append('\n').append(Log.getStackTraceString(it)) + postResult(it) + } + + /* Adb on MIUI Android 11 has no permission to access Android/data. + Before MIUI Android 12, we can temporarily use /data/user_de. + After that, is better to implement "adb push" and push files directly to /data/local/tmp. + */ + if (sb.contains("/Android/data/${BuildConfig.APPLICATION_ID}/start.sh: Permission denied")) { + sb.append('\n') + .appendLine("adb have no permission to access Android/data, how could this possible ?!") + .appendLine("try /data/user_de instead...") + .appendLine() + postResult() + + Starter.writeDataFiles(moe.shizuku.manager.application, true) + + AdbClient(host, port, key).runCatching { + connect() + shellCommand(Starter.dataCommand) { + sb.append(String(it)) + postResult() + } + close() + }.onFailure { + it.printStackTrace() + + sb.append('\n').append(Log.getStackTraceString(it)) + postResult(it) + } + } + } + } +} diff --git a/manager/src/main/res/values/strings.xml b/manager/src/main/res/values/strings.xml index 5e469e7d0..a7e434e4f 100644 --- a/manager/src/main/res/values/strings.xml +++ b/manager/src/main/res/values/strings.xml @@ -21,7 +21,7 @@ - Please view the step-by-step guide first.]]> + In addition, Shizuku can be started automatically on boot without root if WRITE_SECURE_SETTINGS permission is granted.

Please view the step-by-step guide first.]]> Step-by-step guide @@ -107,13 +107,16 @@ With %1$s, in any terminal app, you can connect to and interact with the shell r Appearance Black night theme Use the pure black theme if night mode is enabled - Startup + Startup (only one of them can be enabled at a time) Translation contributors Participate in translation Help us translate %s into your language Start on boot (root) For rooted devices, Shizuku is able to start automatically on boot Use system theme color + Start on boot (wireless ADB) + For rootless devices, Shizuku is able to start automatically on boot + Enable Wi-Fi to proceed About diff --git a/manager/src/main/res/xml/settings.xml b/manager/src/main/res/xml/settings.xml index 0138bd4e5..0d308184a 100644 --- a/manager/src/main/res/xml/settings.xml +++ b/manager/src/main/res/xml/settings.xml @@ -10,6 +10,11 @@ android:summary="@string/settings_start_on_boot_summary" android:title="@string/settings_start_on_boot" /> + +