From fb695cb061df9cfd43cdf339f87870a6fcad30e0 Mon Sep 17 00:00:00 2001 From: SanmerDev Date: Wed, 22 May 2024 20:36:44 +0800 Subject: [PATCH] Add `ServiceManagerCompat` - Optimize `impl` --- .../{compat/ProviderCompat.kt => Compat.kt} | 24 +-- .../mrepo/repository/ModulesRepository.kt | 6 +- .../mrepo/ui/activity/InstallActivity.kt | 8 +- .../sanmer/mrepo/ui/activity/MainActivity.kt | 4 +- .../mrepo/viewmodel/InstallViewModel.kt | 26 +-- .../sanmer/mrepo/viewmodel/ModuleViewModel.kt | 4 +- .../mrepo/viewmodel/ModulesViewModel.kt | 16 +- .../mrepo/viewmodel/SettingsViewModel.kt | 6 +- .../mrepo/compat/stub/IInstallCallback.aidl | 4 +- .../kotlin/dev/sanmer/mrepo/compat/Const.kt | 12 -- .../mrepo/compat/ServiceManagerCompat.kt | 183 ++++++++++++++++++ .../sanmer/mrepo/compat/ShizukuProvider.kt | 86 -------- .../dev/sanmer/mrepo/compat/SuProvider.kt | 77 -------- .../compat/impl/APatchModuleManagerImpl.kt | 2 +- .../compat/impl/BaseModuleManagerImpl.kt | 104 +++++----- .../compat/impl/KernelSUModuleManagerImpl.kt | 2 +- .../compat/impl/MagiskModuleManagerImpl.kt | 2 +- .../mrepo/compat/shizuku/ShizukuService.kt | 20 -- .../dev/sanmer/mrepo/compat/su/SuService.kt | 23 --- .../mrepo/compat/su/SuShellInitializer.kt | 8 - .../dev/sanmer/mrepo/compat/utils/FileExt.kt | 42 ---- 21 files changed, 285 insertions(+), 374 deletions(-) rename app/src/main/kotlin/com/sanmer/mrepo/{compat/ProviderCompat.kt => Compat.kt} (70%) delete mode 100644 compat/src/main/kotlin/dev/sanmer/mrepo/compat/Const.kt create mode 100644 compat/src/main/kotlin/dev/sanmer/mrepo/compat/ServiceManagerCompat.kt delete mode 100644 compat/src/main/kotlin/dev/sanmer/mrepo/compat/ShizukuProvider.kt delete mode 100644 compat/src/main/kotlin/dev/sanmer/mrepo/compat/SuProvider.kt delete mode 100644 compat/src/main/kotlin/dev/sanmer/mrepo/compat/shizuku/ShizukuService.kt delete mode 100644 compat/src/main/kotlin/dev/sanmer/mrepo/compat/su/SuService.kt delete mode 100644 compat/src/main/kotlin/dev/sanmer/mrepo/compat/su/SuShellInitializer.kt delete mode 100644 compat/src/main/kotlin/dev/sanmer/mrepo/compat/utils/FileExt.kt diff --git a/app/src/main/kotlin/com/sanmer/mrepo/compat/ProviderCompat.kt b/app/src/main/kotlin/com/sanmer/mrepo/Compat.kt similarity index 70% rename from app/src/main/kotlin/com/sanmer/mrepo/compat/ProviderCompat.kt rename to app/src/main/kotlin/com/sanmer/mrepo/Compat.kt index b873a3a8..8be277bd 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/compat/ProviderCompat.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/Compat.kt @@ -1,21 +1,18 @@ -package com.sanmer.mrepo.compat +package com.sanmer.mrepo import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import com.sanmer.mrepo.datastore.WorkingMode -import dev.sanmer.mrepo.compat.ShizukuProvider -import dev.sanmer.mrepo.compat.SuProvider +import dev.sanmer.mrepo.compat.ServiceManagerCompat import dev.sanmer.mrepo.compat.stub.IFileManager import dev.sanmer.mrepo.compat.stub.IModuleManager import dev.sanmer.mrepo.compat.stub.IServiceManager -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.withContext import timber.log.Timber -object ProviderCompat { +object Compat { private var mServiceOrNull: IServiceManager? = null private val mService get() = checkNotNull(mServiceOrNull) { "IServiceManager haven't been received" @@ -37,15 +34,12 @@ object ProviderCompat { return alive } - suspend fun init(mode: WorkingMode) = withContext(Dispatchers.Main) { - if (isAlive) { - return@withContext true - } - - try { + suspend fun init(mode: WorkingMode) = when { + isAlive -> true + else -> try { mServiceOrNull = when (mode) { - WorkingMode.MODE_SHIZUKU -> ShizukuProvider.launch() - WorkingMode.MODE_ROOT -> SuProvider.launch() + WorkingMode.MODE_SHIZUKU -> ServiceManagerCompat.fromShizuku() + WorkingMode.MODE_ROOT -> ServiceManagerCompat.fromLibSu() else -> null } @@ -57,7 +51,7 @@ object ProviderCompat { } } - fun get(fallback: T, block: ProviderCompat.() -> T): T { + fun get(fallback: T, block: Compat.() -> T): T { return when { isAlive -> block(this) else -> fallback diff --git a/app/src/main/kotlin/com/sanmer/mrepo/repository/ModulesRepository.kt b/app/src/main/kotlin/com/sanmer/mrepo/repository/ModulesRepository.kt index f16facb5..4f2d9127 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/repository/ModulesRepository.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/repository/ModulesRepository.kt @@ -1,6 +1,6 @@ package com.sanmer.mrepo.repository -import com.sanmer.mrepo.compat.ProviderCompat +import com.sanmer.mrepo.Compat import com.sanmer.mrepo.database.entity.Repo import com.sanmer.mrepo.network.runRequest import com.sanmer.mrepo.stub.IRepoManager @@ -15,7 +15,7 @@ class ModulesRepository @Inject constructor( private val localRepository: LocalRepository, ) { suspend fun getLocalAll() = withContext(Dispatchers.IO) { - with(ProviderCompat.moduleManager.modules) { + with(Compat.moduleManager.modules) { localRepository.deleteLocalAll() localRepository.insertLocal(this) localRepository.clearUpdatableTag(map { it.id }) @@ -23,7 +23,7 @@ class ModulesRepository @Inject constructor( } suspend fun getLocal(id: String) = withContext(Dispatchers.IO) { - val module = ProviderCompat.moduleManager.getModuleById(id) + val module = Compat.moduleManager.getModuleById(id) localRepository.insertLocal(module) } diff --git a/app/src/main/kotlin/com/sanmer/mrepo/ui/activity/InstallActivity.kt b/app/src/main/kotlin/com/sanmer/mrepo/ui/activity/InstallActivity.kt index a746ea2b..078a1460 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/ui/activity/InstallActivity.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/ui/activity/InstallActivity.kt @@ -13,7 +13,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope -import com.sanmer.mrepo.compat.ProviderCompat +import com.sanmer.mrepo.Compat import com.sanmer.mrepo.repository.UserPreferencesRepository import com.sanmer.mrepo.ui.providable.LocalUserPreferences import com.sanmer.mrepo.ui.theme.AppTheme @@ -49,11 +49,11 @@ class InstallActivity : ComponentActivity() { } LaunchedEffect(userPreferences) { - ProviderCompat.init(preferences.workingMode) + Compat.init(preferences.workingMode) } - LaunchedEffect(ProviderCompat.isAlive) { - if (ProviderCompat.isAlive) { + LaunchedEffect(Compat.isAlive) { + if (Compat.isAlive) { initModule(intent) } } diff --git a/app/src/main/kotlin/com/sanmer/mrepo/ui/activity/MainActivity.kt b/app/src/main/kotlin/com/sanmer/mrepo/ui/activity/MainActivity.kt index 39b09628..4115e6cb 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/ui/activity/MainActivity.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/ui/activity/MainActivity.kt @@ -15,8 +15,8 @@ import androidx.compose.runtime.setValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope +import com.sanmer.mrepo.Compat import com.sanmer.mrepo.app.Const -import com.sanmer.mrepo.compat.ProviderCompat import com.sanmer.mrepo.database.entity.Repo.Companion.toRepo import com.sanmer.mrepo.datastore.WorkingMode import com.sanmer.mrepo.network.NetworkUtils @@ -60,7 +60,7 @@ class MainActivity : ComponentActivity() { localRepository.insertRepo(Const.DEMO_REPO_URL.toRepo()) } - ProviderCompat.init(preferences.workingMode) + Compat.init(preferences.workingMode) NetworkUtils.setEnableDoh(preferences.useDoh) setInstallActivityEnabled(preferences.isRoot) } diff --git a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/InstallViewModel.kt b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/InstallViewModel.kt index 102e22c4..79aad958 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/InstallViewModel.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/InstallViewModel.kt @@ -8,14 +8,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.sanmer.mrepo.Compat import com.sanmer.mrepo.app.Event import com.sanmer.mrepo.compat.MediaStoreCompat.copyToDir import com.sanmer.mrepo.compat.MediaStoreCompat.getPathForUri -import com.sanmer.mrepo.compat.ProviderCompat -import com.sanmer.mrepo.repository.ModulesRepository +import com.sanmer.mrepo.model.local.LocalModule +import com.sanmer.mrepo.repository.LocalRepository import com.sanmer.mrepo.repository.UserPreferencesRepository import com.sanmer.mrepo.utils.extensions.tmpDir import dagger.hilt.android.lifecycle.HiltViewModel +import dev.sanmer.mrepo.compat.content.State import dev.sanmer.mrepo.compat.stub.IInstallCallback import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first @@ -28,7 +30,7 @@ import javax.inject.Inject @HiltViewModel class InstallViewModel @Inject constructor( - private val modulesRepository: ModulesRepository, + private val localRepository: LocalRepository, private val userPreferencesRepository: UserPreferencesRepository, ) : ViewModel() { val logs = mutableListOf() @@ -57,7 +59,7 @@ class InstallViewModel @Inject constructor( val path = context.getPathForUri(uri) Timber.d("path = $path") - ProviderCompat.moduleManager + Compat.moduleManager .getModuleInfo(path)?.let { Timber.d("module = $it") install(path) @@ -74,7 +76,7 @@ class InstallViewModel @Inject constructor( } } - ProviderCompat.moduleManager + Compat.moduleManager .getModuleInfo(tmpFile.path)?.let { Timber.d("module = $it") install(tmpFile.path) @@ -101,9 +103,9 @@ class InstallViewModel @Inject constructor( logs.add(msg) } - override fun onSuccess(id: String) { + override fun onSuccess(module: LocalModule?) { event = Event.SUCCEEDED - getLocal(id) + module?.let(::insertLocal) if (deleteZipFile) { deleteBySu(zipPath) @@ -115,18 +117,20 @@ class InstallViewModel @Inject constructor( } console.add("- Installing ${zipFile.name}") - ProviderCompat.moduleManager.install(zipPath, callback) + Compat.moduleManager.install(zipPath, callback) } - private fun getLocal(id: String) { + private fun insertLocal(module: LocalModule) { viewModelScope.launch { - modulesRepository.getLocal(id) + localRepository.insertLocal( + module.copy(state = State.UPDATE) + ) } } private fun deleteBySu(zipPath: String) { runCatching { - ProviderCompat.fileManager.deleteOnExit(zipPath) + Compat.fileManager.deleteOnExit(zipPath) }.onFailure { Timber.e(it) }.onSuccess { diff --git a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModuleViewModel.kt b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModuleViewModel.kt index b995e509..56083911 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModuleViewModel.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModuleViewModel.kt @@ -11,7 +11,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewModelScope -import com.sanmer.mrepo.compat.ProviderCompat +import com.sanmer.mrepo.Compat import com.sanmer.mrepo.database.entity.Repo import com.sanmer.mrepo.database.entity.Repo.Companion.toRepo import com.sanmer.mrepo.model.json.UpdateJson @@ -37,7 +37,7 @@ class ModuleViewModel @Inject constructor( private val userPreferencesRepository: UserPreferencesRepository, savedStateHandle: SavedStateHandle ) : ViewModel() { - val isProviderAlive get() = ProviderCompat.isAlive + val isProviderAlive get() = Compat.isAlive private val moduleId = getModuleId(savedStateHandle) var online: OnlineModule by mutableStateOf(OnlineModule.example()) diff --git a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModulesViewModel.kt b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModulesViewModel.kt index e9374592..f4d90a7f 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModulesViewModel.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModulesViewModel.kt @@ -13,7 +13,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewModelScope -import com.sanmer.mrepo.compat.ProviderCompat +import com.sanmer.mrepo.Compat import com.sanmer.mrepo.datastore.modules.ModulesMenuCompat import com.sanmer.mrepo.datastore.repository.Option import com.sanmer.mrepo.model.json.UpdateJson @@ -45,7 +45,7 @@ class ModulesViewModel @Inject constructor( private val modulesRepository: ModulesRepository, private val userPreferencesRepository: UserPreferencesRepository, ) : ViewModel() { - val isProviderAlive get() = ProviderCompat.isAlive + val isProviderAlive get() = Compat.isAlive private val modulesMenu get() = userPreferencesRepository.data .map { it.modulesMenu } @@ -86,7 +86,7 @@ class ModulesViewModel @Inject constructor( } private fun providerObserver() { - ProviderCompat.isAliveFlow + Compat.isAliveFlow .onEach { if (it) getLocalAll() @@ -182,12 +182,12 @@ class ModulesViewModel @Inject constructor( isOpsRunning = opsTasks.contains(module.id), toggle = { opsTasks.add(module.id) - ProviderCompat.moduleManager + Compat.moduleManager .disable(module.id, opsCallback) }, change = { opsTasks.add(module.id) - ProviderCompat.moduleManager + Compat.moduleManager .remove(module.id, opsCallback) } ) @@ -196,12 +196,12 @@ class ModulesViewModel @Inject constructor( isOpsRunning = opsTasks.contains(module.id), toggle = { opsTasks.add(module.id) - ProviderCompat.moduleManager + Compat.moduleManager .enable(module.id, opsCallback) }, change = { opsTasks.add(module.id) - ProviderCompat.moduleManager + Compat.moduleManager .remove(module.id, opsCallback) } ) @@ -211,7 +211,7 @@ class ModulesViewModel @Inject constructor( toggle = {}, change = { opsTasks.add(module.id) - ProviderCompat.moduleManager + Compat.moduleManager .enable(module.id, opsCallback) } ) diff --git a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/SettingsViewModel.kt b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/SettingsViewModel.kt index ef30f203..0f72a179 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/SettingsViewModel.kt @@ -2,7 +2,7 @@ package com.sanmer.mrepo.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.sanmer.mrepo.compat.ProviderCompat +import com.sanmer.mrepo.Compat import com.sanmer.mrepo.datastore.DarkMode import com.sanmer.mrepo.datastore.WorkingMode import com.sanmer.mrepo.repository.UserPreferencesRepository @@ -16,9 +16,9 @@ import javax.inject.Inject class SettingsViewModel @Inject constructor( private val userPreferencesRepository: UserPreferencesRepository ) : ViewModel() { - val isProviderAlive get() = ProviderCompat.isAlive + val isProviderAlive get() = Compat.isAlive - val version get() = ProviderCompat.get("") { + val version get() = Compat.get("") { with(moduleManager) { "$version (${versionCode})" } } diff --git a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IInstallCallback.aidl b/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IInstallCallback.aidl index 34d02cdb..a3b9d41f 100644 --- a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IInstallCallback.aidl +++ b/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IInstallCallback.aidl @@ -1,8 +1,10 @@ package dev.sanmer.mrepo.compat.stub; +import dev.sanmer.mrepo.compat.content.LocalModule; + interface IInstallCallback { void onStdout(String msg); void onStderr(String msg); - void onSuccess(String id); + void onSuccess(in LocalModule module); void onFailure(); } \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/Const.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/Const.kt deleted file mode 100644 index 9081e85e..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/Const.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.sanmer.mrepo.compat - -import dev.sanmer.mrepo.compat.delegate.ContextDelegate - -internal object Const { - const val TIMEOUT_MILLIS = 15_000L - - val PACKAGE_NAME: String by lazy { - val context = ContextDelegate.getContext() - context.packageName - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/ServiceManagerCompat.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/ServiceManagerCompat.kt new file mode 100644 index 00000000..84db97b8 --- /dev/null +++ b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/ServiceManagerCompat.kt @@ -0,0 +1,183 @@ +package dev.sanmer.mrepo.compat + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.content.pm.PackageManager +import android.os.IBinder +import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.ipc.RootService +import dev.sanmer.mrepo.compat.delegate.ContextDelegate +import dev.sanmer.mrepo.compat.impl.ServiceManagerImpl +import dev.sanmer.mrepo.compat.stub.IServiceManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import rikka.shizuku.Shizuku +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +object ServiceManagerCompat { + internal const val VERSION_CODE = 1 + + private const val TIMEOUT_MILLIS = 15_000L + + private val context by lazy { ContextDelegate.getContext() } + + interface IProvider { + val name: String + fun isAvailable(): Boolean + suspend fun isAuthorized(): Boolean + fun bind(connection: ServiceConnection) + fun unbind(connection: ServiceConnection) + } + + private suspend fun get( + provider: IProvider + ) = withTimeout(TIMEOUT_MILLIS) { + suspendCancellableCoroutine { continuation -> + val connection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, binder: IBinder) { + val service = IServiceManager.Stub.asInterface(binder) + continuation.resume(service) + } + + override fun onServiceDisconnected(name: ComponentName) { + continuation.resumeWithException( + IllegalStateException("IServiceManager destroyed") + ) + } + + override fun onBindingDied(name: ComponentName?) { + continuation.resumeWithException( + IllegalStateException("IServiceManager destroyed") + ) + } + } + + provider.bind(connection) + continuation.invokeOnCancellation { + provider.unbind(connection) + } + } + } + + suspend fun from(provider: IProvider): IServiceManager = withContext(Dispatchers.Main) { + when { + !provider.isAvailable() -> throw IllegalStateException("${provider.name} not available") + !provider.isAuthorized() -> throw IllegalStateException("${provider.name} not authorized") + else -> get(provider) + } + } + + private class ShizukuService : Shizuku.UserServiceArgs( + ComponentName( + context.packageName, + ServiceManagerImpl::class.java.name + ) + ) { + init { + daemon(false) + debuggable(false) + version(VERSION_CODE) + processNameSuffix("shizuku") + } + } + + private class ShizukuProvider : IProvider { + override val name = "Shizuku" + + override fun isAvailable() = Shizuku.pingBinder() + + override suspend fun isAuthorized() = when { + isGranted -> true + else -> suspendCancellableCoroutine { continuation -> + val listener = object : Shizuku.OnRequestPermissionResultListener { + override fun onRequestPermissionResult( + requestCode: Int, + grantResult: Int + ) { + Shizuku.removeRequestPermissionResultListener(this) + continuation.resume(isGranted) + } + } + + Shizuku.addRequestPermissionResultListener(listener) + continuation.invokeOnCancellation { + Shizuku.removeRequestPermissionResultListener(listener) + } + Shizuku.requestPermission(listener.hashCode()) + } + } + + override fun bind(connection: ServiceConnection) { + Shizuku.bindUserService(ShizukuService(), connection) + } + + override fun unbind(connection: ServiceConnection) { + Shizuku.unbindUserService(ShizukuService(), connection, true) + } + + private val isGranted get() = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED + } + + suspend fun fromShizuku() = from(ShizukuProvider()) + + private class SuService : RootService() { + override fun onBind(intent: Intent): IBinder { + return ServiceManagerImpl() + } + + companion object { + val intent get() = Intent().apply { + component = ComponentName( + context.packageName, + SuService::class.java.name + ) + } + } + } + + private class LibSuProvider : IProvider { + override val name = "LibSu" + + init { + Shell.enableVerboseLogging = true + Shell.setDefaultBuilder( + Shell.Builder.create() + .setInitializers(SuShellInitializer::class.java) + .setTimeout(10) + ) + } + + override fun isAvailable() = true + + override suspend fun isAuthorized() = suspendCancellableCoroutine { continuation -> + Shell.EXECUTOR.submit { + runCatching { + Shell.getShell() + }.onSuccess { + continuation.resume(true) + }.onFailure { + continuation.resume(false) + } + } + } + + override fun bind(connection: ServiceConnection) { + RootService.bind(SuService.intent, connection) + } + + override fun unbind(connection: ServiceConnection) { + RootService.stop(SuService.intent) + } + + private class SuShellInitializer : Shell.Initializer() { + override fun onInit(context: Context, shell: Shell) = shell.isRoot + } + } + + suspend fun fromLibSu() = from(LibSuProvider()) +} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/ShizukuProvider.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/ShizukuProvider.kt deleted file mode 100644 index f08f3871..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/ShizukuProvider.kt +++ /dev/null @@ -1,86 +0,0 @@ -package dev.sanmer.mrepo.compat - -import android.content.ComponentName -import android.content.ServiceConnection -import android.content.pm.PackageManager -import android.os.IBinder -import dev.sanmer.mrepo.compat.shizuku.ShizukuService -import dev.sanmer.mrepo.compat.stub.IServiceManager -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeout -import rikka.shizuku.Shizuku -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -object ShizukuProvider { - private val isGranted get() = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED - - private suspend fun checkPermission() = suspendCancellableCoroutine { continuation -> - if (isGranted) { - continuation.resume(true) - return@suspendCancellableCoroutine - } - - val listener = object : Shizuku.OnRequestPermissionResultListener { - override fun onRequestPermissionResult( - requestCode: Int, - grantResult: Int - ) { - Shizuku.removeRequestPermissionResultListener(this) - continuation.resume(isGranted) - } - } - - Shizuku.addRequestPermissionResultListener(listener) - continuation.invokeOnCancellation { - Shizuku.removeRequestPermissionResultListener(listener) - } - Shizuku.requestPermission(listener.hashCode()) - } - - private suspend fun getIServiceManager() = withTimeout(Const.TIMEOUT_MILLIS) { - suspendCancellableCoroutine { continuation -> - val connection = object : ServiceConnection { - override fun onServiceConnected(name: ComponentName, binder: IBinder) { - val service = IServiceManager.Stub.asInterface(binder) - continuation.resume(service) - } - - override fun onServiceDisconnected(name: ComponentName) { - continuation.resumeWithException( - IllegalStateException("IServiceManager destroyed") - ) - } - - override fun onBindingDied(name: ComponentName?) { - continuation.resumeWithException( - IllegalStateException("IServiceManager destroyed") - ) - } - } - - Shizuku.bindUserService(ShizukuService(), connection) - continuation.invokeOnCancellation { - Shizuku.unbindUserService(ShizukuService(), connection, true) - } - } - } - - @Throws(IllegalStateException::class) - suspend fun launch(): IServiceManager { - if (!Shizuku.pingBinder()) { - throw IllegalStateException("Shizuku not running") - } - - if (!checkPermission()) { - throw IllegalStateException("Shizuku not authorized") - } - - return try { - getIServiceManager() - } catch (e: TimeoutCancellationException) { - throw IllegalStateException(e) - } - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/SuProvider.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/SuProvider.kt deleted file mode 100644 index 972c5b8b..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/SuProvider.kt +++ /dev/null @@ -1,77 +0,0 @@ -package dev.sanmer.mrepo.compat - -import android.content.ComponentName -import android.content.ServiceConnection -import android.os.IBinder -import com.topjohnwu.superuser.Shell -import com.topjohnwu.superuser.ipc.RootService -import dev.sanmer.mrepo.compat.stub.IServiceManager -import dev.sanmer.mrepo.compat.su.SuService -import dev.sanmer.mrepo.compat.su.SuShellInitializer -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeout -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -object SuProvider { - init { - Shell.enableVerboseLogging = true - Shell.setDefaultBuilder( - Shell.Builder.create() - .setInitializers(SuShellInitializer::class.java) - .setTimeout(10) - ) - } - - private suspend fun checkPermission() = suspendCancellableCoroutine { continuation -> - Shell.EXECUTOR.submit { - runCatching { - Shell.getShell() - }.onSuccess { - continuation.resume(Unit) - }.onFailure { - continuation.resumeWithException(it) - } - } - } - - private suspend fun getIServiceManager() = withTimeout(Const.TIMEOUT_MILLIS) { - suspendCancellableCoroutine { continuation -> - val connection = object : ServiceConnection { - override fun onServiceConnected(name: ComponentName, binder: IBinder) { - val service = IServiceManager.Stub.asInterface(binder) - continuation.resume(service) - } - - override fun onServiceDisconnected(name: ComponentName) { - continuation.resumeWithException( - IllegalStateException("IServiceManager destroyed") - ) - } - - override fun onBindingDied(name: ComponentName?) { - continuation.resumeWithException( - IllegalStateException("IServiceManager destroyed") - ) - } - } - - RootService.bind(SuService.intent, connection) - continuation.invokeOnCancellation { - RootService.stop(SuService.intent) - } - } - } - - @Throws(IllegalStateException::class) - suspend fun launch(): IServiceManager { - checkPermission() - - return try { - getIServiceManager() - } catch (e: TimeoutCancellationException) { - throw IllegalStateException(e) - } - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/APatchModuleManagerImpl.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/APatchModuleManagerImpl.kt index a9aa8055..bbaf35d8 100644 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/APatchModuleManagerImpl.kt +++ b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/APatchModuleManagerImpl.kt @@ -65,7 +65,7 @@ internal class APatchModuleManagerImpl( val result = shell.newJob().add(cmd).to(stdout, stderr).exec() if (result.isSuccess) { val module = getModuleInfo(path) - callback.onSuccess(module?.id ?: "unknown") + callback.onSuccess(module) } else { callback.onFailure() } diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/BaseModuleManagerImpl.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/BaseModuleManagerImpl.kt index d0d481f6..c2e9fb2d 100644 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/BaseModuleManagerImpl.kt +++ b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/BaseModuleManagerImpl.kt @@ -5,43 +5,39 @@ import com.topjohnwu.superuser.ShellUtils import dev.sanmer.mrepo.compat.content.LocalModule import dev.sanmer.mrepo.compat.content.State import dev.sanmer.mrepo.compat.stub.IModuleManager -import dev.sanmer.mrepo.compat.utils.unzip import java.io.File +import java.util.zip.ZipFile internal abstract class BaseModuleManagerImpl( private val shell: Shell ) : IModuleManager.Stub() { internal val modulesDir = File(MODULES_PATH) - private val tmpDir = File(TMP_PATH).apply { - if (!exists()) mkdirs() + + private val mVersion by lazy { + runCatching { + "su -v".exec() + }.getOrDefault("unknown") } - private var _version: String = "unknown" - private var _versionCode: Int = -1 + private val mVersionCode by lazy { + runCatching { + "su -V".exec().toInt() + }.getOrDefault(-1) + } override fun getVersion(): String { - if (_version == "unknown") { - _version = runCatching { "su -v".exec() } - .getOrDefault("unknown") - } - - return _version + return mVersion } override fun getVersionCode(): Int { - if (_versionCode == -1) { - _versionCode = runCatching { "su -V".exec().toInt() } - .getOrDefault(-1) - } - - return _versionCode + return mVersionCode } override fun getModules() = modulesDir.listFiles().orEmpty() .mapNotNull { moduleDir -> runCatching { readProps(moduleDir) - .toModule( + ?.toModule( state = readState(moduleDir), lastUpdated = readLastUpdated(moduleDir) ) @@ -51,38 +47,42 @@ internal abstract class BaseModuleManagerImpl( override fun getModuleById(id: String) = runCatching { val moduleDir = modulesDir.resolve(id) readProps(moduleDir) - .toModule( + ?.toModule( state = readState(moduleDir), lastUpdated = readLastUpdated(moduleDir) ) }.getOrNull() override fun getModuleInfo(zipPath: String) = runCatching { - val zipFile = File(zipPath) - val mDir = tmpDir.resolve("mrepo") - zipFile.unzip(mDir, PROP_FILE, true) + val zipFile = ZipFile(zipPath) + val entry = zipFile.getEntry(PROP_FILE) ?: return@runCatching null + + zipFile.getInputStream(entry).use { + it.bufferedReader() + .readText() + .let(::readProps) - val module = readProps(mDir).toModule() - mDir.deleteRecursively() + }.toModule() - module }.getOrNull() - private fun readProps(moduleDir: File): Map? { - return moduleDir.resolve(PROP_FILE) - .apply { - if (!exists()) return null + private fun readProps(props: String) = props.lines() + .associate { line -> + val items = line.split("=", limit = 2).map { it.trim() } + if (items.size != 2) { + "" to "" + } else { + items[0] to items[1] } - .readText().lines() - .associate { line -> - val items = line.split("=", limit = 2).map { it.trim() } - if (items.size != 2) { - "" to "" - } else { - items[0] to items[1] - } + } + + private fun readProps(moduleDir: File) = + moduleDir.resolve(PROP_FILE).let { + when { + it.exists() -> readProps(it.readText()) + else -> null } - } + } private fun readState(moduleDir: File): State { moduleDir.resolve("remove").apply { @@ -111,23 +111,20 @@ internal abstract class BaseModuleManagerImpl( return 0L } - private fun Map?.toModule( + private fun Map.toModule( state: State = State.ENABLE, lastUpdated: Long = 0L - ): LocalModule? { - if (this == null) return null - return LocalModule( - id = getOrDefault("id", "unknown"), - name = getOrDefault("name", "unknown"), - version = getOrDefault("version", ""), - versionCode = getOrDefault("versionCode", "-1").toInt(), - author = getOrDefault("author", ""), - description = getOrDefault("description", ""), - updateJson = getOrDefault("updateJson", ""), - state = state, - lastUpdated = lastUpdated - ) - } + ) = LocalModule( + id = getOrDefault("id", "unknown"), + name = getOrDefault("name", "unknown"), + version = getOrDefault("version", ""), + versionCode = getOrDefault("versionCode", "-1").toInt(), + author = getOrDefault("author", ""), + description = getOrDefault("description", ""), + updateJson = getOrDefault("updateJson", ""), + state = state, + lastUpdated = lastUpdated + ) private fun String.exec() = ShellUtils.fastCmd(shell, this) @@ -138,7 +135,6 @@ internal abstract class BaseModuleManagerImpl( companion object { const val PROP_FILE = "module.prop" const val MODULES_PATH = "/data/adb/modules" - const val TMP_PATH = "/data/local/tmp" val MODULE_FILES = listOf( "post-fs-data.sh", "service.sh", "uninstall.sh", diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/KernelSUModuleManagerImpl.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/KernelSUModuleManagerImpl.kt index 660f7c61..1a1a77b1 100644 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/KernelSUModuleManagerImpl.kt +++ b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/KernelSUModuleManagerImpl.kt @@ -65,7 +65,7 @@ internal class KernelSUModuleManagerImpl( val result = shell.newJob().add(cmd).to(stdout, stderr).exec() if (result.isSuccess) { val module = getModuleInfo(path) - callback.onSuccess(module?.id ?: "unknown") + callback.onSuccess(module) } else { callback.onFailure() } diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/MagiskModuleManagerImpl.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/MagiskModuleManagerImpl.kt index 628587d7..70b6457e 100644 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/MagiskModuleManagerImpl.kt +++ b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/MagiskModuleManagerImpl.kt @@ -68,7 +68,7 @@ internal class MagiskModuleManagerImpl( val result = shell.newJob().add(cmd).to(stdout, stderr).exec() if (result.isSuccess) { val module = getModuleInfo(path) - callback.onSuccess(module?.id ?: "unknown") + callback.onSuccess(module) } else { callback.onFailure() } diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/shizuku/ShizukuService.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/shizuku/ShizukuService.kt deleted file mode 100644 index fb3c4e5a..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/shizuku/ShizukuService.kt +++ /dev/null @@ -1,20 +0,0 @@ -package dev.sanmer.mrepo.compat.shizuku - -import android.content.ComponentName -import dev.sanmer.mrepo.compat.Const -import dev.sanmer.mrepo.compat.impl.ServiceManagerImpl -import rikka.shizuku.Shizuku - -internal class ShizukuService : Shizuku.UserServiceArgs( - ComponentName( - Const.PACKAGE_NAME, - ServiceManagerImpl::class.java.name - ) -) { - init { - daemon(false) - debuggable(false) - version(1) - processNameSuffix("shizuku") - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/su/SuService.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/su/SuService.kt deleted file mode 100644 index e5faa282..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/su/SuService.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.sanmer.mrepo.compat.su - -import android.content.ComponentName -import android.content.Intent -import android.os.IBinder -import com.topjohnwu.superuser.ipc.RootService -import dev.sanmer.mrepo.compat.Const -import dev.sanmer.mrepo.compat.impl.ServiceManagerImpl - -internal class SuService : RootService() { - override fun onBind(intent: Intent): IBinder { - return ServiceManagerImpl() - } - - companion object { - val intent get() = Intent().apply { - component = ComponentName( - Const.PACKAGE_NAME, - SuService::class.java.name - ) - } - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/su/SuShellInitializer.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/su/SuShellInitializer.kt deleted file mode 100644 index 692b0020..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/su/SuShellInitializer.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.sanmer.mrepo.compat.su - -import android.content.Context -import com.topjohnwu.superuser.Shell - -internal class SuShellInitializer : Shell.Initializer() { - override fun onInit(context: Context, shell: Shell) = shell.isRoot -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/utils/FileExt.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/utils/FileExt.kt deleted file mode 100644 index 6efb4468..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/utils/FileExt.kt +++ /dev/null @@ -1,42 +0,0 @@ -package dev.sanmer.mrepo.compat.utils - -import java.io.File -import java.io.IOException -import java.io.InputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream - -@Throws(IOException::class) -internal fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) { - inputStream().buffered().use { - it.unzip(folder, path, junkPath) - } -} - -@Throws(IOException::class) -internal fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) { - try { - val zin = ZipInputStream(this) - var entry: ZipEntry - while (true) { - entry = zin.nextEntry ?: break - if (!entry.name.startsWith(path) || entry.isDirectory) { - // Ignore directories, only create files - continue - } - val name = if (junkPath) - entry.name.substring(entry.name.lastIndexOf('/') + 1) - else - entry.name - - val dest = File(folder, name) - dest.parentFile!!.let { - if (!it.exists()) - it.mkdirs() - } - dest.outputStream().use { out -> zin.copyTo(out) } - } - } catch (e: IllegalArgumentException) { - throw IOException(e) - } -} \ No newline at end of file