Cleanup hide fragment code

This commit is contained in:
topjohnwu 2020-08-26 04:23:43 -07:00
parent b44dcc2da0
commit 84f92bd661
12 changed files with 172 additions and 196 deletions

View File

@ -1,23 +1,13 @@
package com.topjohnwu.magisk.data.repository package com.topjohnwu.magisk.data.repository
import android.content.pm.PackageManager
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.data.network.GithubRawServices 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 timber.log.Timber
import java.io.IOException import java.io.IOException
class MagiskRepository( class MagiskRepository(
private val apiRaw: GithubRawServices, private val apiRaw: GithubRawServices
private val packageManager: PackageManager
) { ) {
suspend fun fetchUpdate() = try { suspend fun fetchUpdate() = try {
@ -40,35 +30,4 @@ class MagiskRepository(
null 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"
) }
}
} }

View File

@ -28,10 +28,17 @@ abstract class RvItem {
abstract class ComparableRvItem<in T> : RvItem() { abstract class ComparableRvItem<in T> : RvItem() {
abstract fun itemSameAs(other: T): Boolean // Use Any.equals by default
abstract fun contentSameAs(other: T): Boolean 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") @Suppress("UNCHECKED_CAST")
open fun genericItemSameAs(other: Any): Boolean = other::class == this::class && itemSameAs(other as T) open fun genericItemSameAs(other: Any): Boolean = other::class == this::class && itemSameAs(other as T)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
open fun genericContentSameAs(other: Any): Boolean = other::class == this::class && contentSameAs(other as T) open fun genericContentSameAs(other: Any): Boolean = other::class == this::class && contentSameAs(other as T)

View File

@ -7,7 +7,7 @@ import org.koin.dsl.module
val repositoryModule = module { val repositoryModule = module {
single { MagiskRepository(get(), get()) } single { MagiskRepository(get()) }
single { LogRepository(get()) } single { LogRepository(get()) }
single { StringRepository(get()) } single { StringRepository(get()) }
} }

View File

@ -17,7 +17,7 @@ import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
val viewModelModules = module { val viewModelModules = module {
viewModel { HideViewModel(get()) } viewModel { HideViewModel() }
viewModel { HomeViewModel(get()) } viewModel { HomeViewModel(get()) }
viewModel { LogViewModel(get()) } viewModel { LogViewModel(get()) }
viewModel { ModuleViewModel(get(), get(), get()) } viewModel { ModuleViewModel(get(), get(), get()) }

View File

@ -65,40 +65,22 @@ val PackageInfo.processes
val Array<out ComponentInfo>.processNames get() = mapNotNull { it.processName } val Array<out ComponentInfo>.processNames get() = mapNotNull { it.processName }
val ApplicationInfo.packageInfo: PackageInfo? val ApplicationInfo.packageInfo: PackageInfo get() {
get() { val pm = get<PackageManager>()
val pm: PackageManager by inject()
return try { return try {
val request = GET_ACTIVITIES or val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
GET_SERVICES or
GET_RECEIVERS or
GET_PROVIDERS
pm.getPackageInfo(packageName, request) pm.getPackageInfo(packageName, request)
} catch (e1: Exception) { } catch (e: Exception) {
try { // Exceed binder data transfer limit, fetch each component type separately
pm.activities(packageName).apply { pm.getPackageInfo(packageName, 0).apply {
services = pm.services(packageName) runCatching { activities = pm.getPackageInfo(packageName, GET_ACTIVITIES).activities }
receivers = pm.receivers(packageName) runCatching { services = pm.getPackageInfo(packageName, GET_SERVICES).services }
providers = pm.providers(packageName) runCatching { receivers = pm.getPackageInfo(packageName, GET_RECEIVERS).receivers }
} runCatching { providers = pm.getPackageInfo(packageName, GET_PROVIDERS).providers }
} catch (e2: Exception) {
null
} }
} }
} }
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) fun Context.rawResource(id: Int) = resources.openRawResource(id)

View 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 }
}

View File

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

View File

@ -9,27 +9,27 @@ import com.topjohnwu.magisk.databinding.ObservableItem
import com.topjohnwu.magisk.ktx.startAnimations import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.utils.addOnPropertyChangedCallback import com.topjohnwu.magisk.utils.addOnPropertyChangedCallback
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell
import kotlin.math.roundToInt import kotlin.math.roundToInt
class HideItem( class HideItem(
val item: HideAppTarget, app: HideAppTarget
viewModel: HideViewModel ) : ObservableItem<HideItem>(), Comparable<HideItem> {
) : ObservableItem<HideItem>() {
override val layoutRes = R.layout.item_hide_md2 override val layoutRes = R.layout.item_hide_md2
val packageName = item.info.info.packageName.orEmpty() val info = app.info
val items = item.processes.map { HideProcessItem(it, viewModel) } val processes = app.processes.map { HideProcessItem(it) }
@get:Bindable @get:Bindable
var isExpanded = false var isExpanded = false
set(value) = set(value, field, { field = it }, BR.expanded) set(value) = set(value, field, { field = it }, BR.expanded)
var itemsChecked = 0 var itemsChecked = 0
set(value) = set(value, field, { field = it }, BR.itemsCheckedPercent) set(value) = set(value, field, { field = it }, BR.checkedPercent)
@get:Bindable @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 private var state: Boolean? = false
set(value) = set(value, field, { field = it }, BR.hiddenState) set(value) = set(value, field, { field = it }, BR.hiddenState)
@ -39,14 +39,14 @@ class HideItem(
get() = state get() = state
set(value) = set(value, state, { state = it }, BR.hiddenState) { set(value) = set(value, state, { state = it }, BR.hiddenState) {
if (value == true) { if (value == true) {
items.filterNot { it.isHidden } processes.filterNot { it.isHidden }
} else { } else {
items processes
}.forEach { it.toggle() } }.forEach { it.toggle() }
} }
init { init {
items.forEach { it.addOnPropertyChangedCallback(BR.hidden) { recalculateChecked() } } processes.forEach { it.addOnPropertyChangedCallback(BR.hidden) { recalculateChecked() } }
recalculateChecked() recalculateChecked()
} }
@ -56,37 +56,44 @@ class HideItem(
} }
private fun recalculateChecked() { private fun recalculateChecked() {
itemsChecked = items.count { it.isHidden } itemsChecked = processes.count { it.isHidden }
state = when (itemsChecked) { state = when (itemsChecked) {
0 -> false 0 -> false
items.size -> true processes.size -> true
else -> null else -> null
} }
} }
override fun contentSameAs(other: HideItem): Boolean = item == other.item override fun compareTo(other: HideItem) = comparator.compare(this, other)
override fun itemSameAs(other: HideItem): Boolean = item.info == other.item.info
companion object {
private val comparator = compareBy<HideItem>(
{ it.itemsChecked == 0 },
{ it.info }
)
}
} }
class HideProcessItem( class HideProcessItem(
val item: StatefulProcess, val process: HideProcessInfo
val viewModel: HideViewModel
) : ObservableItem<HideProcessItem>() { ) : ObservableItem<HideProcessItem>() {
override val layoutRes = R.layout.item_hide_process_md2 override val layoutRes = R.layout.item_hide_process_md2
@get:Bindable @get:Bindable
var isHidden = item.isHidden var isHidden = process.isHidden
set(value) = set(value, field, { field = it }, BR.hidden) { 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() { fun toggle() {
isHidden = !isHidden isHidden = !isHidden
} }
override fun contentSameAs(other: HideProcessItem) = item == other.item override fun contentSameAs(other: HideProcessItem) = process == other.process
override fun itemSameAs(other: HideProcessItem) = item.name == other.item.name override fun itemSameAs(other: HideProcessItem) = process.name == other.process.name
} }

View File

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

View File

@ -1,6 +1,7 @@
package com.topjohnwu.magisk.ui.hide package com.topjohnwu.magisk.ui.hide
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR 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.filterableListOf
import com.topjohnwu.magisk.arch.itemBindingOf import com.topjohnwu.magisk.arch.itemBindingOf
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.data.repository.MagiskRepository 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.magisk.utils.set
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
class HideViewModel( class HideViewModel : BaseViewModel(), Queryable {
private val magiskRepo: MagiskRepository
) : BaseViewModel(), Queryable {
override val queryDelay = 1000L override val queryDelay = 1000L
@ -45,63 +47,74 @@ class HideViewModel(
override fun refresh() = viewModelScope.launch { override fun refresh() = viewModelScope.launch {
state = State.LOADING state = State.LOADING
val apps = magiskRepo.fetchApps() val (apps, diff) = withContext(Dispatchers.Default) {
val hides = magiskRepo.fetchHideTargets() val pm = get<PackageManager>()
val (appList, diff) = withContext(Dispatchers.Default) { val hides = Shell.su("magiskhide --ls").exec().out.map { HideTarget(it) }
val list = apps val apps = pm.getInstalledApplications(0)
.asSequence()
.filter { it.enabled && !blacklist.contains(it.packageName) }
.map { HideAppInfo(it, pm) }
.map { createTarget(it, hides) } .map { createTarget(it, hides) }
.map { HideItem(it, this@HideViewModel) } .filter { it.processes.isNotEmpty() }
.sort() .map { HideItem(it) }
list to items.calculateDiff(list) .toList()
.sorted()
apps to items.calculateDiff(apps)
} }
items.update(appList, diff) items.update(apps, diff)
submitQuery() submitQuery()
state = State.LOADED
} }
// --- // ---
private fun createTarget(app: HideAppInfo, hideList: List<HideTarget>): HideAppTarget { private fun createTarget(info: HideAppInfo, hideList: List<HideTarget>): HideAppTarget {
val hidden = hideList.filter { it.packageName == app.info.packageName } val pkg = info.packageName
val packageName = app.info.packageName val hidden = hideList.filter { it.packageName == pkg }
val processes = app.processes.map { name -> val processNames = info.packageInfo.processes.distinct()
StatefulProcess(name, packageName, hidden.any { name == it.process }) 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 { override fun query() {
fun showHidden()= it.itemsChecked != 0 items.filter {
fun showHidden() = it.itemsChecked != 0
fun filterSystem(): Boolean { fun filterSystem() =
return isShowSystem || it.item.info.info.flags and ApplicationInfo.FLAG_SYSTEM == 0 isShowSystem || it.info.flags and ApplicationInfo.FLAG_SYSTEM == 0
}
fun filterQuery(): Boolean { fun filterQuery(): Boolean {
fun inName() = it.item.info.name.contains(query, true) fun inName() = it.info.label.contains(query, true)
fun inPackage() = it.item.info.info.packageName.contains(query, true) fun inPackage() = it.info.packageName.contains(query, true)
fun inProcesses() = it.item.processes.any { it.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() && filterQuery()) showHidden() || (filterSystem() && filterQuery())
} }
state = State.LOADED
}
// --- // ---
fun toggleItem(item: HideProcessItem) {
magiskRepo.toggleHide(item.isHidden, item.item.packageName, item.item.name)
}
fun resetQuery() { fun resetQuery() {
query = "" 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"
) }
}
} }

View File

@ -39,12 +39,12 @@
android:id="@+id/hide_icon" android:id="@+id/hide_icon"
style="@style/WidgetFoundation.Image" style="@style/WidgetFoundation.Image"
android:layout_margin="@dimen/l1" android:layout_margin="@dimen/l1"
android:src="@{item.item.info.icon}" android:src="@{item.info.iconImage}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0" app:layout_constraintVertical_bias="0"
tools:src="@drawable/ic_magisk" /> tools:src="@drawable/ic_launcher" />
<TextView <TextView
android:id="@+id/hide_name" android:id="@+id/hide_name"
@ -53,7 +53,7 @@
android:layout_marginStart="@dimen/l1" android:layout_marginStart="@dimen/l1"
android:ellipsize="middle" android:ellipsize="middle"
android:singleLine="true" android:singleLine="true"
android:text="@{item.item.info.name}" android:text="@{item.info.label}"
android:textAppearance="@style/AppearanceFoundation.Body" android:textAppearance="@style/AppearanceFoundation.Body"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/hide_package" app:layout_constraintBottom_toTopOf="@+id/hide_package"
@ -65,10 +65,9 @@
<TextView <TextView
android:id="@+id/hide_package" android:id="@+id/hide_package"
gone="@{item.item.info.info.packageName.empty}"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@{item.item.info.info.packageName}" android:text="@{item.info.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"
@ -90,7 +89,7 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
goneUnless="@{item.isExpanded}" goneUnless="@{item.isExpanded}"
itemBinding="@{viewModel.itemInternalBinding}" itemBinding="@{viewModel.itemInternalBinding}"
items="@{item.items}" items="@{item.processes}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?colorSurfaceVariant" android:background="?colorSurfaceVariant"
@ -105,7 +104,7 @@
style="@style/WidgetFoundation.ProgressBar" style="@style/WidgetFoundation.ProgressBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_gravity="top" android:layout_gravity="top"
android:progress="@{item.itemsCheckedPercent}" /> android:progress="@{item.checkedPercent}" />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@ -29,7 +29,8 @@
android:layout_marginEnd="@dimen/l1" android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@dimen/l_75" android:layout_marginBottom="@dimen/l_75"
android:singleLine="true" android:singleLine="true"
android:text="@{item.item.name}" android:ellipsize="middle"
android:text="@{item.process.name}"
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_toStartOf="@+id/hide_process_checkbox" app:layout_constraintEnd_toStartOf="@+id/hide_process_checkbox"