diff --git a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt index e9e0c6fa8..875122013 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt @@ -9,9 +9,6 @@ import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.* -import android.content.pm.ServiceInfo -import android.content.pm.ServiceInfo.FLAG_ISOLATED_PROCESS -import android.content.pm.ServiceInfo.FLAG_USE_APP_ZYGOTE import android.content.res.Configuration import android.content.res.Resources import android.database.Cursor @@ -58,11 +55,6 @@ import kotlinx.coroutines.launch import java.io.File import java.lang.reflect.Array as JArray -val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0 - -@get:SuppressLint("InlinedApi") -val ServiceInfo.useAppZygote get() = (flags and FLAG_USE_APP_ZYGOTE) != 0 - fun Context.rawResource(id: Int) = resources.openRawResource(id) fun Context.getBitmap(id: Int): Bitmap { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt b/app/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt index a9cdd5ff6..43fb4ab29 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt @@ -10,8 +10,6 @@ import android.graphics.drawable.Drawable import android.os.Build.VERSION.SDK_INT import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.ktx.getLabel -import com.topjohnwu.magisk.ktx.isIsolated -import com.topjohnwu.magisk.ktx.useAppZygote class CmdlineListItem(line: String) { val packageName: String @@ -27,72 +25,63 @@ class CmdlineListItem(line: String) { const val ISOLATED_MAGIC = "isolated" @SuppressLint("InlinedApi") -class AppProcessInfo(info: ApplicationInfo, pm: PackageManager, denyList: List) - : ApplicationInfo(info), Comparable { +class AppProcessInfo(val applicationInfo: ApplicationInfo, pm: PackageManager, + denyList: List) : Comparable { - val label = info.getLabel(pm) - val iconImage: Drawable = info.loadIcon(pm) - val processes = fetchProcesses(pm, denyList) + private val denyList = denyList.filter { + it.packageName == applicationInfo.packageName || it.packageName == ISOLATED_MAGIC + } + val label = applicationInfo.getLabel(pm) + val iconImage: Drawable = applicationInfo.loadIcon(pm) + val processes = fetchProcesses(pm) override fun compareTo(other: AppProcessInfo) = comparator.compare(this, other) - private fun fetchProcesses( - pm: PackageManager, - denylist: List - ): List { - // Fetch full PackageInfo - val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES - val packageInfo = try { - val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS - pm.getPackageInfo(packageName, baseFlag or request) - } catch (e: NameNotFoundException) { - // EdXposed hooked, issue#3276 - return emptyList() - } catch (e: Exception) { - // Exceed binder data transfer limit, fetch each component type separately - pm.getPackageInfo(packageName, baseFlag).apply { - runCatching { activities = pm.getPackageInfo(packageName, baseFlag or GET_ACTIVITIES).activities } - runCatching { services = pm.getPackageInfo(packageName, baseFlag or GET_SERVICES).services } - runCatching { receivers = pm.getPackageInfo(packageName, baseFlag or GET_RECEIVERS).receivers } - runCatching { providers = pm.getPackageInfo(packageName, baseFlag or GET_PROVIDERS).providers } - } - } + private fun createProcess(name: String, pkg: String = applicationInfo.packageName) = + ProcessInfo(name, pkg, denyList.any { it.process == name && it.packageName == pkg }) - val enabledList = denylist.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC } - fun createProcess(name: String, pkg: String = packageName): ProcessInfo { - return ProcessInfo(name, pkg, enabledList.any { it.process == name && it.packageName == pkg }) - } + private fun ComponentInfo.getProcName(): String = processName + ?: applicationInfo.processName + ?: applicationInfo.packageName - var haveAppZygote = false - fun Array.processes() = map { createProcess(it.processName) } - fun Array.processes() = map { - if (it.isIsolated) { - if (it.useAppZygote) { - haveAppZygote = true - // Using app zygote, don't need to track the process - null - } else { - val proc = if (SDK_INT >= 29) "${it.processName}:${it.name}" else it.processName - createProcess(proc, ISOLATED_MAGIC) - } + private fun Array.processes() = map { createProcess(it.getProcName()) } + + private fun Array.processes() = map { + if ((it.flags and ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) { + if ((it.flags and ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) { + val proc = applicationInfo.processName ?: applicationInfo.packageName + createProcess("${proc}_zygote") } else { - createProcess(it.processName) + val proc = if (SDK_INT >= 29) "${it.getProcName()}:${it.name}" else it.getProcName() + createProcess(proc, ISOLATED_MAGIC) } + } else { + createProcess(it.getProcName()) + } + } + + private fun fetchProcesses(pm: PackageManager): List { + val flag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES or + GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS + val packageInfo = try { + pm.getPackageInfo(applicationInfo.packageName, flag) + } catch (e: Exception) { + // Exceed binder data transfer limit, local parsing package + pm.getPackageArchiveInfo(applicationInfo.sourceDir, flag) ?: return emptyList() } - return with(packageInfo) { - activities?.processes().orEmpty() + - services?.processes().orEmpty() + - receivers?.processes().orEmpty() + - providers?.processes().orEmpty() + - listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null) - }.filterNotNull().distinct().sortedBy { it.name } + val list = LinkedHashSet() + list += packageInfo.activities?.processes().orEmpty() + list += packageInfo.services?.processes().orEmpty() + list += packageInfo.receivers?.processes().orEmpty() + list += packageInfo.providers?.processes().orEmpty() + return list.sortedBy { it.name } } companion object { private val comparator = compareBy( { it.label.lowercase(currentLocale) }, - { it.packageName } + { it.applicationInfo.packageName } ) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListFragment.kt index bc129428c..d4bfff31d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListFragment.kt @@ -6,8 +6,7 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.RecyclerView import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseUIFragment @@ -17,19 +16,13 @@ import com.topjohnwu.magisk.ktx.addSimpleItemDecoration import com.topjohnwu.magisk.ktx.addVerticalPadding import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ktx.hideKeyboard -import com.topjohnwu.magisk.utils.MotionRevealHelper class DenyListFragment : BaseUIFragment() { override val layoutRes = R.layout.fragment_deny_md2 override val viewModel by viewModel() - private var isFilterVisible - get() = binding.processFilter.isVisible - set(value) { - if (!value) hideKeyboard() - MotionRevealHelper.withViews(binding.processFilter, binding.filterToggle, value) - } + private lateinit var searchView: SearchView override fun onAttach(context: Context) { super.onAttach(context) @@ -40,12 +33,6 @@ class DenyListFragment : BaseUIFragment binding.appList - .takeIf { (it.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() ?: 0 > 10 } - ?.also { it.scrollToPosition(10) } - .let { binding.appList } - .also { it.post { it.smoothScrollToPosition(0) } } + R.id.action_show_system -> { + val check = !item.isChecked + viewModel.isShowSystem = check + item.isChecked = check + return true + } + R.id.action_show_OS -> { + val check = !item.isChecked + viewModel.isShowOS = check + item.isChecked = check + return true + } } return super.onOptionsItemSelected(item) } + override fun onPrepareOptionsMenu(menu: Menu) { + val showSystem = menu.findItem(R.id.action_show_system) + val showOS = menu.findItem(R.id.action_show_OS) + showOS.isEnabled = showSystem.isChecked + } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt index 2c76a83cc..747dfcc4e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt @@ -4,42 +4,39 @@ import android.annotation.SuppressLint import android.content.pm.ApplicationInfo 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.core.Config import com.topjohnwu.magisk.databinding.filterableListOf import com.topjohnwu.magisk.databinding.itemBindingOf -import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.di.AppContext import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.superuser.Shell import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.util.stream.Collectors class DenyListViewModel : BaseViewModel(), Queryable { - override val queryDelay = 1000L + override val queryDelay = 0L - @get:Bindable - var isShowSystem = Config.showSystemApp - set(value) = set(value, field, { field = it }, BR.showSystem) { - Config.showSystemApp = it + var isShowSystem = false + set(value) { + field = value submitQuery() } - @get:Bindable var isShowOS = false - set(value) = set(value, field, { field = it }, BR.showOS) { + set(value) { + field = value submitQuery() } - @get:Bindable var query = "" - set(value) = set(value, field, { field = it }, BR.query) { + set(value) { + field = value submitQuery() } @@ -60,64 +57,43 @@ class DenyListViewModel : BaseViewModel(), Queryable { state = State.LOADING val (apps, diff) = withContext(Dispatchers.Default) { val pm = AppContext.packageManager - val hideList = Shell.su("magisk --denylist ls").exec().out.map { CmdlineListItem(it) } - val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES) - .asSequence() - .filterNot { blacklist.contains(it.packageName) } - .map { AppProcessInfo(it, pm, hideList) } + val denyList = Shell.su("magisk --denylist ls").exec().out + .map { CmdlineListItem(it) } + val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES).parallelStream() + .filter { AppContext.packageName != it.packageName } + .map { AppProcessInfo(it, pm, denyList) } .filter { it.processes.isNotEmpty() } - .filter { info -> info.enabled || info.processes.any { it.isEnabled } } .map { DenyListRvItem(it) } - .toList() .sorted() + .collect(Collectors.toList()) apps to items.calculateDiff(apps) } items.update(apps, diff) submitQuery() } - // --- - override fun query() { + fun isApp(uid: Int) = run { + val appId: Int = uid % 100000 + appId >= Process.FIRST_APPLICATION_UID && appId <= Process.LAST_APPLICATION_UID + } + + fun isSystemApp(flag: Int) = flag and ApplicationInfo.FLAG_SYSTEM != 0 + items.filter { - fun showHidden() = it.itemsChecked != 0 + fun filterSystem() = isShowSystem || !isSystemApp(it.info.applicationInfo.flags) - 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 filterOS() = (isShowSystem && isShowOS) || isApp(it.info.applicationInfo.uid) fun filterQuery(): Boolean { fun inName() = it.info.label.contains(query, true) - fun inPackage() = it.info.packageName.contains(query, true) + fun inPackage() = it.info.applicationInfo.packageName.contains(query, true) fun inProcesses() = it.processes.any { p -> p.process.name.contains(query, true) } return inName() || inPackage() || inProcesses() } - showHidden() || (filterSystem() && filterOS() && filterQuery()) + filterSystem() && filterOS() && filterQuery() } state = State.LOADED } - - // --- - - fun resetQuery() { - query = "" - } - - companion object { - private val blacklist by lazy { listOf( - AppContext.packageName, - "com.android.chrome", - "com.chrome.beta", - "com.chrome.dev", - "com.chrome.canary", - "com.android.webview", - "com.google.android.webview" - ) } - } } diff --git a/app/src/main/res/drawable/ic_search_md2.xml b/app/src/main/res/drawable/ic_search_md2.xml index 626a6c3ce..d290daebb 100644 --- a/app/src/main/res/drawable/ic_search_md2.xml +++ b/app/src/main/res/drawable/ic_search_md2.xml @@ -1,18 +1,9 @@ - - - - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/layout/app_list_filter.xml b/app/src/main/res/layout/app_list_filter.xml deleted file mode 100644 index e48344a34..000000000 --- a/app/src/main/res/layout/app_list_filter.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_deny_md2.xml b/app/src/main/res/layout/fragment_deny_md2.xml index 8a28e88ce..1766d513b 100644 --- a/app/src/main/res/layout/fragment_deny_md2.xml +++ b/app/src/main/res/layout/fragment_deny_md2.xml @@ -17,7 +17,7 @@ - - - - - - - - + + + + + diff --git a/app/src/main/res/menu/menu_hide_md2.xml b/app/src/main/res/menu/menu_hide_md2.xml deleted file mode 100644 index 8b4ab146d..000000000 --- a/app/src/main/res/menu/menu_hide_md2.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file