Skip to content

Commit

Permalink
feat(service-auto-start): add auto-start support for rootless devices
Browse files Browse the repository at this point in the history
This feature is tested working on GrapheneOS Android 14, Bluejay
  • Loading branch information
pixincreate committed Apr 8, 2024
1 parent 9792354 commit 06ca7a6
Show file tree
Hide file tree
Showing 12 changed files with 316 additions and 35 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
10 changes: 10 additions & 0 deletions manager/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
<uses-permission android:name="moe.shizuku.manager.permission.MANAGER" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission
android:name="android.permission.WRITE_SECURE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="moe.shizuku.manager.permission.API_V23"
tools:node="remove" />
Expand Down Expand Up @@ -130,6 +135,11 @@
android:exported="false"
android:foregroundServiceType="connectedDevice" />

<service
android:name=".starter.SelfStarterService"
android:enabled="true"
android:exported="false" />

<receiver
android:name=".receiver.BootCompleteReceiver"
android:directBootAware="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.os.Build;
import android.text.TextUtils;

import androidx.annotation.IntDef;
Expand All @@ -25,6 +24,7 @@ public class ShizukuSettings {
public static final String NIGHT_MODE = "night_mode";
public static final String LANGUAGE = "language";
public static final String KEEP_START_ON_BOOT = "start_on_boot";
public static final String KEEP_START_ON_BOOT_WIRELESS = "start_on_boot_wireless";

private static SharedPreferences sPreferences;

Expand All @@ -35,11 +35,7 @@ public static SharedPreferences getPreferences() {
@NonNull
private static Context getSettingsStorageContext(@NonNull Context context) {
Context storageContext;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
storageContext = context.createDeviceProtectedStorageContext();
} else {
storageContext = context;
}
storageContext = context.createDeviceProtectedStorageContext();

storageContext = new ContextWrapper(storageContext) {
@Override
Expand Down
46 changes: 46 additions & 0 deletions manager/src/main/java/moe/shizuku/manager/adb/AdbWirelessHelper.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}

Expand All @@ -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()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Any?>(root) {
Expand Down Expand Up @@ -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<FragmentActivity>().supportFragmentManager)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,27 @@ 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() {

override fun onReceive(context: Context, intent: Intent) {
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)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -56,21 +63,38 @@ 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")!!
useSystemColorPreference = findPreference(KEY_USE_SYSTEM_COLOR)!!

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) {
Expand Down
Loading

0 comments on commit 06ca7a6

Please sign in to comment.