Magisk/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt
2020-07-09 04:49:14 -07:00

161 lines
5.6 KiB
Kotlin

package com.topjohnwu.magisk.ui.superuser
import android.content.pm.PackageManager
import android.content.res.Resources
import androidx.databinding.ObservableArrayList
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.recycler.PolicyItem
import com.topjohnwu.magisk.model.entity.recycler.TappableHeadlineItem
import com.topjohnwu.magisk.model.entity.recycler.TextItem
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.dialog.BiometricDialog
import com.topjohnwu.magisk.model.events.dialog.SuperuserRevokeDialog
import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.adapterOf
import com.topjohnwu.magisk.ui.base.diffListOf
import com.topjohnwu.magisk.ui.base.itemBindingOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
class SuperuserViewModel(
private val db: PolicyDao,
private val packageManager: PackageManager,
private val resources: Resources
) : BaseViewModel(), TappableHeadlineItem.Listener {
private val itemNoData = TextItem(R.string.superuser_policy_none)
private val itemsPolicies = diffListOf<PolicyItem>()
private val itemsHelpers = ObservableArrayList<TextItem>().also {
it.add(itemNoData)
}
val adapter = adapterOf<ComparableRvItem<*>>()
val items = MergeObservableList<ComparableRvItem<*>>()
.insertItem(TappableHeadlineItem.Hide)
.insertItem(TappableHeadlineItem.Safetynet)
.insertList(itemsHelpers)
.insertList(itemsPolicies)
val itemBinding = itemBindingOf<ComparableRvItem<*>> {
it.bindExtra(BR.viewModel, this)
it.bindExtra(BR.listener, this)
}
// ---
override fun refresh() = viewModelScope.launch {
state = State.LOADING
val (policies, diff) = withContext(Dispatchers.Default) {
val policies = db.fetchAll {
PolicyItem(it, it.applicationInfo.loadIcon(packageManager))
}.sortedWith(compareBy(
{ it.item.appName.toLowerCase(currentLocale) },
{ it.item.packageName }
))
policies to itemsPolicies.calculateDiff(policies)
}
itemsPolicies.update(policies, diff)
if (itemsPolicies.isNotEmpty()) {
itemsHelpers.remove(itemNoData)
}
state = State.LOADED
}
// ---
@Suppress("REDUNDANT_ELSE_IN_WHEN")
override fun onItemPressed(item: TappableHeadlineItem) = when (item) {
TappableHeadlineItem.Hide -> hidePressed()
TappableHeadlineItem.Safetynet -> safetynetPressed()
else -> Unit
}
private fun safetynetPressed() =
SuperuserFragmentDirections.actionSuperuserFragmentToSafetynetFragment().publish()
private fun hidePressed() =
SuperuserFragmentDirections.actionSuperuserFragmentToHideFragment().publish()
fun deletePressed(item: PolicyItem) {
fun updateState() = viewModelScope.launch {
db.delete(item.item.uid)
itemsPolicies.removeAll { it.genericItemSameAs(item) }
if (itemsPolicies.isEmpty() && itemsHelpers.isEmpty()) {
itemsHelpers.add(itemNoData)
}
}
if (BiometricHelper.isEnabled) {
BiometricDialog {
onSuccess { updateState() }
}.publish()
} else {
SuperuserRevokeDialog {
appName = item.item.appName
onSuccess { updateState() }
}.publish()
}
}
//---
fun updatePolicy(it: PolicyUpdateEvent) = viewModelScope.launch {
val snackStr = when (it) {
is PolicyUpdateEvent.Notification -> {
updatePolicy(it.item)
when {
it.item.notification -> R.string.su_snack_notif_on
else -> R.string.su_snack_notif_off
}
}
is PolicyUpdateEvent.Log -> {
updatePolicy(it.item)
when {
it.item.logging -> R.string.su_snack_log_on
else -> R.string.su_snack_log_off
}
}
}
SnackbarEvent(resources.getString(snackStr, it.item.appName)).publish()
}
fun togglePolicy(item: PolicyItem, enable: Boolean) {
fun updateState() {
val policy = if (enable) MagiskPolicy.ALLOW else MagiskPolicy.DENY
val app = item.item.copy(policy = policy)
viewModelScope.launch {
updatePolicy(app)
val res = if (app.policy == MagiskPolicy.ALLOW) R.string.su_snack_grant
else R.string.su_snack_deny
SnackbarEvent(resources.getString(res).format(item.item.appName))
}
}
if (BiometricHelper.isEnabled) {
BiometricDialog {
onSuccess { updateState() }
onFailure { item.isEnabled.toggle() }
}.publish()
} else {
updateState()
}
}
//---
private suspend fun updatePolicy(policy: MagiskPolicy) = db.update(policy)
}