Magisk/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt

126 lines
4.0 KiB
Kotlin

package com.topjohnwu.magisk.ui.hide
import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import android.os.Process
import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.Queryable
import com.topjohnwu.magisk.arch.filterableListOf
import com.topjohnwu.magisk.arch.itemBindingOf
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.packageName
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class HideViewModel : BaseViewModel(), Queryable {
override val queryDelay = 1000L
@get:Bindable
var isShowSystem = Config.showSystemApp
set(value) = set(value, field, { field = it }, BR.showSystem) {
Config.showSystemApp = it
submitQuery()
}
@get:Bindable
var isShowOS = false
set(value) = set(value, field, { field = it }, BR.showOS) {
submitQuery()
}
@get:Bindable
var query = ""
set(value) = set(value, field, { field = it }, BR.query) {
submitQuery()
}
val items = filterableListOf<HideRvItem>()
val itemBinding = itemBindingOf<HideRvItem> {
it.bindExtra(BR.viewModel, this)
}
val itemInternalBinding = itemBindingOf<HideProcessRvItem> {
it.bindExtra(BR.viewModel, this)
}
@SuppressLint("InlinedApi")
override fun refresh() = viewModelScope.launch {
if (!Utils.showSuperUser()) {
state = State.LOADING_FAILED
return@launch
}
state = State.LOADING
val (apps, diff) = withContext(Dispatchers.Default) {
val pm = get<PackageManager>()
val hideList = Shell.su("magiskhide ls").exec().out.map { CmdlineHiddenItem(it) }
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES)
.asSequence()
.filterNot { blacklist.contains(it.packageName) }
.map { HideAppInfo(it, pm, hideList) }
.filter { it.processes.isNotEmpty() }
.filter { info -> info.enabled || info.processes.any { it.isHidden } }
.map { HideRvItem(it) }
.toList()
.sorted()
apps to items.calculateDiff(apps)
}
items.update(apps, diff)
submitQuery()
}
// ---
override fun query() {
items.filter {
fun showHidden() = it.itemsChecked != 0
fun filterSystem() = isShowSystem || it.info.flags and ApplicationInfo.FLAG_SYSTEM == 0
fun isApp(uid: Int) = run {
val appId: Int = uid % 100000
appId >= Process.FIRST_APPLICATION_UID && appId <= Process.LAST_APPLICATION_UID
}
fun filterOS() = (isShowSystem && isShowOS) || isApp(it.info.uid)
fun filterQuery(): Boolean {
fun inName() = it.info.label.contains(query, true)
fun inPackage() = it.info.packageName.contains(query, true)
fun inProcesses() = it.processes.any { p -> p.process.name.contains(query, true) }
return inName() || inPackage() || inProcesses()
}
showHidden() || (filterSystem() && filterOS() && filterQuery())
}
state = State.LOADED
}
// ---
fun resetQuery() {
query = ""
}
companion object {
private val blacklist by lazy { listOf(
packageName,
"com.android.chrome",
"com.chrome.beta",
"com.chrome.dev",
"com.chrome.canary",
"com.android.webview",
"com.google.android.webview"
) }
}
}