mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-26 00:57:38 +00:00
Cleanup hide fragment code
This commit is contained in:
parent
b44dcc2da0
commit
84f92bd661
@ -1,23 +1,13 @@
|
||||
package com.topjohnwu.magisk.data.repository
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||
import com.topjohnwu.magisk.ktx.await
|
||||
import com.topjohnwu.magisk.ktx.getLabel
|
||||
import com.topjohnwu.magisk.ktx.packageName
|
||||
import com.topjohnwu.magisk.ui.hide.HideAppInfo
|
||||
import com.topjohnwu.magisk.ui.hide.HideTarget
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
class MagiskRepository(
|
||||
private val apiRaw: GithubRawServices,
|
||||
private val packageManager: PackageManager
|
||||
private val apiRaw: GithubRawServices
|
||||
) {
|
||||
|
||||
suspend fun fetchUpdate() = try {
|
||||
@ -40,35 +30,4 @@ class MagiskRepository(
|
||||
null
|
||||
}
|
||||
|
||||
suspend fun fetchApps() = withContext(Dispatchers.Default) {
|
||||
packageManager.getInstalledApplications(0).filter {
|
||||
it.enabled && !blacklist.contains(it.packageName)
|
||||
}.map {
|
||||
val label = it.getLabel(packageManager)
|
||||
val icon = it.loadIcon(packageManager)
|
||||
HideAppInfo(it, label, icon)
|
||||
}.filter { it.processes.isNotEmpty() }
|
||||
}
|
||||
|
||||
suspend fun fetchHideTargets() =
|
||||
Shell.su("magiskhide --ls").await().out.map { HideTarget(it) }
|
||||
|
||||
fun toggleHide(isEnabled: Boolean, packageName: String, process: String) =
|
||||
Shell.su("magiskhide --${isEnabled.state} $packageName $process").submit()
|
||||
|
||||
private val Boolean.state get() = if (this) "add" else "rm"
|
||||
|
||||
companion object {
|
||||
private val blacklist by lazy { listOf(
|
||||
packageName,
|
||||
"android",
|
||||
"com.android.chrome",
|
||||
"com.chrome.beta",
|
||||
"com.chrome.dev",
|
||||
"com.chrome.canary",
|
||||
"com.android.webview",
|
||||
"com.google.android.webview"
|
||||
) }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,10 +28,17 @@ abstract class RvItem {
|
||||
|
||||
abstract class ComparableRvItem<in T> : RvItem() {
|
||||
|
||||
abstract fun itemSameAs(other: T): Boolean
|
||||
abstract fun contentSameAs(other: T): Boolean
|
||||
// Use Any.equals by default
|
||||
open fun itemSameAs(other: T) = this == other
|
||||
|
||||
// Use compareTo if this is Comparable or assume not same
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
open fun contentSameAs(other: T) =
|
||||
(this as? Comparable<T>)?.run { compareTo(other) == 0 } ?: false
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
open fun genericItemSameAs(other: Any): Boolean = other::class == this::class && itemSameAs(other as T)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
open fun genericContentSameAs(other: Any): Boolean = other::class == this::class && contentSameAs(other as T)
|
||||
|
||||
|
@ -7,7 +7,7 @@ import org.koin.dsl.module
|
||||
|
||||
|
||||
val repositoryModule = module {
|
||||
single { MagiskRepository(get(), get()) }
|
||||
single { MagiskRepository(get()) }
|
||||
single { LogRepository(get()) }
|
||||
single { StringRepository(get()) }
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val viewModelModules = module {
|
||||
viewModel { HideViewModel(get()) }
|
||||
viewModel { HideViewModel() }
|
||||
viewModel { HomeViewModel(get()) }
|
||||
viewModel { LogViewModel(get()) }
|
||||
viewModel { ModuleViewModel(get(), get(), get()) }
|
||||
|
@ -65,40 +65,22 @@ val PackageInfo.processes
|
||||
|
||||
val Array<out ComponentInfo>.processNames get() = mapNotNull { it.processName }
|
||||
|
||||
val ApplicationInfo.packageInfo: PackageInfo?
|
||||
get() {
|
||||
val pm: PackageManager by inject()
|
||||
val ApplicationInfo.packageInfo: PackageInfo get() {
|
||||
val pm = get<PackageManager>()
|
||||
|
||||
return try {
|
||||
val request = GET_ACTIVITIES or
|
||||
GET_SERVICES or
|
||||
GET_RECEIVERS or
|
||||
GET_PROVIDERS
|
||||
val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
|
||||
pm.getPackageInfo(packageName, request)
|
||||
} catch (e1: Exception) {
|
||||
try {
|
||||
pm.activities(packageName).apply {
|
||||
services = pm.services(packageName)
|
||||
receivers = pm.receivers(packageName)
|
||||
providers = pm.providers(packageName)
|
||||
}
|
||||
} catch (e2: Exception) {
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
// Exceed binder data transfer limit, fetch each component type separately
|
||||
pm.getPackageInfo(packageName, 0).apply {
|
||||
runCatching { activities = pm.getPackageInfo(packageName, GET_ACTIVITIES).activities }
|
||||
runCatching { services = pm.getPackageInfo(packageName, GET_SERVICES).services }
|
||||
runCatching { receivers = pm.getPackageInfo(packageName, GET_RECEIVERS).receivers }
|
||||
runCatching { providers = pm.getPackageInfo(packageName, GET_PROVIDERS).providers }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PackageManager.activities(packageName: String) =
|
||||
getPackageInfo(packageName, GET_ACTIVITIES)
|
||||
|
||||
fun PackageManager.services(packageName: String) =
|
||||
getPackageInfo(packageName, GET_SERVICES).services
|
||||
|
||||
fun PackageManager.receivers(packageName: String) =
|
||||
getPackageInfo(packageName, GET_RECEIVERS).receivers
|
||||
|
||||
fun PackageManager.providers(packageName: String) =
|
||||
getPackageInfo(packageName, GET_PROVIDERS).providers
|
||||
}
|
||||
|
||||
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
||||
|
||||
|
47
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideApp.kt
Normal file
47
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideApp.kt
Normal file
@ -0,0 +1,47 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.ktx.getLabel
|
||||
|
||||
class HideTarget(line: String) {
|
||||
val packageName: String
|
||||
val process: String
|
||||
|
||||
init {
|
||||
val split = line.split(Regex("\\|"), 2)
|
||||
packageName = split[0]
|
||||
process = split.getOrElse(1) { packageName }
|
||||
}
|
||||
}
|
||||
|
||||
class HideAppInfo(info: ApplicationInfo, pm: PackageManager)
|
||||
: ApplicationInfo(info), Comparable<HideAppInfo> {
|
||||
|
||||
val label = info.getLabel(pm)
|
||||
val iconImage: Drawable = info.loadIcon(pm)
|
||||
|
||||
override fun compareTo(other: HideAppInfo) = comparator.compare(this, other)
|
||||
|
||||
companion object {
|
||||
private val comparator = compareBy<HideAppInfo>(
|
||||
{ it.label.toLowerCase(currentLocale) },
|
||||
{ it.packageName }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class HideProcessInfo(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
val isHidden: Boolean
|
||||
)
|
||||
|
||||
class HideAppTarget(
|
||||
val info: HideAppInfo,
|
||||
val processes: List<HideProcessInfo>
|
||||
) : Comparable<HideAppTarget> {
|
||||
override fun compareTo(other: HideAppTarget) = compareValuesBy(this, other) { it.info }
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.topjohnwu.magisk.ktx.packageInfo
|
||||
import com.topjohnwu.magisk.ktx.processes
|
||||
|
||||
data class HideAppInfo(
|
||||
val info: ApplicationInfo,
|
||||
val name: String,
|
||||
val icon: Drawable
|
||||
) {
|
||||
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
||||
}
|
||||
|
||||
data class StatefulProcess(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
val isHidden: Boolean
|
||||
)
|
||||
|
||||
data class HideAppTarget(
|
||||
val info: HideAppInfo,
|
||||
val processes: List<StatefulProcess>
|
||||
)
|
@ -9,27 +9,27 @@ import com.topjohnwu.magisk.databinding.ObservableItem
|
||||
import com.topjohnwu.magisk.ktx.startAnimations
|
||||
import com.topjohnwu.magisk.utils.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class HideItem(
|
||||
val item: HideAppTarget,
|
||||
viewModel: HideViewModel
|
||||
) : ObservableItem<HideItem>() {
|
||||
app: HideAppTarget
|
||||
) : ObservableItem<HideItem>(), Comparable<HideItem> {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_md2
|
||||
|
||||
val packageName = item.info.info.packageName.orEmpty()
|
||||
val items = item.processes.map { HideProcessItem(it, viewModel) }
|
||||
val info = app.info
|
||||
val processes = app.processes.map { HideProcessItem(it) }
|
||||
|
||||
@get:Bindable
|
||||
var isExpanded = false
|
||||
set(value) = set(value, field, { field = it }, BR.expanded)
|
||||
|
||||
var itemsChecked = 0
|
||||
set(value) = set(value, field, { field = it }, BR.itemsCheckedPercent)
|
||||
set(value) = set(value, field, { field = it }, BR.checkedPercent)
|
||||
|
||||
@get:Bindable
|
||||
val itemsCheckedPercent get() = (itemsChecked.toFloat() / items.size * 100).roundToInt()
|
||||
val checkedPercent get() = (itemsChecked.toFloat() / processes.size * 100).roundToInt()
|
||||
|
||||
private var state: Boolean? = false
|
||||
set(value) = set(value, field, { field = it }, BR.hiddenState)
|
||||
@ -39,14 +39,14 @@ class HideItem(
|
||||
get() = state
|
||||
set(value) = set(value, state, { state = it }, BR.hiddenState) {
|
||||
if (value == true) {
|
||||
items.filterNot { it.isHidden }
|
||||
processes.filterNot { it.isHidden }
|
||||
} else {
|
||||
items
|
||||
processes
|
||||
}.forEach { it.toggle() }
|
||||
}
|
||||
|
||||
init {
|
||||
items.forEach { it.addOnPropertyChangedCallback(BR.hidden) { recalculateChecked() } }
|
||||
processes.forEach { it.addOnPropertyChangedCallback(BR.hidden) { recalculateChecked() } }
|
||||
recalculateChecked()
|
||||
}
|
||||
|
||||
@ -56,37 +56,44 @@ class HideItem(
|
||||
}
|
||||
|
||||
private fun recalculateChecked() {
|
||||
itemsChecked = items.count { it.isHidden }
|
||||
itemsChecked = processes.count { it.isHidden }
|
||||
state = when (itemsChecked) {
|
||||
0 -> false
|
||||
items.size -> true
|
||||
processes.size -> true
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideItem): Boolean = item == other.item
|
||||
override fun itemSameAs(other: HideItem): Boolean = item.info == other.item.info
|
||||
override fun compareTo(other: HideItem) = comparator.compare(this, other)
|
||||
|
||||
companion object {
|
||||
private val comparator = compareBy<HideItem>(
|
||||
{ it.itemsChecked == 0 },
|
||||
{ it.info }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class HideProcessItem(
|
||||
val item: StatefulProcess,
|
||||
val viewModel: HideViewModel
|
||||
val process: HideProcessInfo
|
||||
) : ObservableItem<HideProcessItem>() {
|
||||
|
||||
override val layoutRes = R.layout.item_hide_process_md2
|
||||
|
||||
@get:Bindable
|
||||
var isHidden = item.isHidden
|
||||
var isHidden = process.isHidden
|
||||
set(value) = set(value, field, { field = it }, BR.hidden) {
|
||||
viewModel.toggleItem(this)
|
||||
val arg = if (isHidden) "add" else "rm"
|
||||
val (name, pkg) = process
|
||||
Shell.su("magiskhide --$arg $pkg $name").submit()
|
||||
}
|
||||
|
||||
fun toggle() {
|
||||
isHidden = !isHidden
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideProcessItem) = item == other.item
|
||||
override fun itemSameAs(other: HideProcessItem) = item.name == other.item.name
|
||||
override fun contentSameAs(other: HideProcessItem) = process == other.process
|
||||
override fun itemSameAs(other: HideProcessItem) = process.name == other.process.name
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
class HideTarget(line: String) {
|
||||
|
||||
val packageName: String
|
||||
val process: String
|
||||
|
||||
init {
|
||||
val split = line.split(Regex("\\|"), 2)
|
||||
packageName = split[0]
|
||||
process = split.getOrElse(1) { packageName }
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
@ -9,16 +10,17 @@ import com.topjohnwu.magisk.arch.Queryable
|
||||
import com.topjohnwu.magisk.arch.filterableListOf
|
||||
import com.topjohnwu.magisk.arch.itemBindingOf
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import com.topjohnwu.magisk.ktx.get
|
||||
import com.topjohnwu.magisk.ktx.packageInfo
|
||||
import com.topjohnwu.magisk.ktx.packageName
|
||||
import com.topjohnwu.magisk.ktx.processes
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class HideViewModel(
|
||||
private val magiskRepo: MagiskRepository
|
||||
) : BaseViewModel(), Queryable {
|
||||
class HideViewModel : BaseViewModel(), Queryable {
|
||||
|
||||
override val queryDelay = 1000L
|
||||
|
||||
@ -45,63 +47,74 @@ class HideViewModel(
|
||||
|
||||
override fun refresh() = viewModelScope.launch {
|
||||
state = State.LOADING
|
||||
val apps = magiskRepo.fetchApps()
|
||||
val hides = magiskRepo.fetchHideTargets()
|
||||
val (appList, diff) = withContext(Dispatchers.Default) {
|
||||
val list = apps
|
||||
val (apps, diff) = withContext(Dispatchers.Default) {
|
||||
val pm = get<PackageManager>()
|
||||
val hides = Shell.su("magiskhide --ls").exec().out.map { HideTarget(it) }
|
||||
val apps = pm.getInstalledApplications(0)
|
||||
.asSequence()
|
||||
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
||||
.map { HideAppInfo(it, pm) }
|
||||
.map { createTarget(it, hides) }
|
||||
.map { HideItem(it, this@HideViewModel) }
|
||||
.sort()
|
||||
list to items.calculateDiff(list)
|
||||
.filter { it.processes.isNotEmpty() }
|
||||
.map { HideItem(it) }
|
||||
.toList()
|
||||
.sorted()
|
||||
apps to items.calculateDiff(apps)
|
||||
}
|
||||
items.update(appList, diff)
|
||||
items.update(apps, diff)
|
||||
submitQuery()
|
||||
state = State.LOADED
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private fun createTarget(app: HideAppInfo, hideList: List<HideTarget>): HideAppTarget {
|
||||
val hidden = hideList.filter { it.packageName == app.info.packageName }
|
||||
val packageName = app.info.packageName
|
||||
val processes = app.processes.map { name ->
|
||||
StatefulProcess(name, packageName, hidden.any { name == it.process })
|
||||
private fun createTarget(info: HideAppInfo, hideList: List<HideTarget>): HideAppTarget {
|
||||
val pkg = info.packageName
|
||||
val hidden = hideList.filter { it.packageName == pkg }
|
||||
val processNames = info.packageInfo.processes.distinct()
|
||||
val processes = processNames.map { name ->
|
||||
HideProcessInfo(name, pkg, hidden.any { name == it.process })
|
||||
}
|
||||
return HideAppTarget(app, processes)
|
||||
return HideAppTarget(info, processes)
|
||||
}
|
||||
|
||||
private fun List<HideItem>.sort() = compareByDescending<HideItem> { it.itemsChecked != 0 }
|
||||
.thenBy { it.item.info.name.toLowerCase(currentLocale) }
|
||||
.thenBy { it.item.info.info.packageName }
|
||||
.let { sortedWith(it) }
|
||||
|
||||
// ---
|
||||
|
||||
override fun query() = items.filter {
|
||||
fun showHidden()= it.itemsChecked != 0
|
||||
override fun query() {
|
||||
items.filter {
|
||||
fun showHidden() = it.itemsChecked != 0
|
||||
|
||||
fun filterSystem(): Boolean {
|
||||
return isShowSystem || it.item.info.info.flags and ApplicationInfo.FLAG_SYSTEM == 0
|
||||
}
|
||||
fun filterSystem() =
|
||||
isShowSystem || it.info.flags and ApplicationInfo.FLAG_SYSTEM == 0
|
||||
|
||||
fun filterQuery(): Boolean {
|
||||
fun inName() = it.item.info.name.contains(query, true)
|
||||
fun inPackage() = it.item.info.info.packageName.contains(query, true)
|
||||
fun inProcesses() = it.item.processes.any { it.name.contains(query, true) }
|
||||
fun inName() = it.info.label.contains(query, true)
|
||||
fun inPackage() = it.info.packageName.contains(query, true)
|
||||
fun inProcesses() = it.processes.any { p -> p.process.name.contains(query, true) }
|
||||
return inName() || inPackage() || inProcesses()
|
||||
}
|
||||
|
||||
showHidden() || (filterSystem() && filterQuery())
|
||||
}
|
||||
state = State.LOADED
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
fun toggleItem(item: HideProcessItem) {
|
||||
magiskRepo.toggleHide(item.isHidden, item.item.packageName, item.item.name)
|
||||
}
|
||||
|
||||
fun resetQuery() {
|
||||
query = ""
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val blacklist by lazy { listOf(
|
||||
packageName,
|
||||
"android",
|
||||
"com.android.chrome",
|
||||
"com.chrome.beta",
|
||||
"com.chrome.dev",
|
||||
"com.chrome.canary",
|
||||
"com.android.webview",
|
||||
"com.google.android.webview"
|
||||
) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,12 +39,12 @@
|
||||
android:id="@+id/hide_icon"
|
||||
style="@style/WidgetFoundation.Image"
|
||||
android:layout_margin="@dimen/l1"
|
||||
android:src="@{item.item.info.icon}"
|
||||
android:src="@{item.info.iconImage}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0"
|
||||
tools:src="@drawable/ic_magisk" />
|
||||
tools:src="@drawable/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/hide_name"
|
||||
@ -53,7 +53,7 @@
|
||||
android:layout_marginStart="@dimen/l1"
|
||||
android:ellipsize="middle"
|
||||
android:singleLine="true"
|
||||
android:text="@{item.item.info.name}"
|
||||
android:text="@{item.info.label}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Body"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_package"
|
||||
@ -65,10 +65,9 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/hide_package"
|
||||
gone="@{item.item.info.info.packageName.empty}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.item.info.info.packageName}"
|
||||
android:text="@{item.info.packageName}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/hide_name"
|
||||
@ -90,7 +89,7 @@
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
goneUnless="@{item.isExpanded}"
|
||||
itemBinding="@{viewModel.itemInternalBinding}"
|
||||
items="@{item.items}"
|
||||
items="@{item.processes}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorSurfaceVariant"
|
||||
@ -105,7 +104,7 @@
|
||||
style="@style/WidgetFoundation.ProgressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_gravity="top"
|
||||
android:progress="@{item.itemsCheckedPercent}" />
|
||||
android:progress="@{item.checkedPercent}" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
@ -29,7 +29,8 @@
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l_75"
|
||||
android:singleLine="true"
|
||||
android:text="@{item.item.name}"
|
||||
android:ellipsize="middle"
|
||||
android:text="@{item.process.name}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_process_checkbox"
|
||||
|
Loading…
x
Reference in New Issue
Block a user