____ _ _______ __
/ __ \ | /| / / __/ |/ /
/ /_/ / |/ |/ / _// /
\____/|__/|__/___/_||_/
owenclave / app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt
/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
******************************************************************************/
package io.nekohasekai.sagernet.utils
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.ktx.app
import io.nekohasekai.sagernet.ktx.listenForPackageChanges
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.atomic.AtomicBoolean
object PackageCache {
lateinit var installedPackages: Map
lateinit var installedApps: Map
lateinit var packageMap: Map
val uidMap = HashMap>()
val loaded = Mutex(true)
var registered = AtomicBoolean(false)
fun register() {
if (registered.getAndSet(true)) return
reload()
app.listenForPackageChanges(false) {
reload()
labelMap.clear()
}
loaded.unlock()
}
@SuppressLint("InlinedApi")
fun reload() {
installedPackages = if (DataStore.queryAllPackagesAlternativeMethod) {
app.packageManager.getPackagesHoldingPermissions(
arrayOf(Manifest.permission.INTERNET),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES
}
)
} else {
app.packageManager.getInstalledPackages(
PackageManager.GET_PERMISSIONS or
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
PackageManager.MATCH_UNINSTALLED_PACKAGES
} else {
@Suppress("DEPRECATION")
PackageManager.GET_UNINSTALLED_PACKAGES
}
).filter {
when (it.packageName) {
"android" -> true
else -> it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true
}
}
}.associateBy { it.packageName }
val installed = if (DataStore.queryAllPackagesAlternativeMethod) {
installedPackages.map { it.value.applicationInfo }
} else {
app.packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
}
installedApps = installed.associateBy { it.packageName }
packageMap = installed.associate { it.packageName to it.uid }
uidMap.clear()
for (info in installed) {
val uid = info.uid
uidMap.getOrPut(uid) { HashSet() }.add(info.packageName)
}
}
operator fun get(uid: Int) = uidMap[uid]
operator fun get(packageName: String) = packageMap[packageName]
fun awaitLoadSync() {
if (::packageMap.isInitialized) {
return
}
if (!registered.get()) {
register()
return
}
runBlocking {
loaded.withLock {
// just await
}
}
}
private val labelMap = mutableMapOf()
fun loadLabel(packageName: String): String {
var label = labelMap[packageName]
if (label != null) return label
val info = installedApps[packageName] ?: return packageName
label = info.loadLabel(app.packageManager).toString()
labelMap[packageName] = label
return label
}
}