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 layoutRes: Int
protected abstract val viewModel: ViewModel protected abstract val viewModel: ViewModel
protected open val themeRes: Int = R.style.MagiskTheme 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>() } private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }

View File

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

View File

@ -2,14 +2,17 @@ package com.topjohnwu.magisk.model.events
import android.content.Context import android.content.Context
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.extensions.snackbar
class SnackbarEvent private constructor( class SnackbarEvent private constructor(
@StringRes private val messageRes: Int, @StringRes private val messageRes: Int,
private val messageString: String?, private val messageString: String?,
val length: Int, val length: Int,
val f: Snackbar.() -> Unit val f: Snackbar.() -> Unit
) : ViewEvent() { ) : ViewEvent(), ActivityExecutor {
constructor( constructor(
@StringRes messageRes: Int, @StringRes messageRes: Int,
@ -24,4 +27,11 @@ class SnackbarEvent private constructor(
) : this(-1, message, length, f) ) : this(-1, message, length, f)
fun message(context: Context): String = messageString ?: context.getString(messageRes) 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 { abstract class DialogEvent : ViewEvent(), ContextExecutor {
protected lateinit var dialog: MagiskDialog
override fun invoke(context: Context) { override fun invoke(context: Context) {
MagiskDialog(context).apply(this::build).reveal() dialog = MagiskDialog(context).apply(this::build).reveal()
} }
abstract fun build(dialog: MagiskDialog) 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 androidx.transition.TransitionManager
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity 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.events.ViewEvent
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator import com.topjohnwu.magisk.model.navigation.Navigator
@ -54,6 +56,9 @@ abstract class CompatActivity<ViewModel : CompatViewModel, Binding : ViewDataBin
override fun onEventDispatched(event: ViewEvent) { override fun onEventDispatched(event: ViewEvent) {
delegate.onEventExecute(event, this) delegate.onEventExecute(event, this)
when (event) {
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
}
} }
override fun onBackPressed() { override fun onBackPressed() {

View File

@ -1,20 +1,34 @@
package com.topjohnwu.magisk.redesign.superuser package com.topjohnwu.magisk.redesign.superuser
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Resources
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.database.PolicyDao import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.applySchedulers import com.topjohnwu.magisk.extensions.applySchedulers
import com.topjohnwu.magisk.extensions.subscribeK 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.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.model.navigation.Navigation
import com.topjohnwu.magisk.redesign.compat.CompatViewModel import com.topjohnwu.magisk.redesign.compat.CompatViewModel
import com.topjohnwu.magisk.redesign.home.itemBindingOf import com.topjohnwu.magisk.redesign.home.itemBindingOf
import com.topjohnwu.magisk.utils.DiffObservableList import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.RxBus
import io.reactivex.Single
class SuperuserViewModel( class SuperuserViewModel(
private val rxBus: RxBus,
private val db: PolicyDao, private val db: PolicyDao,
private val packageManager: PackageManager private val packageManager: PackageManager,
private val resources: Resources
) : CompatViewModel() { ) : CompatViewModel() {
val items = diffListOf<PolicyRvItem>() val items = diffListOf<PolicyRvItem>()
@ -22,6 +36,17 @@ class SuperuserViewModel(
it.bindExtra(BR.viewModel, this) 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() override fun refresh() = db.fetchAll()
.flattenAsFlowable { it } .flattenAsFlowable { it }
.parallel() .parallel()
@ -39,12 +64,77 @@ class SuperuserViewModel(
.applyViewModel(this) .applyViewModel(this)
.subscribeK { items.update(it.first, it.second) } .subscribeK { items.update(it.first, it.second) }
// ---
fun hidePressed() = Navigation.hide().publish() fun hidePressed() = Navigation.hide().publish()
fun deletePressed(item: PolicyRvItem) { 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( inline fun <T : ComparableRvItem<T>> diffListOf(

View File

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