diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 3f53272..44cb3a2 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -144,4 +144,10 @@ dependencies {
// Markdown to HTML
implementation(libs.markdown)
+
+ // Shizuku
+ implementation("dev.rikka.shizuku:api:13.1.2")
+ implementation("dev.rikka.shizuku:provider:13.1.2")
+ implementation("dev.rikka.tools.refine:runtime:4.3.0")
+ compileOnly("dev.rikka.hidden:stub:4.2.0")
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index edb1188..998b096 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -60,5 +60,21 @@
android:value="androidx.startup"
tools:node="remove" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/manager/ManagerApplication.kt b/app/src/main/java/app/revanced/manager/ManagerApplication.kt
index 07856a5..a5ca266 100644
--- a/app/src/main/java/app/revanced/manager/ManagerApplication.kt
+++ b/app/src/main/java/app/revanced/manager/ManagerApplication.kt
@@ -8,10 +8,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
+import app.revanced.manager.service.ShizukuApi
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.androidx.workmanager.koin.workManagerFactory
import org.koin.core.context.startKoin
+lateinit var rvmApp: ManagerApplication
class ManagerApplication : Application() {
private val scope = MainScope()
@@ -20,6 +22,9 @@ class ManagerApplication : Application() {
override fun onCreate() {
super.onCreate()
+ rvmApp = this
+ ShizukuApi.init()
+
startKoin {
androidContext(this@ManagerApplication)
androidLogger()
diff --git a/app/src/main/java/app/revanced/manager/service/ShizkuService.kt b/app/src/main/java/app/revanced/manager/service/ShizkuService.kt
new file mode 100644
index 0000000..e9214ce
--- /dev/null
+++ b/app/src/main/java/app/revanced/manager/service/ShizkuService.kt
@@ -0,0 +1,74 @@
+package app.revanced.manager.service
+
+import android.content.Intent
+import android.content.pm.IPackageManager
+import android.content.pm.IPackageInstaller
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstallerHidden
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.IBinder
+import android.os.IInterface
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.core.content.FileProvider
+import app.revanced.manager.rvmApp
+import dev.rikka.tools.refine.Refine
+import rikka.shizuku.Shizuku
+import rikka.shizuku.ShizukuBinderWrapper
+import rikka.shizuku.SystemServiceHelper
+import java.io.File
+
+object ShizukuApi {
+
+ private fun IBinder.wrap() = ShizukuBinderWrapper(this)
+ private fun IInterface.asShizukuBinder() = this.asBinder().wrap()
+
+ private val iPackageManager: IPackageManager by lazy {
+ IPackageManager.Stub.asInterface(SystemServiceHelper.getSystemService("package").wrap())
+ }
+
+ private val iPackageInstaller: IPackageInstaller by lazy {
+ IPackageInstaller.Stub.asInterface(iPackageManager.packageInstaller.asShizukuBinder())
+ }
+
+ private val packageInstaller: PackageInstaller by lazy {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ Refine.unsafeCast(
+ PackageInstallerHidden(
+ iPackageInstaller,
+ "com.android.shell",
+ null,
+ 0
+ )
+ )
+ } else {
+ Refine.unsafeCast(PackageInstallerHidden(iPackageInstaller, "com.android.shell", 0))
+ }
+ }
+
+ var isBinderAvailable = false
+ var isPermissionGranted by mutableStateOf(false)
+
+ fun init() {
+ Shizuku.addBinderReceivedListenerSticky {
+ isBinderAvailable = true
+ isPermissionGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
+ }
+ Shizuku.addBinderDeadListener {
+ isBinderAvailable = false
+ isPermissionGranted = false
+ }
+ }
+
+ fun installPackage(file: File) {
+ val intent = Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(rvmApp, "app.revanced.manager.provider", file)
+ ).apply {
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ rvmApp.startActivity(intent)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/manager/ui/component/ShizukuCard.kt b/app/src/main/java/app/revanced/manager/ui/component/ShizukuCard.kt
new file mode 100644
index 0000000..6ab0179
--- /dev/null
+++ b/app/src/main/java/app/revanced/manager/ui/component/ShizukuCard.kt
@@ -0,0 +1,94 @@
+package app.revanced.manager.ui.component
+
+import android.content.pm.PackageManager
+import android.util.Log
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Warning
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import app.revanced.manager.R
+import app.revanced.manager.service.ShizukuApi
+import rikka.shizuku.Shizuku
+
+private val listener: (Int, Int) -> Unit = { _, grantResult ->
+ ShizukuApi.isPermissionGranted = grantResult == PackageManager.PERMISSION_GRANTED
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ShizukuCard() {
+ LaunchedEffect(Unit) {
+ Shizuku.addRequestPermissionResultListener(listener)
+ }
+ DisposableEffect(Unit) {
+ onDispose {
+ Shizuku.removeRequestPermissionResultListener(listener)
+ }
+ }
+
+ AnimatedVisibility(visible = !ShizukuApi.isPermissionGranted) {
+ Card(
+ colors = CardDefaults.cardColors(
+ MaterialTheme.colorScheme.errorContainer
+ ),
+ onClick = {
+ if (ShizukuApi.isBinderAvailable && !ShizukuApi.isPermissionGranted) {
+ Log.e("ShizukuCard", "Requesting permission")
+ Shizuku.requestPermission(114514)
+ }
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ .clip(RoundedCornerShape(24.dp))
+ .background(MaterialTheme.colorScheme.tertiaryContainer),
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Warning,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onErrorContainer,
+ modifier = Modifier.size(24.dp)
+ )
+ Column() {
+ Text(
+ text = stringResource(R.string.shizuku_unavailable),
+ style = MaterialTheme.typography.titleMedium
+ )
+ Text(
+ text = stringResource(R.string.home_shizuku_warning),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onErrorContainer
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt
index d43a3c1..2f1d6a2 100644
--- a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt
+++ b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt
@@ -39,6 +39,7 @@ import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.bundle.ImportBundleDialog
import app.revanced.manager.ui.viewmodel.DashboardViewModel
import app.revanced.manager.util.toast
+import app.revanced.manager.ui.component.ShizukuCard
import kotlinx.coroutines.launch
import org.koin.androidx.compose.getViewModel
@@ -141,7 +142,7 @@ fun DashboardScreen(
)
}
}
-
+ ShizukuCard()
HorizontalPager(
pageCount = pages.size,
state = pagerState,
diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstallerViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstallerViewModel.kt
index c98128e..cd5e057 100644
--- a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstallerViewModel.kt
+++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstallerViewModel.kt
@@ -32,6 +32,7 @@ import app.revanced.manager.util.tag
import app.revanced.manager.util.toast
import app.revanced.patcher.logging.Logger
import kotlinx.collections.immutable.ImmutableList
+import app.revanced.manager.service.ShizukuApi
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -123,7 +124,7 @@ class InstallerViewModel(input: Destination.Installer) : ViewModel(), KoinCompon
}
}
}
-
+
init {
app.registerReceiver(installBroadcastReceiver, IntentFilter().apply {
addAction(InstallService.APP_INSTALL_ACTION)
@@ -188,7 +189,9 @@ class InstallerViewModel(input: Destination.Installer) : ViewModel(), KoinCompon
isInstalling = true
try {
if (!signApk()) return@launch
- pm.installApp(listOf(signedFile))
+//
+// pm.installApp(listOf(signedFile))
+ ShizukuApi.installPackage(signedFile)
} finally {
isInstalling = false
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index eaf954d..263d3a3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -230,4 +230,7 @@
Save
Update
Tap on Update when prompted. \n ReVanced Manager will close when updating.
+ Shizuku service available
+ Shizuku service not connected
+ Some functions unavailable
\ No newline at end of file
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000..d157705
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file