Added logic to superuser screen

This commit is contained in:
Viktor De Pasquale 2019-10-19 20:51:28 +02:00
parent c44b85ea87
commit b66b82a6e9
9 changed files with 228 additions and 14 deletions

View File

@ -33,7 +33,8 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
protected abstract val layoutRes: Int
protected abstract val viewModel: ViewModel
protected open val themeRes: Int = R.style.MagiskTheme
protected open val snackbarView get() = binding.root
open val snackbarView get() = binding.root
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }

View File

@ -23,7 +23,7 @@ val redesignModule = module {
viewModel { RequestViewModel() }
viewModel { SafetynetViewModel() }
viewModel { SettingsViewModel() }
viewModel { SuperuserViewModel(get(), get()) }
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
viewModel { ThemeViewModel() }
viewModel { MainViewModel() }

View File

@ -2,14 +2,17 @@ package com.topjohnwu.magisk.model.events
import android.content.Context
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.extensions.snackbar
class SnackbarEvent private constructor(
@StringRes private val messageRes: Int,
private val messageString: String?,
val length: Int,
val f: Snackbar.() -> Unit
) : ViewEvent() {
) : ViewEvent(), ActivityExecutor {
constructor(
@StringRes messageRes: Int,
@ -24,4 +27,11 @@ class SnackbarEvent private constructor(
) : this(-1, message, length, f)
fun message(context: Context): String = messageString ?: context.getString(messageRes)
override fun invoke(activity: AppCompatActivity) {
if (activity is BaseActivity<*, *>) {
activity.snackbar(activity.snackbarView, message(activity), length, f)
}
}
}

View File

@ -7,10 +7,14 @@ import com.topjohnwu.magisk.view.MagiskDialog
abstract class DialogEvent : ViewEvent(), ContextExecutor {
protected lateinit var dialog: MagiskDialog
override fun invoke(context: Context) {
MagiskDialog(context).apply(this::build).reveal()
dialog = MagiskDialog(context).apply(this::build).reveal()
}
abstract fun build(dialog: MagiskDialog)
}
}
typealias GenericDialogListener = () -> Unit

View File

@ -0,0 +1,74 @@
package com.topjohnwu.magisk.model.events.dialog
import android.hardware.fingerprint.FingerprintManager
import android.widget.Toast
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
class FingerprintDialog(
builder: Builder.() -> Unit
) : DialogEvent() {
private val callbacks = Builder().apply(builder)
private var helper: Helper? = null
get() {
if (field == null) {
runCatching { field = Helper() }
}
return field
}
override fun build(dialog: MagiskDialog) {
dialog.applyIcon(R.drawable.ic_fingerprint)
.applyTitle(R.string.auth_fingerprint)
.cancellable(false) //possible fix for devices that have flawed under-screen sensor implementation
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
titleRes = android.R.string.cancel
onClick {
callbacks.listenerOnFailure()
helper?.cancel()
}
}
.onShow {
helper?.authenticate() ?: it.let {
callbacks.listenerOnFailure()
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT)
it.dismiss()
}
}
}
inner class Builder internal constructor() {
internal var listenerOnFailure: GenericDialogListener = {}
internal var listenerOnSuccess: GenericDialogListener = {}
fun onFailure(listener: GenericDialogListener) {
listenerOnFailure = listener
}
fun onSuccess(listener: GenericDialogListener) {
listenerOnSuccess = listener
}
}
private inner class Helper @Throws(Exception::class) constructor() : FingerprintHelper() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
dialog.applyMessage(errString)
}
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
dialog.applyMessage(helpString)
}
override fun onAuthenticationFailed() {
dialog.applyMessage(R.string.auth_fail)
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
callbacks.listenerOnSuccess()
dialog.dismiss()
}
}
}

View File

@ -0,0 +1,33 @@
package com.topjohnwu.magisk.model.events.dialog
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.view.MagiskDialog
class SuperuserRevokeDialog(
builder: Builder.() -> Unit
) : DialogEvent() {
private val callbacks = Builder().apply(builder)
override fun build(dialog: MagiskDialog) {
dialog.applyTitle(R.string.su_revoke_title)
.applyMessage(R.string.su_revoke_msg, callbacks.appName)
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
titleRes = R.string.yes
onClick { callbacks.listenerOnSuccess() }
}
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = R.string.no_thanks
}
}
inner class Builder internal constructor() {
var appName: String = ""
internal var listenerOnSuccess: GenericDialogListener = {}
fun onSuccess(listener: GenericDialogListener) {
listenerOnSuccess = listener
}
}
}

View File

@ -9,6 +9,8 @@ import androidx.fragment.app.Fragment
import androidx.transition.TransitionManager
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.extensions.snackbar
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
@ -54,6 +56,9 @@ abstract class CompatActivity<ViewModel : CompatViewModel, Binding : ViewDataBin
override fun onEventDispatched(event: ViewEvent) {
delegate.onEventExecute(event, this)
when (event) {
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
}
}
override fun onBackPressed() {

View File

@ -1,20 +1,34 @@
package com.topjohnwu.magisk.redesign.superuser
import android.content.pm.PackageManager
import android.content.res.Resources
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.applySchedulers
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.toggle
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.dialog.FingerprintDialog
import com.topjohnwu.magisk.model.events.dialog.SuperuserRevokeDialog
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
import com.topjohnwu.magisk.redesign.home.itemBindingOf
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.RxBus
import io.reactivex.Single
class SuperuserViewModel(
private val rxBus: RxBus,
private val db: PolicyDao,
private val packageManager: PackageManager
private val packageManager: PackageManager,
private val resources: Resources
) : CompatViewModel() {
val items = diffListOf<PolicyRvItem>()
@ -22,6 +36,17 @@ class SuperuserViewModel(
it.bindExtra(BR.viewModel, this)
}
init {
rxBus.register<PolicyEnableEvent>()
.subscribeK { togglePolicy(it.item, it.enable) }
.add()
rxBus.register<PolicyUpdateEvent>()
.subscribeK { updatePolicy(it) }
.add()
}
// ---
override fun refresh() = db.fetchAll()
.flattenAsFlowable { it }
.parallel()
@ -39,12 +64,77 @@ class SuperuserViewModel(
.applyViewModel(this)
.subscribeK { items.update(it.first, it.second) }
// ---
fun hidePressed() = Navigation.hide().publish()
fun deletePressed(item: PolicyRvItem) {
TODO()
fun updateState() = deletePolicy(item.item)
.subscribeK { items.removeAll { it.itemSameAs(item) } }
.add()
if (FingerprintHelper.useFingerprint()) {
FingerprintDialog {
onSuccess { updateState() }
}.publish()
} else {
SuperuserRevokeDialog {
appName = item.item.appName
onSuccess { updateState() }
}.publish()
}
}
//---
private fun updatePolicy(it: PolicyUpdateEvent) = when (it) {
is PolicyUpdateEvent.Notification -> updatePolicy(it.item).map {
when {
it.notification -> R.string.su_snack_notif_on
else -> R.string.su_snack_notif_off
} to it.appName
}
is PolicyUpdateEvent.Log -> updatePolicy(it.item).map {
when {
it.logging -> R.string.su_snack_log_on
else -> R.string.su_snack_log_off
} to it.appName
}
}.map { resources.getString(it.first, it.second) }
.subscribeK { SnackbarEvent(it).publish() }
.add()
private fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
fun updateState() {
val policy = if (enable) MagiskPolicy.ALLOW else MagiskPolicy.DENY
val app = item.item.copy(policy = policy)
updatePolicy(app)
.map { it.policy == MagiskPolicy.ALLOW }
.map { if (it) R.string.su_snack_grant else R.string.su_snack_deny }
.map { resources.getString(it).format(item.item.appName) }
.subscribeK { SnackbarEvent(it).publish() }
.add()
}
if (FingerprintHelper.useFingerprint()) {
FingerprintDialog {
onSuccess { updateState() }
onFailure { item.isEnabled.toggle() }
}.publish()
} else {
updateState()
}
}
//---
private fun updatePolicy(policy: MagiskPolicy) =
db.update(policy).andThen(Single.just(policy))
private fun deletePolicy(policy: MagiskPolicy) =
db.delete(policy.uid).andThen(Single.just(policy))
}
inline fun <T : ComparableRvItem<T>> diffListOf(

View File

@ -10,10 +10,8 @@ import android.view.LayoutInflater
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding
import com.topjohnwu.magisk.utils.KObservableField
@ -21,12 +19,11 @@ class MagiskDialog @JvmOverloads constructor(
context: Context, theme: Int = 0
) : AlertDialog(context, theme) {
private val binding: DialogMagiskBaseBinding
private val binding: DialogMagiskBaseBinding =
DialogMagiskBaseBinding.inflate(LayoutInflater.from(context))
private val data = Data()
init {
val layoutInflater = LayoutInflater.from(context)
binding = DataBindingUtil.inflate(layoutInflater, R.layout.dialog_magisk_base, null, false)
binding.setVariable(BR.data, data)
super.setView(binding.root)
}
@ -106,8 +103,8 @@ class MagiskDialog @JvmOverloads constructor(
fun applyTitle(title: CharSequence) =
apply { data.title.value = title }
fun applyMessage(@StringRes stringRes: Int) =
apply { data.message.value = context.getString(stringRes) }
fun applyMessage(@StringRes stringRes: Int, vararg args: Any) =
apply { data.message.value = context.getString(stringRes, *args) }
fun applyMessage(message: CharSequence) =
apply { data.message.value = message }