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.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
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.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
@ -58,11 +55,6 @@ import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.lang.reflect.Array as JArray 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.rawResource(id: Int) = resources.openRawResource(id)
fun Context.getBitmap(id: Int): Bitmap { 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 android.os.Build.VERSION.SDK_INT
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.ktx.getLabel import com.topjohnwu.magisk.ktx.getLabel
import com.topjohnwu.magisk.ktx.isIsolated
import com.topjohnwu.magisk.ktx.useAppZygote
class CmdlineListItem(line: String) { class CmdlineListItem(line: String) {
val packageName: String val packageName: String
@ -27,72 +25,63 @@ class CmdlineListItem(line: String) {
const val ISOLATED_MAGIC = "isolated" const val ISOLATED_MAGIC = "isolated"
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
class AppProcessInfo(info: ApplicationInfo, pm: PackageManager, denyList: List<CmdlineListItem>) class AppProcessInfo(val applicationInfo: ApplicationInfo, pm: PackageManager,
: ApplicationInfo(info), Comparable<AppProcessInfo> { denyList: List<CmdlineListItem>) : Comparable<AppProcessInfo> {
val label = info.getLabel(pm) private val denyList = denyList.filter {
val iconImage: Drawable = info.loadIcon(pm) it.packageName == applicationInfo.packageName || it.packageName == ISOLATED_MAGIC
val processes = fetchProcesses(pm, denyList) }
val label = applicationInfo.getLabel(pm)
val iconImage: Drawable = applicationInfo.loadIcon(pm)
val processes = fetchProcesses(pm)
override fun compareTo(other: AppProcessInfo) = comparator.compare(this, other) override fun compareTo(other: AppProcessInfo) = comparator.compare(this, other)
private fun fetchProcesses( private fun createProcess(name: String, pkg: String = applicationInfo.packageName) =
pm: PackageManager, ProcessInfo(name, pkg, denyList.any { it.process == name && it.packageName == pkg })
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 }
}
}
val enabledList = denylist.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC } private fun ComponentInfo.getProcName(): String = processName
fun createProcess(name: String, pkg: String = packageName): ProcessInfo { ?: applicationInfo.processName
return ProcessInfo(name, pkg, enabledList.any { it.process == name && it.packageName == pkg }) ?: applicationInfo.packageName
}
var haveAppZygote = false private fun Array<out ComponentInfo>.processes() = map { createProcess(it.getProcName()) }
fun Array<out ComponentInfo>.processes() = map { createProcess(it.processName) }
fun Array<ServiceInfo>.processes() = map { private fun Array<ServiceInfo>.processes() = map {
if (it.isIsolated) { if ((it.flags and ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
if (it.useAppZygote) { if ((it.flags and ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
haveAppZygote = true val proc = applicationInfo.processName ?: applicationInfo.packageName
// Using app zygote, don't need to track the process createProcess("${proc}_zygote")
null
} else {
val proc = if (SDK_INT >= 29) "${it.processName}:${it.name}" else it.processName
createProcess(proc, ISOLATED_MAGIC)
}
} else { } 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) { val list = LinkedHashSet<ProcessInfo>()
activities?.processes().orEmpty() + list += packageInfo.activities?.processes().orEmpty()
services?.processes().orEmpty() + list += packageInfo.services?.processes().orEmpty()
receivers?.processes().orEmpty() + list += packageInfo.receivers?.processes().orEmpty()
providers?.processes().orEmpty() + list += packageInfo.providers?.processes().orEmpty()
listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null) return list.sortedBy { it.name }
}.filterNotNull().distinct().sortedBy { it.name }
} }
companion object { companion object {
private val comparator = compareBy<AppProcessInfo>( private val comparator = compareBy<AppProcessInfo>(
{ it.label.lowercase(currentLocale) }, { 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.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment 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.addVerticalPadding
import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ktx.fixEdgeEffect
import com.topjohnwu.magisk.ktx.hideKeyboard import com.topjohnwu.magisk.ktx.hideKeyboard
import com.topjohnwu.magisk.utils.MotionRevealHelper
class DenyListFragment : BaseUIFragment<DenyListViewModel, FragmentDenyMd2Binding>() { class DenyListFragment : BaseUIFragment<DenyListViewModel, FragmentDenyMd2Binding>() {
override val layoutRes = R.layout.fragment_deny_md2 override val layoutRes = R.layout.fragment_deny_md2
override val viewModel by viewModel<DenyListViewModel>() override val viewModel by viewModel<DenyListViewModel>()
private var isFilterVisible private lateinit var searchView: SearchView
get() = binding.processFilter.isVisible
set(value) {
if (!value) hideKeyboard()
MotionRevealHelper.withViews(binding.processFilter, binding.filterToggle, value)
}
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
@ -40,12 +33,6 @@ class DenyListFragment : BaseUIFragment<DenyListViewModel, FragmentDenyMd2Bindin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.filterToggle.setOnClickListener {
isFilterVisible = true
}
binding.appFilterInclude.filterDone.setOnClickListener {
isFilterVisible = false
}
binding.appList.addOnScrollListener(object : RecyclerView.OnScrollListener() { binding.appList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState != RecyclerView.SCROLL_STATE_IDLE) hideKeyboard() if (newState != RecyclerView.SCROLL_STATE_IDLE) hideKeyboard()
@ -66,36 +53,56 @@ class DenyListFragment : BaseUIFragment<DenyListViewModel, FragmentDenyMd2Bindin
bottom = l_50, bottom = l_50,
) )
binding.appList.fixEdgeEffect() binding.appList.fixEdgeEffect()
val lama = binding.appList.layoutManager ?: return
lama.isAutoMeasureEnabled = false
} }
override fun onPreBind(binding: FragmentDenyMd2Binding) = Unit override fun onPreBind(binding: FragmentDenyMd2Binding) = Unit
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {
if (isFilterVisible) { if (searchView.isIconfiedByDefault && !searchView.isIconified) {
isFilterVisible = false searchView.isIconified = true
return true return true
} }
return super.onBackPressed() return super.onBackPressed()
} }
// ---
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 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 { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_focus_up -> binding.appList R.id.action_show_system -> {
.takeIf { (it.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() ?: 0 > 10 } val check = !item.isChecked
?.also { it.scrollToPosition(10) } viewModel.isShowSystem = check
.let { binding.appList } item.isChecked = check
.also { it.post { it.smoothScrollToPosition(0) } } return true
}
R.id.action_show_OS -> {
val check = !item.isChecked
viewModel.isShowOS = check
item.isChecked = check
return true
}
} }
return super.onOptionsItemSelected(item) 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.ApplicationInfo
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import android.os.Process import android.os.Process
import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.Queryable import com.topjohnwu.magisk.arch.Queryable
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.databinding.filterableListOf import com.topjohnwu.magisk.databinding.filterableListOf
import com.topjohnwu.magisk.databinding.itemBindingOf import com.topjohnwu.magisk.databinding.itemBindingOf
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.di.AppContext import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.stream.Collectors
class DenyListViewModel : BaseViewModel(), Queryable { class DenyListViewModel : BaseViewModel(), Queryable {
override val queryDelay = 1000L override val queryDelay = 0L
@get:Bindable var isShowSystem = false
var isShowSystem = Config.showSystemApp set(value) {
set(value) = set(value, field, { field = it }, BR.showSystem) { field = value
Config.showSystemApp = it
submitQuery() submitQuery()
} }
@get:Bindable
var isShowOS = false var isShowOS = false
set(value) = set(value, field, { field = it }, BR.showOS) { set(value) {
field = value
submitQuery() submitQuery()
} }
@get:Bindable
var query = "" var query = ""
set(value) = set(value, field, { field = it }, BR.query) { set(value) {
field = value
submitQuery() submitQuery()
} }
@ -60,64 +57,43 @@ class DenyListViewModel : BaseViewModel(), Queryable {
state = State.LOADING state = State.LOADING
val (apps, diff) = withContext(Dispatchers.Default) { val (apps, diff) = withContext(Dispatchers.Default) {
val pm = AppContext.packageManager val pm = AppContext.packageManager
val hideList = Shell.su("magisk --denylist ls").exec().out.map { CmdlineListItem(it) } val denyList = Shell.su("magisk --denylist ls").exec().out
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES) .map { CmdlineListItem(it) }
.asSequence() val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES).parallelStream()
.filterNot { blacklist.contains(it.packageName) } .filter { AppContext.packageName != it.packageName }
.map { AppProcessInfo(it, pm, hideList) } .map { AppProcessInfo(it, pm, denyList) }
.filter { it.processes.isNotEmpty() } .filter { it.processes.isNotEmpty() }
.filter { info -> info.enabled || info.processes.any { it.isEnabled } }
.map { DenyListRvItem(it) } .map { DenyListRvItem(it) }
.toList()
.sorted() .sorted()
.collect(Collectors.toList())
apps to items.calculateDiff(apps) apps to items.calculateDiff(apps)
} }
items.update(apps, diff) items.update(apps, diff)
submitQuery() submitQuery()
} }
// ---
override fun query() { 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 { 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 filterOS() = (isShowSystem && isShowOS) || isApp(it.info.applicationInfo.uid)
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 filterQuery(): Boolean { fun filterQuery(): Boolean {
fun inName() = it.info.label.contains(query, true) 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) } fun inProcesses() = it.processes.any { p -> p.process.name.contains(query, true) }
return inName() || inPackage() || inProcesses() return inName() || inPackage() || inProcesses()
} }
showHidden() || (filterSystem() && filterOS() && filterQuery()) filterSystem() && filterOS() && filterQuery()
} }
state = State.LOADED 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" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="475.084" android:viewportWidth="24"
android:viewportHeight="475.084"> android:viewportHeight="24">
<path
<group android:fillColor="?colorOnSurface"
android:pivotX="237.542" 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" />
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>
</vector> </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 <androidx.recyclerview.widget.RecyclerView
android:id="@+id/app_list" android:id="@+id/app_list"
invisibleUnless="@{viewModel.loaded || !viewModel.items.empty}" invisibleUnless="@{viewModel.loaded}"
itemBinding="@{viewModel.itemBinding}" itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}" items="@{viewModel.items}"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -30,42 +30,8 @@
tools:listitem="@layout/item_hide_md2" tools:listitem="@layout/item_hide_md2"
tools:paddingTop="40dp" /> 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 <LinearLayout
goneUnless="@{viewModel.loading &amp;&amp; viewModel.items.empty}" goneUnless="@{viewModel.loading}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"

View File

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