diff --git a/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt b/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt index 0b8fed26f..fd32472c5 100644 --- a/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/base/BaseActivity.kt @@ -33,7 +33,8 @@ abstract class BaseActivity() } diff --git a/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt b/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt index a5c34465b..dd233e7a0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt +++ b/app/src/main/java/com/topjohnwu/magisk/di/RedesignModule.kt @@ -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() } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/SnackbarEvent.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/SnackbarEvent.kt index c60d79835..61a878a49 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/events/SnackbarEvent.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/SnackbarEvent.kt @@ -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) + } + } + } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/DialogEvent.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/DialogEvent.kt index ed7b67692..60355c914 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/DialogEvent.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/DialogEvent.kt @@ -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) -} \ No newline at end of file +} + +typealias GenericDialogListener = () -> Unit \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/FingerprintDialog.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/FingerprintDialog.kt new file mode 100644 index 000000000..ddc763962 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/FingerprintDialog.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/SuperuserRevokeDialog.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/SuperuserRevokeDialog.kt new file mode 100644 index 000000000..b5121d9f8 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/dialog/SuperuserRevokeDialog.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/redesign/compat/CompatActivity.kt b/app/src/main/java/com/topjohnwu/magisk/redesign/compat/CompatActivity.kt index 1b68ca148..8f79c78f7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/redesign/compat/CompatActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/redesign/compat/CompatActivity.kt @@ -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 snackbar(snackbarView, event.message(this), event.length, event.f) + } } override fun onBackPressed() { diff --git a/app/src/main/java/com/topjohnwu/magisk/redesign/superuser/SuperuserViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/redesign/superuser/SuperuserViewModel.kt index 5ad78ec21..3baf7b305 100644 --- a/app/src/main/java/com/topjohnwu/magisk/redesign/superuser/SuperuserViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/redesign/superuser/SuperuserViewModel.kt @@ -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() @@ -22,6 +36,17 @@ class SuperuserViewModel( it.bindExtra(BR.viewModel, this) } + init { + rxBus.register() + .subscribeK { togglePolicy(it.item, it.enable) } + .add() + rxBus.register() + .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 > diffListOf( diff --git a/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt b/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt index b7f1d4df1..85b5a17ab 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt @@ -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 }