Rewrite deny list UI

This commit is contained in:
vvb2060 2021-10-27 01:10:20 +08:00 committed by John Wu
parent 16322ab30c
commit 9126cf0c73
10 changed files with 131 additions and 365 deletions

View File

@ -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 {

View File

@ -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<CmdlineListItem>)
: ApplicationInfo(info), Comparable<AppProcessInfo> {
class AppProcessInfo(val applicationInfo: ApplicationInfo, pm: PackageManager,
denyList: List<CmdlineListItem>) : Comparable<AppProcessInfo> {
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<CmdlineListItem>
): List<ProcessInfo> {
// 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<out ComponentInfo>.processes() = map { createProcess(it.processName) }
fun Array<ServiceInfo>.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<out ComponentInfo>.processes() = map { createProcess(it.getProcName()) }
private fun Array<ServiceInfo>.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<ProcessInfo> {
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<ProcessInfo>()
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<AppProcessInfo>(
{ it.label.lowercase(currentLocale) },
{ it.packageName }
{ it.applicationInfo.packageName }
)
}
}

View File

@ -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<DenyListViewModel, FragmentDenyMd2Binding>() {
override val layoutRes = R.layout.fragment_deny_md2
override val viewModel by viewModel<DenyListViewModel>()
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<DenyListViewModel, FragmentDenyMd2Bindin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.filterToggle.setOnClickListener {
isFilterVisible = true
}
binding.appFilterInclude.filterDone.setOnClickListener {
isFilterVisible = false
}
binding.appList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState != RecyclerView.SCROLL_STATE_IDLE) hideKeyboard()
@ -66,36 +53,56 @@ class DenyListFragment : BaseUIFragment<DenyListViewModel, FragmentDenyMd2Bindin
bottom = l_50,
)
binding.appList.fixEdgeEffect()
val lama = binding.appList.layoutManager ?: return
lama.isAutoMeasureEnabled = false
}
override fun onPreBind(binding: FragmentDenyMd2Binding) = Unit
override fun onBackPressed(): Boolean {
if (isFilterVisible) {
isFilterVisible = false
if (searchView.isIconfiedByDefault && !searchView.isIconified) {
searchView.isIconified = true
return true
}
return super.onBackPressed()
}
// ---
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_hide_md2, menu)
inflater.inflate(R.menu.menu_deny_md2, menu)
searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView.queryHint = searchView.context.getString(R.string.hide_filter_hint)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
viewModel.query = query ?: ""
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
viewModel.query = newText ?: ""
return true
}
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_focus_up -> 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
}
}

View File

@ -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"
) }
}
}

View File

@ -1,18 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="24dp"
android:height="24dp"
android:viewportWidth="475.084"
android:viewportHeight="475.084">
<group
android:pivotX="237.542"
android:pivotY="237.542"
android:scaleX="0.8"
android:scaleY="0.8">
<path
android:fillColor="?colorOnSurface"
android:pathData="M464.524,412.846l-97.929,-97.925c23.6,-34.068 35.406,-72.047 35.406,-113.917c0,-27.218 -5.284,-53.249 -15.852,-78.087c-10.561,-24.842 -24.838,-46.254 -42.825,-64.241c-17.987,-17.987 -39.396,-32.264 -64.233,-42.826C254.246,5.285 228.217,0.003 200.999,0.003c-27.216,0 -53.247,5.282 -78.085,15.847C98.072,26.412 76.66,40.689 58.673,58.676c-17.989,17.987 -32.264,39.403 -42.827,64.241C5.282,147.758 0,173.786 0,201.004c0,27.216 5.282,53.238 15.846,78.083c10.562,24.838 24.838,46.247 42.827,64.234c17.987,17.993 39.403,32.264 64.241,42.832c24.841,10.563 50.869,15.844 78.085,15.844c41.879,0 79.852,-11.807 113.922,-35.405l97.929,97.641c6.852,7.231 15.406,10.849 25.693,10.849c9.897,0 18.467,-3.617 25.694,-10.849c7.23,-7.23 10.848,-15.796 10.848,-25.693C475.088,428.458 471.567,419.889 464.524,412.846zM291.363,291.358c-25.029,25.033 -55.148,37.549 -90.364,37.549c-35.21,0 -65.329,-12.519 -90.36,-37.549c-25.031,-25.029 -37.546,-55.144 -37.546,-90.36c0,-35.21 12.518,-65.334 37.546,-90.36c25.026,-25.032 55.15,-37.546 90.36,-37.546c35.212,0 65.331,12.519 90.364,37.546c25.033,25.026 37.548,55.15 37.548,90.36C328.911,236.214 316.392,266.329 291.363,291.358z"
tools:fillColor="@android:color/black" />
</group>
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?colorOnSurface"
android:pathData="M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.25,4.25c0.41,0.41 1.08,0.41 1.49,0 0.41,-0.41 0.41,-1.08 0,-1.49L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector>

View File

@ -1,166 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.topjohnwu.magisk.R" />
<variable
name="viewModel"
type="com.topjohnwu.magisk.ui.deny.DenyListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingStart="@dimen/l1"
android:paddingTop="@dimen/l1"
android:paddingEnd="@dimen/l1"
android:paddingBottom="@dimen/l1"
app:fitsSystemWindowsInsets="bottom"
tools:layout_gravity="bottom"
tools:paddingBottom="64dp">
<TextView
android:id="@+id/filter_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hide_filters"
android:textAllCaps="true"
android:textAppearance="@style/AppearanceFoundation.Caption"
android:textColor="?colorPrimary"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/hide_filter_chip_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l1"
app:chipSpacing="2dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filter_title">
<com.google.android.material.chip.Chip
android:id="@+id/hide_filter_system_chip"
style="@style/Widget.MaterialComponents.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={viewModel.showSystem}"
android:nextFocusRight="@id/hide_filter_chip_data"
android:nextFocusDown="@id/hide_filter_search_field"
android:text="@string/show_system_app"
android:textAppearance="@style/AppearanceFoundation.Caption"
app:checkedIcon="@drawable/ic_check_md2"
app:chipBackgroundColor="?colorSurfaceVariant"
tools:checked="true" />
<com.google.android.material.chip.Chip
android:id="@+id/hide_filter_os_chip"
style="@style/Widget.MaterialComponents.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={viewModel.showOS}"
android:nextFocusRight="@id/hide_filter_chip_data"
android:nextFocusDown="@id/hide_filter_search_field"
android:text="@string/show_os_app"
android:textAppearance="@style/AppearanceFoundation.Caption"
app:checkedIcon="@drawable/ic_check_md2"
app:chipBackgroundColor="?colorSurfaceVariant"
tools:checked="true" />
<com.google.android.material.chip.Chip
android:id="@+id/hide_filter_chip_data"
style="@style/Widget.MaterialComponents.Chip.Entry"
gone="@{viewModel.query.empty}"
onCloseClicked="@{() -> viewModel.resetQuery()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="false"
android:nextFocusRight="@id/filter_done"
android:nextFocusDown="@id/hide_filter_search_field"
android:text="@{viewModel.query}"
android:textAppearance="@style/AppearanceFoundation.Caption"
app:chipBackgroundColor="?colorSurfaceVariant" />
</com.google.android.material.chip.ChipGroup>
<TextView
android:id="@+id/hide_filter_title_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l1"
android:text="@string/hide_search"
android:textAllCaps="true"
android:textAppearance="@style/AppearanceFoundation.Caption"
android:textColor="?colorPrimary"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@+id/hide_filter_chip_group" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/hide_filter_search"
style="@style/WidgetFoundation.Card"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l_50"
app:cardCornerRadius="18dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/filter_done"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/hide_filter_title_search">
<ImageView
style="@style/WidgetFoundation.Icon"
android:layout_width="48dp"
android:layout_height="36dp"
android:layout_gravity="center_vertical|start"
android:padding="6dp"
app:srcCompat="@drawable/ic_search_md2"
app:tint="?colorDisabled" />
<EditText
android:id="@+id/hide_filter_search_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:background="@null"
android:gravity="start|center_vertical"
android:hint="@string/hide_filter_hint"
android:inputType="textUri"
android:minHeight="36dp"
android:nextFocusRight="@id/filter_done"
android:paddingStart="0dp"
android:paddingEnd="@dimen/l1"
android:singleLine="true"
android:text="@={viewModel.query}"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textColor="@color/color_text_transient"
android:textColorHint="?colorOnSurfaceVariant" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/filter_done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:nextFocusLeft="@id/hide_filter_system_chip"
app:backgroundTint="?colorPrimary"
app:elevation="0dp"
app:fabSize="mini"
app:layout_constraintBottom_toBottomOf="@+id/hide_filter_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/hide_filter_search"
app:srcCompat="@drawable/ic_check_md2"
app:tint="?colorOnPrimary" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -17,7 +17,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/app_list"
invisibleUnless="@{viewModel.loaded || !viewModel.items.empty}"
invisibleUnless="@{viewModel.loaded}"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
android:layout_width="match_parent"
@ -30,42 +30,8 @@
tools:listitem="@layout/item_hide_md2"
tools:paddingTop="40dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/filter_toggle"
invisibleUnless="@{viewModel.loaded || !viewModel.items.empty}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l1"
app:backgroundTint="?colorSurfaceSurfaceVariant"
app:layout_fitsSystemWindowsInsets="bottom"
app:srcCompat="@drawable/ic_search_md2"
app:tint="?colorPrimary"
tools:layout_marginBottom="64dp" />
<com.google.android.material.circularreveal.cardview.CircularRevealCardView
android:id="@+id/process_filter"
style="@style/WidgetFoundation.Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="invisible"
app:layout_fitsSystemWindowsInsets="bottom"
app:cardCornerRadius="0dp">
<include
android:id="@+id/app_filter_include"
layout="@layout/app_list_filter"
viewModel="@{viewModel}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.circularreveal.cardview.CircularRevealCardView>
<LinearLayout
goneUnless="@{viewModel.loading &amp;&amp; viewModel.items.empty}"
goneUnless="@{viewModel.loading}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"

View File

@ -70,7 +70,7 @@
android:id="@+id/hide_package"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{item.info.packageName}"
android:text="@{item.info.applicationInfo.packageName}"
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/hide_name"

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search_md2"
android:title="@string/hide_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_show_system"
android:checkable="true"
android:title="@string/show_system_app"
app:showAsAction="never" />
<item
android:id="@+id/action_show_OS"
android:checkable="true"
android:title="@string/show_os_app"
app:showAsAction="never" />
</menu>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_focus_up"
android:icon="@drawable/ic_up_md2"
android:title="@string/hide_scroll_up"
app:showAsAction="ifRoom" />
</menu>