owenclave / app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt

github - dev - back to tree - upstream
/****************************************************************************** * * * 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 } }