package com.github.kr328.clash import android.Manifest.permission.INTERNET import android.content.ClipData import android.content.ClipboardManager import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import androidx.core.content.getSystemService import com.github.kr328.clash.design.AccessControlDesign import com.github.kr328.clash.design.model.AppInfo import com.github.kr328.clash.design.util.toAppInfo import com.github.kr328.clash.service.store.ServiceStore import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.selects.select import kotlinx.coroutines.withContext class AccessControlActivity : BaseActivity() { override suspend fun main() { val service = ServiceStore(this) val selected = withContext(Dispatchers.IO) { service.accessControlPackages.toMutableSet() } defer { withContext(Dispatchers.IO) { service.accessControlPackages = selected } } val design = AccessControlDesign(this, uiStore, selected) setContentDesign(design) design.requests.send(AccessControlDesign.Request.ReloadApps) while (isActive) { select { events.onReceive { } design.requests.onReceive { when (it) { AccessControlDesign.Request.ReloadApps -> { design.patchApps(loadApps(selected)) } AccessControlDesign.Request.SelectAll -> { val all = withContext(Dispatchers.Default) { design.apps.map(AppInfo::packageName) } selected.clear() selected.addAll(all) design.rebindAll() } AccessControlDesign.Request.SelectNone -> { selected.clear() design.rebindAll() } AccessControlDesign.Request.SelectInvert -> { val all = withContext(Dispatchers.Default) { design.apps.map(AppInfo::packageName).toSet() - selected } selected.clear() selected.addAll(all) design.rebindAll() } AccessControlDesign.Request.Import -> { val clipboard = getSystemService() val data = clipboard?.primaryClip if (data != null && data.itemCount > 0) { val packages = data.getItemAt(0).text.split("\n").toSet() val all = design.apps.map(AppInfo::packageName).intersect(packages) selected.clear() selected.addAll(all) } design.rebindAll() } AccessControlDesign.Request.Export -> { val clipboard = getSystemService() val data = ClipData.newPlainText( "packages", selected.joinToString("\n") ) clipboard?.setPrimaryClip(data) } } } } } } private suspend fun loadApps(selected: Set): List = withContext(Dispatchers.IO) { val reverse = uiStore.accessControlReverse val sort = uiStore.accessControlSort val systemApp = uiStore.accessControlSystemApp val base = compareByDescending { it.packageName in selected } val comparator = if (reverse) base.thenDescending(sort) else base.then(sort) val pm = packageManager val packages = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS) packages.asSequence() .filter { it.packageName != packageName } .filter { it.packageName == "android" || it.requestedPermissions?.contains(INTERNET) == true } .filter { systemApp || !it.isSystemApp } .map { it.toAppInfo(pm) } .sortedWith(comparator) .toList() } private val PackageInfo.isSystemApp: Boolean get() { return applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0 } }