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