diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 073b06ec..b6c06fd0 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -14,6 +14,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: + submodules: 'recursive' fetch-depth: 0 - name: Set up signing key @@ -39,6 +40,10 @@ jobs: validate-wrappers: true gradle-home-cache-cleanup: true + - name: Build dependencies + working-directory: compat + run: ./gradlew publishToMavenLocal + - name: Build with Gradle run: ./gradlew assembleRelease diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..d49a8cee --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "compat"] + path = compat + url = git@github.com:SanmerApps/ServiceManagerCompat.git diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9687bf2d..9417d645 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -113,8 +113,8 @@ protobuf { } dependencies { - compileOnly(projects.hiddenApi) - implementation(projects.compat) + implementation(projects.core) + implementation(libs.sanmer.su) implementation(libs.androidx.activity.compose) implementation(libs.androidx.appcompat) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 2754634f..4fd07173 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,14 +1,4 @@ --verbose --dontpreverify --optimizationpasses 5 --dontskipnonpubliclibraryclasses - --dontwarn org.conscrypt.** --dontwarn kotlinx.serialization.** +-repackageclasses dev.sanmer.mrepo # Keep DataStore fields --keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite* { - ; -} - --repackageclasses com.sanmer.mrepo \ No newline at end of file +-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite* { ; } \ No newline at end of file diff --git a/app/src/main/kotlin/com/sanmer/mrepo/App.kt b/app/src/main/kotlin/com/sanmer/mrepo/App.kt index 51c4cd5b..40afcd19 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/App.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/App.kt @@ -6,7 +6,6 @@ import com.sanmer.mrepo.network.NetworkUtils import com.sanmer.mrepo.utils.timber.DebugTree import com.sanmer.mrepo.utils.timber.ReleaseTree import dagger.hilt.android.HiltAndroidApp -import dev.sanmer.mrepo.compat.ServiceManagerCompat import timber.log.Timber @HiltAndroidApp @@ -22,7 +21,6 @@ class App : Application() { override fun onCreate() { super.onCreate() - ServiceManagerCompat.setHiddenApiExemptions() NotificationUtils.init(this) NetworkUtils.setCacheDir(cacheDir) } diff --git a/app/src/main/kotlin/com/sanmer/mrepo/Compat.kt b/app/src/main/kotlin/com/sanmer/mrepo/Compat.kt index 1ccd2556..efad73d2 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/Compat.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/Compat.kt @@ -4,11 +4,11 @@ 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.ServiceManagerCompat -import dev.sanmer.mrepo.compat.stub.IFileManager -import dev.sanmer.mrepo.compat.stub.IModuleManager -import dev.sanmer.mrepo.compat.stub.IPowerManager -import dev.sanmer.mrepo.compat.stub.IServiceManager +import dev.sanmer.mrepo.ModuleManager +import dev.sanmer.mrepo.stub.IModuleManager +import dev.sanmer.su.IServiceManager +import dev.sanmer.su.ServiceManagerCompat +import dev.sanmer.su.ServiceManagerCompat.createBy import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import timber.log.Timber @@ -25,9 +25,11 @@ object Compat { private val _isAliveFlow = MutableStateFlow(false) val isAliveFlow get() = _isAliveFlow.asStateFlow() - val moduleManager: IModuleManager get() = mService.moduleManager - val fileManager: IFileManager get() = mService.fileManager - val powerManager: IPowerManager get() = mService.powerManager + val moduleManager: IModuleManager by lazy { + IModuleManager.Stub.asInterface( + ModuleManager::class.createBy(mService) + ) + } private fun state(): Boolean { isAlive = mServiceOrNull != null diff --git a/app/src/main/kotlin/com/sanmer/mrepo/model/local/LocalModule.kt b/app/src/main/kotlin/com/sanmer/mrepo/model/local/LocalModule.kt index d9cf9924..b1ad02c5 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/model/local/LocalModule.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/model/local/LocalModule.kt @@ -1,21 +1,8 @@ package com.sanmer.mrepo.model.local import com.sanmer.mrepo.utils.Utils -import dev.sanmer.mrepo.compat.content.LocalModule +import dev.sanmer.mrepo.content.Module -typealias LocalModule = LocalModule +typealias LocalModule = Module -val LocalModule.versionDisplay get() = Utils.getVersionDisplay(version, versionCode) - -fun LocalModule.Companion.example() = - LocalModule( - id = "local_example", - name = "Example", - version = "2022.08.16", - versionCode = 1703, - author = "Sanmer", - description = "This is an example!", - updateJson = "", - state = State.ENABLE, - lastUpdated = 0L - ) \ No newline at end of file +val Module.versionDisplay get() = Utils.getVersionDisplay(version, versionCode) \ No newline at end of file diff --git a/app/src/main/kotlin/com/sanmer/mrepo/model/local/State.kt b/app/src/main/kotlin/com/sanmer/mrepo/model/local/State.kt index ae0f7903..a828025e 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/model/local/State.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/model/local/State.kt @@ -1,5 +1,5 @@ package com.sanmer.mrepo.model.local -import dev.sanmer.mrepo.compat.content.State +import dev.sanmer.mrepo.content.State typealias State = State \ No newline at end of file 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 4f2d9127..0e4cc0b1 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/repository/ModulesRepository.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/repository/ModulesRepository.kt @@ -14,8 +14,10 @@ import javax.inject.Singleton class ModulesRepository @Inject constructor( private val localRepository: LocalRepository, ) { + private val mm get() = Compat.moduleManager + suspend fun getLocalAll() = withContext(Dispatchers.IO) { - with(Compat.moduleManager.modules) { + with(mm.modules) { localRepository.deleteLocalAll() localRepository.insertLocal(this) localRepository.clearUpdatableTag(map { it.id }) @@ -23,7 +25,7 @@ class ModulesRepository @Inject constructor( } suspend fun getLocal(id: String) = withContext(Dispatchers.IO) { - val module = Compat.moduleManager.getModuleById(id) + val module = mm.getModuleById(id) localRepository.insertLocal(module) } 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 98739eb8..4eb4d213 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/InstallViewModel.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/InstallViewModel.kt @@ -17,9 +17,8 @@ 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.delegate.PowerManagerDelegate -import dev.sanmer.mrepo.compat.stub.IInstallCallback +import dev.sanmer.mrepo.content.State +import dev.sanmer.mrepo.stub.IInstallCallback import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -34,6 +33,8 @@ class InstallViewModel @Inject constructor( private val localRepository: LocalRepository, private val userPreferencesRepository: UserPreferencesRepository, ) : ViewModel() { + private val mm get() = Compat.moduleManager + val logs = mutableListOf() val console = mutableStateListOf() var event by mutableStateOf(Event.LOADING) @@ -45,11 +46,7 @@ class InstallViewModel @Inject constructor( Timber.d("InstallViewModel init") } - fun reboot() { - PowerManagerDelegate(Compat.powerManager).apply { - reboot() - } - } + fun reboot() = mm.reboot() suspend fun writeLogsTo(context: Context, uri: Uri) = withContext(Dispatchers.IO) { runCatching { @@ -74,13 +71,12 @@ class InstallViewModel @Inject constructor( val path = context.getPathForUri(uri) Timber.d("path = $path") - Compat.moduleManager - .getModuleInfo(path)?.let { - Timber.d("module = $it") - install(path) + mm.getModuleInfo(path)?.let { + Timber.d("module = $it") + install(path) - return@withContext - } + return@withContext + } console.add("- Copying zip to temp directory") val tmpFile = context.copyToDir(uri, context.tmpDir) @@ -91,13 +87,12 @@ class InstallViewModel @Inject constructor( } } - Compat.moduleManager - .getModuleInfo(tmpFile.path)?.let { - Timber.d("module = $it") - install(tmpFile.path) + mm.getModuleInfo(tmpFile.path)?.let { + Timber.d("module = $it") + install(tmpFile.path) - return@withContext - } + return@withContext + } event = Event.FAILED console.add("- Zip parsing failed") @@ -132,7 +127,7 @@ class InstallViewModel @Inject constructor( } console.add("- Installing ${zipFile.name}") - Compat.moduleManager.install(zipPath, callback) + mm.install(zipPath, callback) } private fun insertLocal(module: LocalModule) { @@ -145,7 +140,7 @@ class InstallViewModel @Inject constructor( private fun deleteBySu(zipPath: String) { runCatching { - Compat.fileManager.deleteOnExit(zipPath) + mm.deleteOnExit(zipPath) }.onFailure { Timber.e(it) }.onSuccess { 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 f4d90a7f..ea4902f2 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModulesViewModel.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/ModulesViewModel.kt @@ -26,7 +26,7 @@ import com.sanmer.mrepo.repository.UserPreferencesRepository import com.sanmer.mrepo.service.DownloadService import com.sanmer.mrepo.utils.Utils import dagger.hilt.android.lifecycle.HiltViewModel -import dev.sanmer.mrepo.compat.stub.IModuleOpsCallback +import dev.sanmer.mrepo.stub.IModuleOpsCallback import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -45,6 +45,7 @@ class ModulesViewModel @Inject constructor( private val modulesRepository: ModulesRepository, private val userPreferencesRepository: UserPreferencesRepository, ) : ViewModel() { + private val mm get() = Compat.moduleManager val isProviderAlive get() = Compat.isAlive private val modulesMenu get() = userPreferencesRepository.data @@ -182,13 +183,11 @@ class ModulesViewModel @Inject constructor( isOpsRunning = opsTasks.contains(module.id), toggle = { opsTasks.add(module.id) - Compat.moduleManager - .disable(module.id, opsCallback) + mm.disable(module.id, opsCallback) }, change = { opsTasks.add(module.id) - Compat.moduleManager - .remove(module.id, opsCallback) + mm.remove(module.id, opsCallback) } ) @@ -196,13 +195,11 @@ class ModulesViewModel @Inject constructor( isOpsRunning = opsTasks.contains(module.id), toggle = { opsTasks.add(module.id) - Compat.moduleManager - .enable(module.id, opsCallback) + mm.enable(module.id, opsCallback) }, change = { opsTasks.add(module.id) - Compat.moduleManager - .remove(module.id, opsCallback) + mm.remove(module.id, opsCallback) } ) @@ -211,8 +208,7 @@ class ModulesViewModel @Inject constructor( toggle = {}, change = { opsTasks.add(module.id) - Compat.moduleManager - .enable(module.id, opsCallback) + mm.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 0f72a179..31849766 100644 --- a/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/sanmer/mrepo/viewmodel/SettingsViewModel.kt @@ -16,10 +16,11 @@ import javax.inject.Inject class SettingsViewModel @Inject constructor( private val userPreferencesRepository: UserPreferencesRepository ) : ViewModel() { + private val mm get() = Compat.moduleManager val isProviderAlive get() = Compat.isAlive val version get() = Compat.get("") { - with(moduleManager) { "$version (${versionCode})" } + with(mm) { "$version (${versionCode})" } } init { diff --git a/compat b/compat new file mode 160000 index 00000000..e5812397 --- /dev/null +++ b/compat @@ -0,0 +1 @@ +Subproject commit e58123973e86612200b8cd46bda3556794f5c190 diff --git a/compat/build.gradle.kts b/compat/build.gradle.kts deleted file mode 100644 index f4e16784..00000000 --- a/compat/build.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - alias(libs.plugins.self.library) - alias(libs.plugins.kotlin.parcelize) - alias(libs.plugins.rikka.refine) -} - -android { - namespace = "dev.sanmer.mrepo.compat" - - buildFeatures { - aidl = true - } -} - -dependencies { - compileOnly(projects.hiddenApi) - implementation(libs.hiddenApiBypass) - implementation(libs.rikka.refine.runtime) - - implementation(libs.libsu.core) - implementation(libs.libsu.service) - - implementation(libs.rikka.shizuku.api) - implementation(libs.rikka.shizuku.provider) - - implementation(libs.androidx.annotation) - implementation(libs.kotlinx.coroutines.android) -} \ No newline at end of file diff --git a/compat/src/main/AndroidManifest.xml b/compat/src/main/AndroidManifest.xml deleted file mode 100644 index 1ac33631..00000000 --- a/compat/src/main/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/compat/src/main/aidl/dev/sanmer/mrepo/compat/content/LocalModule.aidl b/compat/src/main/aidl/dev/sanmer/mrepo/compat/content/LocalModule.aidl deleted file mode 100644 index f66d8567..00000000 --- a/compat/src/main/aidl/dev/sanmer/mrepo/compat/content/LocalModule.aidl +++ /dev/null @@ -1,3 +0,0 @@ -package dev.sanmer.mrepo.compat.content; - -parcelable LocalModule; \ No newline at end of file diff --git a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IFileManager.aidl b/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IFileManager.aidl deleted file mode 100644 index 81eea196..00000000 --- a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IFileManager.aidl +++ /dev/null @@ -1,5 +0,0 @@ -package dev.sanmer.mrepo.compat.stub; - -interface IFileManager { - boolean deleteOnExit(String path); -} \ No newline at end of file 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 deleted file mode 100644 index a3b9d41f..00000000 --- a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IInstallCallback.aidl +++ /dev/null @@ -1,10 +0,0 @@ -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(in LocalModule module); - void onFailure(); -} \ No newline at end of file diff --git a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IModuleManager.aidl b/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IModuleManager.aidl deleted file mode 100644 index df746506..00000000 --- a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IModuleManager.aidl +++ /dev/null @@ -1,17 +0,0 @@ -package dev.sanmer.mrepo.compat.stub; - -import dev.sanmer.mrepo.compat.content.LocalModule; -import dev.sanmer.mrepo.compat.stub.IInstallCallback; -import dev.sanmer.mrepo.compat.stub.IModuleOpsCallback; - -interface IModuleManager { - String getVersion(); - int getVersionCode(); - List getModules(); - LocalModule getModuleById(String id); - LocalModule getModuleInfo(String zipPath); - oneway void enable(String id, IModuleOpsCallback callback); - oneway void disable(String id, IModuleOpsCallback callback); - oneway void remove(String id, IModuleOpsCallback callback); - oneway void install(String path, IInstallCallback callback); -} \ No newline at end of file diff --git a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IPowerManager.aidl b/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IPowerManager.aidl deleted file mode 100644 index c602c848..00000000 --- a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IPowerManager.aidl +++ /dev/null @@ -1,5 +0,0 @@ -package dev.sanmer.mrepo.compat.stub; - -interface IPowerManager { - void reboot(boolean confirm, String reason, boolean wait); -} \ No newline at end of file diff --git a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IServiceManager.aidl b/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IServiceManager.aidl deleted file mode 100644 index 9202cdbc..00000000 --- a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IServiceManager.aidl +++ /dev/null @@ -1,17 +0,0 @@ -package dev.sanmer.mrepo.compat.stub; - -import dev.sanmer.mrepo.compat.stub.IFileManager; -import dev.sanmer.mrepo.compat.stub.IModuleManager; -import dev.sanmer.mrepo.compat.stub.IPowerManager; - -interface IServiceManager { - int getUid() = 0; - int getPid() = 1; - String getSELinuxContext() = 2; - String currentPlatform() = 3; - IModuleManager getModuleManager() = 4; - IFileManager getFileManager() = 5; - IPowerManager getPowerManager() = 6; - - void destroy() = 16777114; // Only for Shizuku -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/BuildCompat.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/BuildCompat.kt deleted file mode 100644 index 1707d39e..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/BuildCompat.kt +++ /dev/null @@ -1,18 +0,0 @@ -package dev.sanmer.mrepo.compat - -import android.os.Build -import androidx.annotation.ChecksSdkIntAtLeast - -internal object BuildCompat { - @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU) - val atLeastT get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU - - @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) - val atLeastS get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S - - @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R) - val atLeastR get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R - - @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P) - val atLeastP get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -} \ 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 deleted file mode 100644 index d330e766..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/ServiceManagerCompat.kt +++ /dev/null @@ -1,208 +0,0 @@ -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 org.lsposed.hiddenapibypass.HiddenApiBypass -import rikka.shizuku.Shizuku -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -object ServiceManagerCompat { - private const val TIMEOUT_MILLIS = 15_000L - - fun setHiddenApiExemptions() = when { - BuildCompat.atLeastP -> HiddenApiBypass.addHiddenApiExemptions("") - else -> true - } - - internal interface IProvider { - val name: String - suspend 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) - } - } - } - - private 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 ShizukuProvider( - private val context: Context - ) : IProvider { - override val name = "Shizuku" - - override suspend fun isAvailable(): Boolean { - return Shizuku.pingBinder() && Shizuku.getUid() == 0 - } - - 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(service, connection) - } - - override fun unbind(connection: ServiceConnection) { - Shizuku.unbindUserService(service, connection, true) - } - - private val isGranted get() = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED - - private val service by lazy { - Shizuku.UserServiceArgs( - ComponentName( - context.packageName, - ServiceManagerImpl::class.java.name - ) - ).apply { - daemon(false) - debuggable(false) - processNameSuffix("shizuku") - } - } - - companion object { - private val provider by lazy { - ShizukuProvider( - ContextDelegate.getContext() - ) - } - - fun get(): IProvider = provider - } - } - - suspend fun fromShizuku() = from(ShizukuProvider.get()) - - private class LibSuProvider( - private val context: Context - ) : IProvider { - override val name = "LibSu" - - override suspend 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(service, connection) - } - - override fun unbind(connection: ServiceConnection) { - RootService.stop(service) - } - - private val service by lazy { - Intent().apply { - component = ComponentName( - context.packageName, - Service::class.java.name - ) - } - } - - init { - Shell.enableVerboseLogging = true - Shell.setDefaultBuilder( - Shell.Builder.create() - .setInitializers(SuShellInitializer::class.java) - .setTimeout(10) - ) - } - - private class SuShellInitializer : Shell.Initializer() { - override fun onInit(context: Context, shell: Shell) = shell.isRoot - } - - private class Service : RootService() { - override fun onBind(intent: Intent): IBinder { - return ServiceManagerImpl() - } - } - - companion object { - private val provider by lazy { - LibSuProvider( - ContextDelegate.getContext() - ) - } - - fun get(): IProvider = provider - } - } - - suspend fun fromLibSu() = from(LibSuProvider.get()) -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/delegate/ContextDelegate.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/delegate/ContextDelegate.kt deleted file mode 100644 index 47b1820a..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/delegate/ContextDelegate.kt +++ /dev/null @@ -1,16 +0,0 @@ -package dev.sanmer.mrepo.compat.delegate - -import android.app.ActivityThread -import android.content.Context -import android.content.ContextWrapper - -object ContextDelegate { - fun getContext(): Context { - var context: Context = ActivityThread.currentApplication() - while (context is ContextWrapper) { - context = context.baseContext - } - - return context - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/delegate/PowerManagerDelegate.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/delegate/PowerManagerDelegate.kt deleted file mode 100644 index 479bb96e..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/delegate/PowerManagerDelegate.kt +++ /dev/null @@ -1,39 +0,0 @@ -package dev.sanmer.mrepo.compat.delegate - -import android.os.Build -import android.os.PowerManagerHidden -import androidx.annotation.RequiresApi -import dev.sanmer.mrepo.compat.BuildCompat -import dev.sanmer.mrepo.compat.stub.IPowerManager - -class PowerManagerDelegate( - private val powerManager: IPowerManager -) { - fun reboot(reason: Reason = Reason.UserRequested) { - powerManager.reboot(false, reason.reason, true) - } - - enum class Reason( - internal val reason: String - ) { - UserRequested(SHUTDOWN_USER_REQUESTED), - @RequiresApi(Build.VERSION_CODES.R) - Userspace(REBOOT_USERSPACE), - Recovery(REBOOT_RECOVERY), - Bootloader(REBOOT_BOOTLOADER) - } - - companion object { - const val SHUTDOWN_USER_REQUESTED = "userrequested" - - @RequiresApi(Build.VERSION_CODES.R) - const val REBOOT_USERSPACE = "userspace" - - const val REBOOT_RECOVERY = "recovery" - - const val REBOOT_BOOTLOADER = "bootloader" - - fun isRebootingUserspaceSupported() = - BuildCompat.atLeastR && PowerManagerHidden.isRebootingUserspaceSupportedImpl() - } -} \ 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 deleted file mode 100644 index 6af5e7b8..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/APatchModuleManagerImpl.kt +++ /dev/null @@ -1,56 +0,0 @@ -package dev.sanmer.mrepo.compat.impl - -import com.topjohnwu.superuser.Shell -import dev.sanmer.mrepo.compat.stub.IInstallCallback -import dev.sanmer.mrepo.compat.stub.IModuleOpsCallback - -internal class APatchModuleManagerImpl( - private val shell: Shell, -) : BaseModuleManagerImpl(shell) { - override fun enable(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) callback.onFailure(id, null) - - "apd module enable $id".submit { - if (it.isSuccess) { - callback.onSuccess(id) - } else { - callback.onFailure(id, it.out.joinToString()) - } - } - } - - override fun disable(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) return callback.onFailure(id, null) - - "apd module disable $id".submit { - if (it.isSuccess) { - callback.onSuccess(id) - } else { - callback.onFailure(id, it.out.joinToString()) - } - } - } - - override fun remove(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) return callback.onFailure(id, null) - - "apd module uninstall $id".submit { - if (it.isSuccess) { - callback.onSuccess(id) - } else { - callback.onFailure(id, it.out.joinToString()) - } - } - } - - override fun install(path: String, callback: IInstallCallback) { - install( - cmd = "apd module install '${path}'", - path = path, - callback = callback - ) - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/FileManagerImpl.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/FileManagerImpl.kt deleted file mode 100644 index 1d59c9ef..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/FileManagerImpl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.sanmer.mrepo.compat.impl - -import dev.sanmer.mrepo.compat.stub.IFileManager -import java.io.File - -internal class FileManagerImpl : IFileManager.Stub() { - override fun deleteOnExit(path: String) = with(File(path)) { - when { - !exists() -> false - isFile -> delete() - isDirectory -> deleteRecursively() - else -> false - } - } -} \ No newline at end of file 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 deleted file mode 100644 index f817d1cf..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/KernelSUModuleManagerImpl.kt +++ /dev/null @@ -1,56 +0,0 @@ -package dev.sanmer.mrepo.compat.impl - -import com.topjohnwu.superuser.Shell -import dev.sanmer.mrepo.compat.stub.IInstallCallback -import dev.sanmer.mrepo.compat.stub.IModuleOpsCallback - -internal class KernelSUModuleManagerImpl( - private val shell: Shell, -) : BaseModuleManagerImpl(shell) { - override fun enable(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) callback.onFailure(id, null) - - "ksud module enable $id".submit { - if (it.isSuccess) { - callback.onSuccess(id) - } else { - callback.onFailure(id, it.out.joinToString()) - } - } - } - - override fun disable(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) return callback.onFailure(id, null) - - "ksud module disable $id".submit { - if (it.isSuccess) { - callback.onSuccess(id) - } else { - callback.onFailure(id, it.out.joinToString()) - } - } - } - - override fun remove(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) return callback.onFailure(id, null) - - "ksud module uninstall $id".submit { - if (it.isSuccess) { - callback.onSuccess(id) - } else { - callback.onFailure(id, it.out.joinToString()) - } - } - } - - override fun install(path: String, callback: IInstallCallback) { - install( - cmd = "ksud module install '${path}'", - path = path, - callback = callback - ) - } -} \ No newline at end of file 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 deleted file mode 100644 index f2f93ae1..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/MagiskModuleManagerImpl.kt +++ /dev/null @@ -1,59 +0,0 @@ -package dev.sanmer.mrepo.compat.impl - -import com.topjohnwu.superuser.Shell -import dev.sanmer.mrepo.compat.stub.IInstallCallback -import dev.sanmer.mrepo.compat.stub.IModuleOpsCallback - -internal class MagiskModuleManagerImpl( - private val shell: Shell -) : BaseModuleManagerImpl(shell) { - override fun enable(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) callback.onFailure(id, null) - - runCatching { - dir.resolve("remove").apply { if (exists()) delete() } - dir.resolve("disable").apply { if (exists()) delete() } - }.onSuccess { - callback.onSuccess(id) - }.onFailure { - callback.onFailure(id, it.message) - } - } - - override fun disable(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) return callback.onFailure(id, null) - - runCatching { - dir.resolve("remove").apply { if (exists()) delete() } - dir.resolve("disable").createNewFile() - }.onSuccess { - callback.onSuccess(id) - }.onFailure { - callback.onFailure(id, it.message) - } - } - - override fun remove(id: String, callback: IModuleOpsCallback) { - val dir = modulesDir.resolve(id) - if (!dir.exists()) return callback.onFailure(id, null) - - runCatching { - dir.resolve("disable").apply { if (exists()) delete() } - dir.resolve("remove").createNewFile() - }.onSuccess { - callback.onSuccess(id) - }.onFailure { - callback.onFailure(id, it.message) - } - } - - override fun install(path: String, callback: IInstallCallback) { - install( - cmd = "magisk --install-module '${path}'", - path = path, - callback = callback - ) - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/PowerManagerImpl.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/PowerManagerImpl.kt deleted file mode 100644 index 845b6989..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/PowerManagerImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.sanmer.mrepo.compat.impl - -import dev.sanmer.mrepo.compat.stub.IPowerManager - -internal class PowerManagerImpl( - private val original: android.os.IPowerManager -) : IPowerManager.Stub() { - override fun reboot(confirm: Boolean, reason: String?, wait: Boolean) { - original.reboot(confirm, reason, wait) - } -} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/ServiceManagerImpl.kt b/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/ServiceManagerImpl.kt deleted file mode 100644 index dbb7b09b..00000000 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/ServiceManagerImpl.kt +++ /dev/null @@ -1,83 +0,0 @@ -package dev.sanmer.mrepo.compat.impl - -import android.content.Context -import android.os.SELinux -import android.os.ServiceManager -import android.system.Os -import com.topjohnwu.superuser.Shell -import com.topjohnwu.superuser.ShellUtils -import dev.sanmer.mrepo.compat.stub.IFileManager -import dev.sanmer.mrepo.compat.stub.IModuleManager -import dev.sanmer.mrepo.compat.stub.IPowerManager -import dev.sanmer.mrepo.compat.stub.IServiceManager -import kotlin.system.exitProcess - -internal class ServiceManagerImpl : IServiceManager.Stub() { - private val main by lazy { - Shell.Builder.create() - .build("sh") - } - - private val platform by lazy { - when { - "which magisk".execResult() -> Platform.Magisk - "which ksud".execResult() -> Platform.KernelSU - "which apd".execResult() -> Platform.APatch - else -> throw IllegalArgumentException("unsupported platform: $seLinuxContext") - } - } - - private val moduleManager by lazy { - when (platform) { - Platform.Magisk -> MagiskModuleManagerImpl(main) - Platform.KernelSU -> KernelSUModuleManagerImpl(main) - Platform.APatch -> APatchModuleManagerImpl(main) - } - } - - private val fileManager by lazy { - FileManagerImpl() - } - - private val powerManager by lazy { - PowerManagerImpl( - android.os.IPowerManager.Stub.asInterface( - ServiceManager.getService(Context.POWER_SERVICE) - ) - ) - } - - override fun getUid(): Int { - return Os.getuid() - } - - override fun getPid(): Int { - return Os.getpid() - } - - override fun getSELinuxContext(): String { - return SELinux.getContext() - } - - override fun currentPlatform(): String { - return platform.name - } - - override fun getModuleManager(): IModuleManager { - return moduleManager - } - - override fun getFileManager(): IFileManager { - return fileManager - } - - override fun getPowerManager(): IPowerManager { - return powerManager - } - - override fun destroy() { - exitProcess(0) - } - - private fun String.execResult() = ShellUtils.fastCmdResult(main, this) -} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 00000000..b94ed04d --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + alias(libs.plugins.self.library) + alias(libs.plugins.kotlin.parcelize) +} + +android { + namespace = "dev.sanmer.mrepo.core" + + defaultConfig { + consumerProguardFile("proguard-rules.pro") + } + + buildFeatures { + aidl = true + } +} \ No newline at end of file diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro new file mode 100644 index 00000000..00470a9f --- /dev/null +++ b/core/proguard-rules.pro @@ -0,0 +1 @@ +-keepclassmembers class dev.sanmer.mrepo.ModuleManager { public (); } \ No newline at end of file diff --git a/core/src/main/aidl/dev/sanmer/mrepo/content/Module.aidl b/core/src/main/aidl/dev/sanmer/mrepo/content/Module.aidl new file mode 100644 index 00000000..5c0a9a69 --- /dev/null +++ b/core/src/main/aidl/dev/sanmer/mrepo/content/Module.aidl @@ -0,0 +1,3 @@ +package dev.sanmer.mrepo.content; + +parcelable Module; \ No newline at end of file diff --git a/core/src/main/aidl/dev/sanmer/mrepo/stub/IInstallCallback.aidl b/core/src/main/aidl/dev/sanmer/mrepo/stub/IInstallCallback.aidl new file mode 100644 index 00000000..bdc9e7e8 --- /dev/null +++ b/core/src/main/aidl/dev/sanmer/mrepo/stub/IInstallCallback.aidl @@ -0,0 +1,10 @@ +package dev.sanmer.mrepo.stub; + +import dev.sanmer.mrepo.content.Module; + +oneway interface IInstallCallback { + void onStdout(String msg); + void onStderr(String msg); + void onSuccess(in Module module); + void onFailure(); +} \ No newline at end of file diff --git a/core/src/main/aidl/dev/sanmer/mrepo/stub/IModuleManager.aidl b/core/src/main/aidl/dev/sanmer/mrepo/stub/IModuleManager.aidl new file mode 100644 index 00000000..9d4920c0 --- /dev/null +++ b/core/src/main/aidl/dev/sanmer/mrepo/stub/IModuleManager.aidl @@ -0,0 +1,23 @@ +package dev.sanmer.mrepo.stub; + +import dev.sanmer.mrepo.content.Module; +import dev.sanmer.mrepo.stub.IInstallCallback; +import dev.sanmer.mrepo.stub.IModuleOpsCallback; + +interface IModuleManager { + String getVersion(); + int getVersionCode(); + String getPlatform(); + + List getModules(); + Module getModuleById(String id); + Module getModuleInfo(String path); + + oneway void enable(String id, IModuleOpsCallback callback); + oneway void disable(String id, IModuleOpsCallback callback); + oneway void remove(String id, IModuleOpsCallback callback); + oneway void install(String path, IInstallCallback callback); + + boolean deleteOnExit(String path); + oneway void reboot(); +} \ No newline at end of file diff --git a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IModuleOpsCallback.aidl b/core/src/main/aidl/dev/sanmer/mrepo/stub/IModuleOpsCallback.aidl similarity index 52% rename from compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IModuleOpsCallback.aidl rename to core/src/main/aidl/dev/sanmer/mrepo/stub/IModuleOpsCallback.aidl index 3459c7f2..839059ca 100644 --- a/compat/src/main/aidl/dev/sanmer/mrepo/compat/stub/IModuleOpsCallback.aidl +++ b/core/src/main/aidl/dev/sanmer/mrepo/stub/IModuleOpsCallback.aidl @@ -1,6 +1,6 @@ -package dev.sanmer.mrepo.compat.stub; +package dev.sanmer.mrepo.stub; -interface IModuleOpsCallback { +oneway interface IModuleOpsCallback { void onSuccess(String id); void onFailure(String id, String msg); } \ No newline at end of file diff --git a/core/src/main/kotlin/dev/sanmer/mrepo/ModuleManager.kt b/core/src/main/kotlin/dev/sanmer/mrepo/ModuleManager.kt new file mode 100644 index 00000000..a20e4a0c --- /dev/null +++ b/core/src/main/kotlin/dev/sanmer/mrepo/ModuleManager.kt @@ -0,0 +1,34 @@ +package dev.sanmer.mrepo + +import dev.sanmer.mrepo.content.Module +import dev.sanmer.mrepo.impl.APatchModuleManagerImpl +import dev.sanmer.mrepo.impl.KernelSUModuleManagerImpl +import dev.sanmer.mrepo.impl.MagiskModuleManagerImpl +import dev.sanmer.mrepo.impl.Shell.exec +import dev.sanmer.mrepo.stub.IInstallCallback +import dev.sanmer.mrepo.stub.IModuleManager +import dev.sanmer.mrepo.stub.IModuleOpsCallback + +class ModuleManager : IModuleManager.Stub() { + private val original by lazy { + when { + "which magisk".exec().isSuccess -> MagiskModuleManagerImpl() + "which ksud".exec().isSuccess -> KernelSUModuleManagerImpl() + "which apd".exec().isSuccess -> APatchModuleManagerImpl() + else -> throw IllegalArgumentException("Unsupported platform") + } + } + + override fun getVersion(): String = original.version + override fun getVersionCode(): Int = original.versionCode + override fun getPlatform(): String = original.platform + override fun getModules(): List = original.modules + override fun getModuleById(id: String): Module? = original.getModuleById(id) + override fun getModuleInfo(path: String): Module? = original.getModuleInfo(path) + override fun enable(id: String, callback: IModuleOpsCallback?) = original.enable(id, callback) + override fun disable(id: String, callback: IModuleOpsCallback?) = original.disable(id, callback) + override fun remove(id: String, callback: IModuleOpsCallback?) = original.remove(id, callback) + override fun install(path: String, callback: IInstallCallback?) = original.install(path, callback) + override fun deleteOnExit(path: String): Boolean = original.deleteOnExit(path) + override fun reboot() = original.reboot() +} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/Platform.kt b/core/src/main/kotlin/dev/sanmer/mrepo/Platform.kt similarity index 62% rename from compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/Platform.kt rename to core/src/main/kotlin/dev/sanmer/mrepo/Platform.kt index 76324624..f1966067 100644 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/Platform.kt +++ b/core/src/main/kotlin/dev/sanmer/mrepo/Platform.kt @@ -1,4 +1,4 @@ -package dev.sanmer.mrepo.compat.impl +package dev.sanmer.mrepo enum class Platform { Magisk, diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/content/LocalModule.kt b/core/src/main/kotlin/dev/sanmer/mrepo/content/Module.kt similarity index 74% rename from compat/src/main/kotlin/dev/sanmer/mrepo/compat/content/LocalModule.kt rename to core/src/main/kotlin/dev/sanmer/mrepo/content/Module.kt index f85b3a7b..95709277 100644 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/content/LocalModule.kt +++ b/core/src/main/kotlin/dev/sanmer/mrepo/content/Module.kt @@ -1,10 +1,10 @@ -package dev.sanmer.mrepo.compat.content +package dev.sanmer.mrepo.content import android.os.Parcelable import kotlinx.parcelize.Parcelize @Parcelize -data class LocalModule( +data class Module( val id: String, val name: String, val version: String, @@ -14,6 +14,4 @@ data class LocalModule( val updateJson: String, val state: State, val lastUpdated: Long -) : Parcelable { - companion object -} \ No newline at end of file +) : Parcelable \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/content/State.kt b/core/src/main/kotlin/dev/sanmer/mrepo/content/State.kt similarity index 63% rename from compat/src/main/kotlin/dev/sanmer/mrepo/compat/content/State.kt rename to core/src/main/kotlin/dev/sanmer/mrepo/content/State.kt index 3e2fcf04..d0e3c2fc 100644 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/content/State.kt +++ b/core/src/main/kotlin/dev/sanmer/mrepo/content/State.kt @@ -1,4 +1,4 @@ -package dev.sanmer.mrepo.compat.content +package dev.sanmer.mrepo.content enum class State { ENABLE, diff --git a/core/src/main/kotlin/dev/sanmer/mrepo/impl/APatchModuleManagerImpl.kt b/core/src/main/kotlin/dev/sanmer/mrepo/impl/APatchModuleManagerImpl.kt new file mode 100644 index 00000000..b50f7039 --- /dev/null +++ b/core/src/main/kotlin/dev/sanmer/mrepo/impl/APatchModuleManagerImpl.kt @@ -0,0 +1,59 @@ +package dev.sanmer.mrepo.impl + +import dev.sanmer.mrepo.Platform +import dev.sanmer.mrepo.impl.Shell.exec +import dev.sanmer.mrepo.stub.IInstallCallback +import dev.sanmer.mrepo.stub.IModuleOpsCallback +import java.io.File + +internal class APatchModuleManagerImpl : BaseModuleManagerImpl() { + override fun getPlatform(): String { + return Platform.APatch.name + } + + override fun enable(id: String, callback: IModuleOpsCallback?) { + moduleOps( + cmd = "apd module enable $id", + id = id, + callback = callback + ) + } + + override fun disable(id: String, callback: IModuleOpsCallback?) { + moduleOps( + cmd = "apd module disable $id", + id = id, + callback = callback + ) + } + + override fun remove(id: String, callback: IModuleOpsCallback?) { + moduleOps( + cmd = "apd module uninstall $id", + id = id, + callback = callback + ) + } + + override fun install(path: String, callback: IInstallCallback?) { + install( + cmd = "apd module install '${path}'", + path = path, + callback = callback + ) + } + + private fun moduleOps(cmd: String, id: String, callback: IModuleOpsCallback?) { + val moduleDir = File(modulesDir, id) + if (!moduleDir.exists()) { + callback?.onFailure(id, null) + return + } + + cmd.exec().onSuccess { + callback?.onSuccess(id) + }.onFailure { + callback?.onFailure(id, it.message) + } + } +} \ No newline at end of file diff --git a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/BaseModuleManagerImpl.kt b/core/src/main/kotlin/dev/sanmer/mrepo/impl/BaseModuleManagerImpl.kt similarity index 67% rename from compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/BaseModuleManagerImpl.kt rename to core/src/main/kotlin/dev/sanmer/mrepo/impl/BaseModuleManagerImpl.kt index 1c581fc9..961a5742 100644 --- a/compat/src/main/kotlin/dev/sanmer/mrepo/compat/impl/BaseModuleManagerImpl.kt +++ b/core/src/main/kotlin/dev/sanmer/mrepo/impl/BaseModuleManagerImpl.kt @@ -1,30 +1,23 @@ -package dev.sanmer.mrepo.compat.impl - -import com.topjohnwu.superuser.CallbackList -import com.topjohnwu.superuser.Shell -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.IInstallCallback -import dev.sanmer.mrepo.compat.stub.IModuleManager +package dev.sanmer.mrepo.impl + +import dev.sanmer.mrepo.content.Module +import dev.sanmer.mrepo.content.State +import dev.sanmer.mrepo.impl.Shell.exec +import dev.sanmer.mrepo.stub.IInstallCallback +import dev.sanmer.mrepo.stub.IModuleManager import java.io.File import java.util.zip.ZipFile -internal abstract class BaseModuleManagerImpl( - private val shell: Shell -) : IModuleManager.Stub() { +internal abstract class BaseModuleManagerImpl : IModuleManager.Stub() { internal val modulesDir = File(MODULES_PATH) private val mVersion by lazy { - runCatching { - "su -v".exec() - }.getOrDefault("unknown") + "su -v".exec().getOrDefault("unknown") } private val mVersionCode by lazy { - runCatching { - "su -V".exec().toInt() - }.getOrDefault(-1) + "su -V".exec().getOrDefault("-1") + .toIntOr(-1) } override fun getVersion(): String { @@ -41,13 +34,13 @@ internal abstract class BaseModuleManagerImpl( readProps(dir)?.toModule(dir) } - override fun getModuleById(id: String): LocalModule? { + override fun getModuleById(id: String): Module? { val dir = modulesDir.resolve(id) return readProps(dir)?.toModule(dir) } - override fun getModuleInfo(zipPath: String): LocalModule? { - val zipFile = ZipFile(zipPath) + override fun getModuleInfo(path: String): Module? { + val zipFile = ZipFile(path) val entry = zipFile.getEntry(PROP_FILE) ?: return null return zipFile.getInputStream(entry).use { @@ -58,6 +51,19 @@ internal abstract class BaseModuleManagerImpl( } } + override fun deleteOnExit(path: String) = with(File(path)) { + when { + !exists() -> false + isFile -> delete() + isDirectory -> deleteRecursively() + else -> false + } + } + + override fun reboot() { + "svc power reboot || reboot".exec() + } + private fun readProps(props: String) = props.lines() .associate { line -> val items = line.split("=", limit = 2).map { it.trim() } @@ -115,7 +121,7 @@ internal abstract class BaseModuleManagerImpl( path: String = "unknown", state: State = State.ENABLE, lastUpdated: Long = 0L - ) = LocalModule( + ) = Module( id = getOrDefault("id", path), name = getOrDefault("name", path), version = getOrDefault("version", ""), @@ -132,34 +138,28 @@ internal abstract class BaseModuleManagerImpl( toInt() }.getOrDefault(defaultValue) - private fun String.exec() = ShellUtils.fastCmd(shell, this) - - internal fun install(cmd: String, path: String, callback: IInstallCallback) { - val stdout = object : CallbackList() { - override fun onAddElement(msg: String?) { - msg?.let(callback::onStdout) - } + internal fun install(cmd: String, path: String, callback: IInstallCallback?) { + if (callback == null) { + cmd.exec() + return } - val stderr = object : CallbackList() { - override fun onAddElement(msg: String?) { - msg?.let(callback::onStderr) - } - } + val result = cmd.exec( + stdout = callback::onStdout, + stderr = callback::onStderr + ) - val result = shell.newJob().add(cmd).to(stdout, stderr).exec() - if (result.isSuccess) { - val module = getModuleInfo(path) - callback.onSuccess(module) - } else { - callback.onFailure() + when { + result.isSuccess -> { + val module = getModuleInfo(path) + callback.onSuccess(module) + } + else -> { + callback.onFailure() + } } } - internal fun String.submit(cb: Shell.ResultCallback) = shell - .newJob().add(this).to(ArrayList(), null) - .submit(cb) - companion object { const val PROP_FILE = "module.prop" const val MODULES_PATH = "/data/adb/modules" diff --git a/core/src/main/kotlin/dev/sanmer/mrepo/impl/KernelSUModuleManagerImpl.kt b/core/src/main/kotlin/dev/sanmer/mrepo/impl/KernelSUModuleManagerImpl.kt new file mode 100644 index 00000000..a47d95eb --- /dev/null +++ b/core/src/main/kotlin/dev/sanmer/mrepo/impl/KernelSUModuleManagerImpl.kt @@ -0,0 +1,59 @@ +package dev.sanmer.mrepo.impl + +import dev.sanmer.mrepo.Platform +import dev.sanmer.mrepo.impl.Shell.exec +import dev.sanmer.mrepo.stub.IInstallCallback +import dev.sanmer.mrepo.stub.IModuleOpsCallback +import java.io.File + +internal class KernelSUModuleManagerImpl : BaseModuleManagerImpl() { + override fun getPlatform(): String { + return Platform.KernelSU.name + } + + override fun enable(id: String, callback: IModuleOpsCallback?) { + moduleOps( + cmd = "ksud module enable $id", + id = id, + callback = callback + ) + } + + override fun disable(id: String, callback: IModuleOpsCallback?) { + moduleOps( + cmd = "ksud module disable $id", + id = id, + callback = callback + ) + } + + override fun remove(id: String, callback: IModuleOpsCallback?) { + moduleOps( + cmd = "ksud module uninstall $id", + id = id, + callback = callback + ) + } + + override fun install(path: String, callback: IInstallCallback?) { + install( + cmd = "ksud module install '${path}'", + path = path, + callback = callback + ) + } + + private fun moduleOps(cmd: String, id: String, callback: IModuleOpsCallback?) { + val moduleDir = File(modulesDir, id) + if (!moduleDir.exists()) { + callback?.onFailure(id, null) + return + } + + cmd.exec().onSuccess { + callback?.onSuccess(id) + }.onFailure { + callback?.onFailure(id, it.message) + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/dev/sanmer/mrepo/impl/MagiskModuleManagerImpl.kt b/core/src/main/kotlin/dev/sanmer/mrepo/impl/MagiskModuleManagerImpl.kt new file mode 100644 index 00000000..df8ca5c7 --- /dev/null +++ b/core/src/main/kotlin/dev/sanmer/mrepo/impl/MagiskModuleManagerImpl.kt @@ -0,0 +1,85 @@ +package dev.sanmer.mrepo.impl + +import dev.sanmer.mrepo.Platform +import dev.sanmer.mrepo.stub.IInstallCallback +import dev.sanmer.mrepo.stub.IModuleOpsCallback +import java.io.File + +internal class MagiskModuleManagerImpl : BaseModuleManagerImpl() { + override fun getPlatform(): String { + return Platform.Magisk.name + } + + override fun enable(id: String, callback: IModuleOpsCallback?) { + moduleOps( + tags = listOf( + Tag("remove", FileOp.Delete), + Tag("disable", FileOp.Delete) + ), + id = id, + callback = callback + ) + } + + override fun disable(id: String, callback: IModuleOpsCallback?) { + moduleOps( + tags = listOf( + Tag("remove", FileOp.Delete), + Tag("disable", FileOp.Create) + ), + id = id, + callback = callback + ) + } + + override fun remove(id: String, callback: IModuleOpsCallback?) { + moduleOps( + tags = listOf( + Tag("disable", FileOp.Delete), + Tag("remove", FileOp.Create) + ), + id = id, + callback = callback + ) + } + + override fun install(path: String, callback: IInstallCallback?) { + install( + cmd = "magisk --install-module '${path}'", + path = path, + callback = callback + ) + } + + private fun moduleOps(tags: List, id: String, callback: IModuleOpsCallback?) { + val moduleDir = File(modulesDir, id) + if (!moduleDir.exists()) { + callback?.onFailure(id, null) + return + } + + runCatching { + tags.forEach { + val tag = File(moduleDir, it.name) + when (it.op) { + FileOp.Delete -> if (tag.exists()) tag.delete() + FileOp.Create -> tag.createNewFile() + } + } + }.onSuccess { + callback?.onSuccess(id) + }.onFailure { + callback?.onFailure(id, it.message) + } + } + + private class Tag( + val name: String, + val op: FileOp + ) + + private enum class FileOp { + Delete, + Create + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/dev/sanmer/mrepo/impl/Shell.kt b/core/src/main/kotlin/dev/sanmer/mrepo/impl/Shell.kt new file mode 100644 index 00000000..3c451c2e --- /dev/null +++ b/core/src/main/kotlin/dev/sanmer/mrepo/impl/Shell.kt @@ -0,0 +1,61 @@ +package dev.sanmer.mrepo.impl + +import android.util.Log + +internal object Shell { + private const val TAG = "Shell" + + fun String.exec(): Result = try { + Log.d(TAG, "exec: $this") + val process = ProcessBuilder("sh", "-c", this).start() + val output = process.inputStream.bufferedReader().readText() + .removeSurrounding("", "\n") + + val error = process.errorStream.bufferedReader().readText() + .removeSurrounding("", "\n") + + when { + process.waitFor().ok() -> { + Log.d(TAG, "output: $output") + Result.success(output) + } + else -> { + Log.d(TAG, "error: $error") + Result.failure(RuntimeException(error)) + } + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + Result.failure(e) + } + + fun String.exec( + stdout: (String) -> Unit, + stderr: (String) -> Unit + ) = try { + Log.d(TAG, "submit: ${this@exec}") + val process = ProcessBuilder("sh", "-c", this@exec).start() + val output = process.inputStream.bufferedReader() + val error = process.errorStream.bufferedReader() + + output.forEachLine { + Log.d(TAG, "output: $it") + stdout(it) + } + + error.forEachLine { + Log.d(TAG, "stderr: $it") + stderr(it) + } + + when { + process.waitFor().ok() -> Result.success(true) + else -> Result.failure(RuntimeException()) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + Result.failure(e) + } + + private fun Int.ok() = this == 0 +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c847c6e3..51fdc6eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,16 +13,13 @@ androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.8.1" androidxNavigation = "2.7.7" androidxRoom = "2.6.1" -hiddenApiRefine = "4.4.0" hilt = "2.51.1" kotlin = "2.0.0" kotlinxCoroutines = "1.8.1" kotlinxDatetime = "0.6.0" ksp = "2.0.0-1.0.22" -libsu = "5.2.2" protobuf = "4.27.1" protobufPlugin = "0.9.4" -shizuku = "13.1.5" squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" squareMoshi = "1.15.1" @@ -53,15 +50,8 @@ hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } -libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } -libsu-service = { group = "com.github.topjohnwu.libsu", name = "service", version.ref = "libsu" } protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" } protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" } -rikka-refine-annotation = { module = "dev.rikka.tools.refine:annotation", version.ref = "hiddenApiRefine" } -rikka-refine-compiler = { module = "dev.rikka.tools.refine:annotation-processor", version.ref = "hiddenApiRefine" } -rikka-refine-runtime = { module = "dev.rikka.tools.refine:runtime", version.ref = "hiddenApiRefine" } -rikka-shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" } -rikka-shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" } square-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "squareRetrofit" } square-retrofit-moshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "squareRetrofit" } square-okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "squareOkhttp" } @@ -69,14 +59,15 @@ square-okhttp-dnsoverhttps = { group = "com.squareup.okhttp3", name = "okhttp-dn square-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "squareOkhttp" } square-moshi = { group = "com.squareup.moshi", name = "moshi", version.ref = "squareMoshi" } square-moshi-kotlin = { group = "com.squareup.moshi", name = "moshi-kotlin-codegen", version.ref = "squareMoshi" } - markwon-core = "io.noties.markwon:core:4.6.2" -hiddenApiBypass = "org.lsposed.hiddenapibypass:hiddenapibypass:4.3" timber = "com.jakewharton.timber:timber:5.0.1" +# Local +sanmer-su = "dev.sanmer.su:core:0.1.0" + # Dependencies of the included build-logic android-gradle = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } -compose-gradle= { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } +compose-gradle= { group = "org.jetbrains.kotlin", name = "compose-compiler-gradle-plugin", version.ref = "kotlin" } kotlin-gradle = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ksp-gradle = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } @@ -88,7 +79,6 @@ hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } -rikka-refine = { id = "dev.rikka.tools.refine", version.ref = "hiddenApiRefine" } protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } # Plugins defined by this project diff --git a/hidden-api/build.gradle.kts b/hidden-api/build.gradle.kts deleted file mode 100644 index bc1925fa..00000000 --- a/hidden-api/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - alias(libs.plugins.self.library) -} - -android { - namespace = "dev.sanmer.mrepo.hidden_api" -} - -dependencies { - annotationProcessor(libs.rikka.refine.compiler) - compileOnly(libs.rikka.refine.annotation) - compileOnly(libs.androidx.annotation) -} \ No newline at end of file diff --git a/hidden-api/src/main/java/android/app/ActivityThread.java b/hidden-api/src/main/java/android/app/ActivityThread.java deleted file mode 100644 index c1b50074..00000000 --- a/hidden-api/src/main/java/android/app/ActivityThread.java +++ /dev/null @@ -1,7 +0,0 @@ -package android.app; - -public class ActivityThread { - public static Application currentApplication() { - throw new RuntimeException("Stub!"); - } -} \ No newline at end of file diff --git a/hidden-api/src/main/java/android/os/IPowerManager.java b/hidden-api/src/main/java/android/os/IPowerManager.java deleted file mode 100644 index e6f23a29..00000000 --- a/hidden-api/src/main/java/android/os/IPowerManager.java +++ /dev/null @@ -1,12 +0,0 @@ -package android.os; - -public interface IPowerManager extends IInterface { - void reboot(boolean confirm, String reason, boolean wait) throws RemoteException; - - abstract class Stub extends Binder implements IPowerManager { - - public static IPowerManager asInterface(IBinder binder) { - throw new RuntimeException("Stub!"); - } - } -} diff --git a/hidden-api/src/main/java/android/os/PowerManagerHidden.java b/hidden-api/src/main/java/android/os/PowerManagerHidden.java deleted file mode 100644 index 6b8ec379..00000000 --- a/hidden-api/src/main/java/android/os/PowerManagerHidden.java +++ /dev/null @@ -1,13 +0,0 @@ -package android.os; - -import androidx.annotation.RequiresApi; - -import dev.rikka.tools.refine.RefineAs; - -@RefineAs(PowerManager.class) -public class PowerManagerHidden { - @RequiresApi(30) - public static boolean isRebootingUserspaceSupportedImpl() { - throw new RuntimeException("Stub!"); - } -} diff --git a/hidden-api/src/main/java/android/os/SELinux.java b/hidden-api/src/main/java/android/os/SELinux.java deleted file mode 100644 index 79c66bff..00000000 --- a/hidden-api/src/main/java/android/os/SELinux.java +++ /dev/null @@ -1,6 +0,0 @@ -package android.os; - -public class SELinux { - - public static native String getContext(); -} \ No newline at end of file diff --git a/hidden-api/src/main/java/android/os/ServiceManager.java b/hidden-api/src/main/java/android/os/ServiceManager.java deleted file mode 100644 index 6718fb30..00000000 --- a/hidden-api/src/main/java/android/os/ServiceManager.java +++ /dev/null @@ -1,7 +0,0 @@ -package android.os; - -public class ServiceManager { - public static IBinder getService(String name) { - throw new RuntimeException("Stub!"); - } -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 7eafc902..6f44011c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,11 +2,17 @@ enableFeaturePreview("STABLE_CONFIGURATION_CACHE") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS repositories { google() mavenCentral() maven("https://jitpack.io") + + mavenLocal { + content { + includeGroup("dev.sanmer.su") + } + } } } @@ -20,6 +26,4 @@ pluginManagement { } rootProject.name = "MRepo" -include(":app") -include(":hidden-api") -include(":compat") \ No newline at end of file +include(":core", ":app") \ No newline at end of file