diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ad5e8537..02237c14 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ import java.time.Instant plugins { alias(libs.plugins.self.application) alias(libs.plugins.self.compose) + alias(libs.plugins.koin) alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.kotlin.serialization) } @@ -12,14 +13,13 @@ val baseVersionName = "2.0.1" val gitCommitTag = gitCommitTag() val gitCommitSha = gitCommitSha() val gitCommitNum = gitCommitNum() -val devSuffix = if (gitCommitTag.isEmpty()) ".dev" else "" android { namespace = "dev.sanmer.pi" defaultConfig { applicationId = namespace - versionName = "${baseVersionName}.${gitCommitSha}${devSuffix}" + versionName = baseVersionName + if (gitCommitTag.isEmpty()) ".$gitCommitSha" else "" versionCode = gitCommitNum ndk.abiFilters += listOf("arm64-v8a", "x86_64") } @@ -55,9 +55,6 @@ android { } packaging { - jniLibs.excludes += setOf( - "**/libdatastore_shared_counter.so" - ) resources.excludes += setOf( "META-INF/**", "kotlin/**", @@ -103,8 +100,11 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.service) implementation(libs.androidx.lifecycle.viewmodel) + implementation(platform(libs.koin.bom)) implementation(libs.koin.android) + implementation(libs.koin.annotations) implementation(libs.koin.compose) + implementation(libs.koin.compose.viewmodel) implementation(libs.kotlinx.coroutines.android) implementation(libs.hiddenApiBypass) implementation(libs.xz) diff --git a/app/src/main/kotlin/dev/sanmer/pi/App.kt b/app/src/main/kotlin/dev/sanmer/pi/App.kt index ef76c12f..cb4cee7c 100644 --- a/app/src/main/kotlin/dev/sanmer/pi/App.kt +++ b/app/src/main/kotlin/dev/sanmer/pi/App.kt @@ -5,11 +5,11 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationManagerCompat -import dev.sanmer.pi.di.Repositories -import dev.sanmer.pi.di.ViewModels +import dev.sanmer.pi.di.ViewModelsModule import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.core.context.startKoin +import org.koin.core.logger.Level import org.lsposed.hiddenapibypass.HiddenApiBypass class App : Application() { @@ -18,9 +18,9 @@ class App : Application() { createNotificationChannels(this) HiddenApiBypass.setHiddenApiExemptions("") startKoin { - androidLogger() + androidLogger(if (BuildConfig.DEBUG) Level.DEBUG else Level.INFO) androidContext(this@App) - modules(Repositories, ViewModels) + modules(ViewModelsModule) } } diff --git a/app/src/main/kotlin/dev/sanmer/pi/di/RepositoriesModule.kt b/app/src/main/kotlin/dev/sanmer/pi/di/RepositoriesModule.kt index 71be02fc..fe1c3f2d 100644 --- a/app/src/main/kotlin/dev/sanmer/pi/di/RepositoriesModule.kt +++ b/app/src/main/kotlin/dev/sanmer/pi/di/RepositoriesModule.kt @@ -1,11 +1,42 @@ package dev.sanmer.pi.di +import dev.sanmer.pi.core.delegate.AppOpsManagerDelegate +import dev.sanmer.pi.core.delegate.PackageInstallerDelegate +import dev.sanmer.pi.core.delegate.PackageManagerDelegate +import dev.sanmer.pi.core.delegate.PermissionManagerDelegate +import dev.sanmer.pi.core.delegate.UserManagerDelegate import dev.sanmer.pi.repository.SuRepository import dev.sanmer.pi.repository.SuRepositoryImpl -import org.koin.core.module.dsl.bind -import org.koin.core.module.dsl.singleOf +import dev.sanmer.su.BinderWrapper +import org.koin.core.qualifier.named +import org.koin.dsl.binds import org.koin.dsl.module +import org.koin.plugin.module.dsl.single -val Repositories = module { - singleOf(::SuRepositoryImpl) { bind() } +val RepositoriesModule = module { + single() binds arrayOf(SuRepository::class, BinderWrapper::class) + + single(named("ownerPackageName")) { + get().ownerPackageName + } + + single { + AppOpsManagerDelegate { get().wrap(this) } + } + + single { + PackageManagerDelegate { get().wrap(this) } + } + + single { + PackageInstallerDelegate { get().wrap(this) } + } + + single { + PermissionManagerDelegate { get().wrap(this) } + } + + single { + UserManagerDelegate { get().wrap(this) } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/sanmer/pi/di/ViewModelsModule.kt b/app/src/main/kotlin/dev/sanmer/pi/di/ViewModelsModule.kt index afc977e3..f3f69e51 100644 --- a/app/src/main/kotlin/dev/sanmer/pi/di/ViewModelsModule.kt +++ b/app/src/main/kotlin/dev/sanmer/pi/di/ViewModelsModule.kt @@ -1,9 +1,10 @@ package dev.sanmer.pi.di import dev.sanmer.pi.ui.main.MainViewModel -import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module +import org.koin.plugin.module.dsl.viewModel -val ViewModels = module { - viewModelOf(::MainViewModel) +val ViewModelsModule = module { + includes(RepositoriesModule) + viewModel() } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/sanmer/pi/repository/SuRepository.kt b/app/src/main/kotlin/dev/sanmer/pi/repository/SuRepository.kt index 9cf1a2b9..cc5f77ec 100644 --- a/app/src/main/kotlin/dev/sanmer/pi/repository/SuRepository.kt +++ b/app/src/main/kotlin/dev/sanmer/pi/repository/SuRepository.kt @@ -1,20 +1,10 @@ package dev.sanmer.pi.repository -import dev.sanmer.pi.core.delegate.AppOpsManagerDelegate -import dev.sanmer.pi.core.delegate.PackageInstallerDelegate -import dev.sanmer.pi.core.delegate.PackageManagerDelegate -import dev.sanmer.pi.core.delegate.PermissionManagerDelegate -import dev.sanmer.pi.core.delegate.UserManagerDelegate import dev.sanmer.pi.model.LoadData import dev.sanmer.su.BinderWrapper import kotlinx.coroutines.flow.StateFlow -interface SuRepository { +interface SuRepository : BinderWrapper { val state: StateFlow> suspend fun launch() - fun getAppOpsManager(): AppOpsManagerDelegate - fun getPackageManager(): PackageManagerDelegate - fun getPackageInstaller(): PackageInstallerDelegate - fun getPermissionManager(): PermissionManagerDelegate - fun getUserManager(): UserManagerDelegate } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/sanmer/pi/repository/SuRepositoryImpl.kt b/app/src/main/kotlin/dev/sanmer/pi/repository/SuRepositoryImpl.kt index 79cb6b52..a5a0bc70 100644 --- a/app/src/main/kotlin/dev/sanmer/pi/repository/SuRepositoryImpl.kt +++ b/app/src/main/kotlin/dev/sanmer/pi/repository/SuRepositoryImpl.kt @@ -3,11 +3,6 @@ package dev.sanmer.pi.repository import android.content.Context import android.os.IBinder import android.util.Log -import dev.sanmer.pi.core.delegate.AppOpsManagerDelegate -import dev.sanmer.pi.core.delegate.PackageInstallerDelegate -import dev.sanmer.pi.core.delegate.PackageManagerDelegate -import dev.sanmer.pi.core.delegate.PermissionManagerDelegate -import dev.sanmer.pi.core.delegate.UserManagerDelegate import dev.sanmer.pi.model.LoadData import dev.sanmer.pi.model.LoadData.Default.loadData import dev.sanmer.su.AnySu @@ -33,15 +28,8 @@ class SuRepositoryImpl( } } - private fun IBinder.proxy() = state.value.getOrElse({ it.wrap(this) }) { this } + override val ownerPackageName: String + get() = state.value.getOrElse({ it.ownerPackageName }) { super.ownerPackageName } - override fun getAppOpsManager() = AppOpsManagerDelegate { proxy() } - - override fun getPackageManager() = PackageManagerDelegate { proxy() } - - override fun getPackageInstaller() = PackageInstallerDelegate { proxy() } - - override fun getPermissionManager() = PermissionManagerDelegate { proxy() } - - override fun getUserManager() = UserManagerDelegate { proxy() } + override fun wrap(original: IBinder) = state.value.getOrElse({ it.wrap(original) }) { original } } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/sanmer/pi/service/InstallService.kt b/app/src/main/kotlin/dev/sanmer/pi/service/InstallService.kt index aac90de5..b848c9f4 100644 --- a/app/src/main/kotlin/dev/sanmer/pi/service/InstallService.kt +++ b/app/src/main/kotlin/dev/sanmer/pi/service/InstallService.kt @@ -26,10 +26,10 @@ import dev.sanmer.pi.core.delegate.PackageInstallerDelegate import dev.sanmer.pi.core.delegate.PackageInstallerDelegate.Default.commit import dev.sanmer.pi.core.delegate.PackageInstallerDelegate.Default.writeFd import dev.sanmer.pi.core.delegate.PackageInstallerDelegate.Default.writeZip +import dev.sanmer.pi.core.delegate.PackageManagerDelegate import dev.sanmer.pi.core.parser.PackageInfoLite import dev.sanmer.pi.ktx.parcelable import dev.sanmer.pi.ktx.versionDisplay -import dev.sanmer.pi.repository.SuRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -39,15 +39,16 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.parcelize.Parcelize import org.koin.core.component.KoinComponent -import org.koin.core.component.inject +import org.koin.core.component.get +import org.koin.core.qualifier.named import kotlin.time.Duration.Companion.seconds import kotlin.time.TimeSource class InstallService : LifecycleService(), KoinComponent { - private val suRepository by inject() - private val pm by lazy { suRepository.getPackageManager() } - private val pi by lazy { suRepository.getPackageInstaller() } - private val nm by lazy { NotificationManagerCompat.from(this) } + private val ownerPackageName = get(named("ownerPackageName")) + private val packageManager = get() + private val packageInstaller = get() + private val notificationManager by lazy { NotificationManagerCompat.from(this) } private val runningMutex = Mutex() private val runningTask = mutableListOf() @@ -76,7 +77,7 @@ class InstallService : LifecycleService(), KoinComponent { id: Int, builder: NotificationCompat.Builder, block: NotificationCompat.Builder.() -> NotificationCompat.Builder - ) = nm.notify(id, builder.block().build()) + ) = notificationManager.notify(id, builder.block().build()) override fun onCreate() { logger.d("onCreate") @@ -190,11 +191,8 @@ class InstallService : LifecycleService(), KoinComponent { params.setAppPackageName(task.packageInfo.packageName) params.setOriginatingUri(task.uri) - val ownerPackageName = suRepository.state.value - .getOrElse({ it.ownerPackageName }) { "" } - - val session = pi.openSession( - pi.createSession( + val session = packageInstaller.openSession( + packageInstaller.createSession( params = params, installerPackageName = ownerPackageName.ifEmpty { task.installerPackageName }, userId = userId @@ -241,8 +239,8 @@ class InstallService : LifecycleService(), KoinComponent { private fun optimize(packageName: String) { runCatching { - pm.clearApplicationProfileData(packageName) - pm.performDexOpt(packageName) + packageManager.clearApplicationProfileData(packageName) + packageManager.performDexOpt(packageName) }.onFailure { logger.d(it) } @@ -272,7 +270,7 @@ class InstallService : LifecycleService(), KoinComponent { } private fun launch(packageName: String, userId: Int): PendingIntent? { - val intent = pm.getLaunchIntentForPackage(packageName, userId) ?: return null + val intent = packageManager.getLaunchIntentForPackage(packageName, userId) ?: return null return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE) } diff --git a/app/src/main/kotlin/dev/sanmer/pi/ui/main/MainViewModel.kt b/app/src/main/kotlin/dev/sanmer/pi/ui/main/MainViewModel.kt index e929c4e9..7a4f8955 100644 --- a/app/src/main/kotlin/dev/sanmer/pi/ui/main/MainViewModel.kt +++ b/app/src/main/kotlin/dev/sanmer/pi/ui/main/MainViewModel.kt @@ -16,6 +16,7 @@ import dev.sanmer.pi.Const import dev.sanmer.pi.Logger import dev.sanmer.pi.core.compat.ContextCompat.userId import dev.sanmer.pi.core.compat.UserHandleCompat +import dev.sanmer.pi.core.delegate.PackageManagerDelegate import dev.sanmer.pi.core.delegate.UserManagerDelegate import dev.sanmer.pi.core.parser.IPackageInfo import dev.sanmer.pi.core.parser.PackageInfoLite @@ -31,10 +32,10 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class MainViewModel( - private val suRepository: SuRepository + private val suRepository: SuRepository, + private val packageManager: PackageManagerDelegate, + private val userManager: UserManagerDelegate ) : ViewModel() { - private val pm by lazy { suRepository.getPackageManager() } - val uris = mutableStateListOf() private val packageInfos = mutableStateMapOf>() private val fileNames = hashMapOf>() @@ -58,9 +59,8 @@ class MainViewModel( when (it) { is LoadData.Success -> { runCatching { - val um = UserManagerDelegate { it.value.wrap(this) } users.clear() - users.addAll(um.getUsers()) + users.addAll(userManager.getUsers()) targetUsers.clear() targetUsers.add(UserHandleCompat.myUserId()) } @@ -80,7 +80,7 @@ class MainViewModel( private fun IPackageInfo.Apk.addCurrentPackageInfo(context: Context) = copy( currentPackageInfo = try { - pm.getPackageInfo( + packageManager.getPackageInfo( packageInfo.packageName, 0, context.userId ).let { PackageInfoLite.from(context, it) } } catch (_: Throwable) { diff --git a/build.gradle.kts b/build.gradle.kts index 84329df8..f5786012 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,8 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false - alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.ksp) apply false } - -tasks.register("clean") { - delete(layout.buildDirectory) -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3585924b..7770c53a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,10 +9,10 @@ androidxCoreSplashscreen = "1.2.0" androidxDocumentFile = "1.1.0" androidxLifecycle = "2.11.0" apacheCompress = "1.28.0" -appiconloader = "1.5.0" hiddenApiRefine = "4.4.0" hiddenApiBypass = "6.1" koin = "4.2.2" +koinPlugin = "1.2.1" kotlin = "2.4.20" kotlinxCoroutines = "1.11.0" ksp = "2.3.12" @@ -42,10 +42,13 @@ androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-ru androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "androidxLifecycle" } androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" } apache-commons-compress = { module = "org.apache.commons:commons-compress", version.ref = "apacheCompress" } -appiconloader-iconloaderlib = { module = "me.zhanghai.android.appiconloader:appiconloader-iconloaderlib", version.ref = "appiconloader" } +appiconloader-iconloaderlib = { module = "me.zhanghai.android.appiconloader:appiconloader-iconloaderlib", version = "1.5.0" } hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" } -koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } -koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } +koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin" } +koin-android = { module = "io.insert-koin:koin-android" } +koin-annotations = { module = "io.insert-koin:koin-annotations" } +koin-compose = { module = "io.insert-koin:koin-compose" } +koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" } @@ -64,7 +67,8 @@ self-compose = { id = "self.compose" } android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } -compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +koin = { id = "io.insert-koin.compiler.plugin", version.ref = "koinPlugin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }