Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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")
}
Expand Down Expand Up @@ -55,9 +55,6 @@ android {
}

packaging {
jniLibs.excludes += setOf(
"**/libdatastore_shared_counter.so"
)
resources.excludes += setOf(
"META-INF/**",
"kotlin/**",
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/kotlin/dev/sanmer/pi/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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)
}
}

Expand Down
39 changes: 35 additions & 4 deletions app/src/main/kotlin/dev/sanmer/pi/di/RepositoriesModule.kt
Original file line number Diff line number Diff line change
@@ -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<SuRepository>() }
val RepositoriesModule = module {
single<SuRepositoryImpl>() binds arrayOf(SuRepository::class, BinderWrapper::class)

single(named("ownerPackageName")) {
get<BinderWrapper>().ownerPackageName
}

single {
AppOpsManagerDelegate { get<BinderWrapper>().wrap(this) }
}

single {
PackageManagerDelegate { get<BinderWrapper>().wrap(this) }
}

single {
PackageInstallerDelegate { get<BinderWrapper>().wrap(this) }
}

single {
PermissionManagerDelegate { get<BinderWrapper>().wrap(this) }
}

single {
UserManagerDelegate { get<BinderWrapper>().wrap(this) }
}
}
7 changes: 4 additions & 3 deletions app/src/main/kotlin/dev/sanmer/pi/di/ViewModelsModule.kt
Original file line number Diff line number Diff line change
@@ -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<MainViewModel>()
}
12 changes: 1 addition & 11 deletions app/src/main/kotlin/dev/sanmer/pi/repository/SuRepository.kt
Original file line number Diff line number Diff line change
@@ -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<LoadData<BinderWrapper>>
suspend fun launch()
fun getAppOpsManager(): AppOpsManagerDelegate
fun getPackageManager(): PackageManagerDelegate
fun getPackageInstaller(): PackageInstallerDelegate
fun getPermissionManager(): PermissionManagerDelegate
fun getUserManager(): UserManagerDelegate
}
18 changes: 3 additions & 15 deletions app/src/main/kotlin/dev/sanmer/pi/repository/SuRepositoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
}
28 changes: 13 additions & 15 deletions app/src/main/kotlin/dev/sanmer/pi/service/InstallService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<SuRepository>()
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<String>(named("ownerPackageName"))
private val packageManager = get<PackageManagerDelegate>()
private val packageInstaller = get<PackageInstallerDelegate>()
private val notificationManager by lazy { NotificationManagerCompat.from(this) }

private val runningMutex = Mutex()
private val runningTask = mutableListOf<String>()
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}

Expand Down
12 changes: 6 additions & 6 deletions app/src/main/kotlin/dev/sanmer/pi/ui/main/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Uri>()
private val packageInfos = mutableStateMapOf<Uri, LoadData<IPackageInfo>>()
private val fileNames = hashMapOf<Uri, SnapshotStateList<String>>()
Expand All @@ -58,9 +59,8 @@ class MainViewModel(
when (it) {
is LoadData.Success<BinderWrapper> -> {
runCatching {
val um = UserManagerDelegate { it.value.wrap(this) }
users.clear()
users.addAll(um.getUsers())
users.addAll(userManager.getUsers())
targetUsers.clear()
targetUsers.add(UserHandleCompat.myUserId())
}
Expand All @@ -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) {
Expand Down
6 changes: 1 addition & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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<Delete>("clean") {
delete(layout.buildDirectory)
}
14 changes: 9 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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" }
Expand All @@ -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" }
Expand Down
Loading