Magisk/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt

187 lines
6.4 KiB
Kotlin
Raw Normal View History

2020-03-31 08:53:11 +02:00
package com.topjohnwu.magisk.ui.surequest
2020-10-22 11:40:47 +02:00
import android.annotation.SuppressLint
2021-04-18 11:12:53 +02:00
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Resources
import android.graphics.drawable.Drawable
2020-10-22 11:40:47 +02:00
import android.os.Bundle
import android.os.CountDownTimer
2020-10-22 11:40:47 +02:00
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeProvider
import android.widget.Toast
2020-07-12 12:17:50 +02:00
import androidx.databinding.Bindable
2020-07-08 12:13:01 +02:00
import androidx.lifecycle.viewModelScope
2020-07-12 12:17:50 +02:00
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
2020-08-18 15:31:15 +02:00
import com.topjohnwu.magisk.arch.BaseViewModel
2020-01-13 15:01:46 +01:00
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
2020-09-11 12:09:01 +02:00
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY
2020-01-13 15:01:46 +01:00
import com.topjohnwu.magisk.core.su.SuRequestHandler
2020-01-28 18:49:59 +01:00
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.events.DieEvent
2020-09-11 11:31:41 +02:00
import com.topjohnwu.magisk.events.ShowUIEvent
2020-09-11 12:09:01 +02:00
import com.topjohnwu.magisk.events.dialog.BiometricEvent
2021-04-18 11:12:53 +02:00
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.utils.TextHolder
2020-10-22 11:40:47 +02:00
import com.topjohnwu.magisk.utils.Utils
2020-07-15 10:21:57 +02:00
import com.topjohnwu.magisk.utils.set
2020-07-08 12:13:01 +02:00
import kotlinx.coroutines.launch
import me.tatarka.bindingcollectionadapter2.ItemBinding
2020-01-13 15:01:46 +01:00
import java.util.concurrent.TimeUnit.SECONDS
class SuRequestViewModel(
2020-09-11 12:09:01 +02:00
policyDB: PolicyDao,
2021-04-18 11:12:53 +02:00
private val timeoutPrefs: SharedPreferences
2019-09-28 09:50:11 +02:00
) : BaseViewModel() {
2020-09-11 11:31:41 +02:00
lateinit var icon: Drawable
lateinit var title: String
lateinit var packageName: String
2020-07-15 10:21:57 +02:00
2020-07-12 12:17:50 +02:00
@get:Bindable
2021-04-18 11:12:53 +02:00
val denyText = DenyText()
2020-07-15 10:21:57 +02:00
2020-07-12 12:17:50 +02:00
@get:Bindable
2020-07-15 10:21:57 +02:00
var selectedItemPosition = 0
set(value) = set(value, field, { field = it }, BR.selectedItemPosition)
2020-07-12 12:17:50 +02:00
@get:Bindable
2020-07-15 10:21:57 +02:00
var grantEnabled = false
set(value) = set(value, field, { field = it }, BR.grantEnabled)
2020-10-22 11:40:47 +02:00
@SuppressLint("ClickableViewAccessibility")
val grantTouchListener = View.OnTouchListener { _: View, event: MotionEvent ->
// Filter obscured touches by consuming them.
if (event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
|| event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0) {
if (event.action == MotionEvent.ACTION_UP) {
Utils.toast(R.string.touch_filtered_warning, Toast.LENGTH_SHORT)
}
return@OnTouchListener Config.suTapjack
}
false
}
2021-04-18 11:12:53 +02:00
val itemBinding = ItemBinding.of<String>(BR.item, R.layout.item_spinner)
2021-04-18 11:12:53 +02:00
private val handler = SuRequestHandler(get<Context>().packageManager, policyDB)
2020-09-11 12:09:01 +02:00
private lateinit var timer: CountDownTimer
fun grantPressed() {
2020-09-11 12:09:01 +02:00
cancelTimer()
2019-11-14 11:42:39 +01:00
if (BiometricHelper.isEnabled) {
2020-09-11 12:09:01 +02:00
BiometricEvent {
onSuccess {
respond(ALLOW)
2019-11-14 11:42:39 +01:00
}
2020-09-11 12:09:01 +02:00
}.publish()
2019-11-14 11:42:39 +01:00
} else {
2020-09-11 12:09:01 +02:00
respond(ALLOW)
2019-11-14 11:42:39 +01:00
}
}
fun denyPressed() {
2020-09-11 12:09:01 +02:00
respond(DENY)
}
fun spinnerTouched(): Boolean {
2020-09-11 12:09:01 +02:00
cancelTimer()
return false
}
2020-07-08 12:13:01 +02:00
fun handleRequest(intent: Intent) {
viewModelScope.launch {
2020-09-11 12:09:01 +02:00
if (handler.start(intent))
showDialog(handler.policy)
else
2020-07-08 12:13:01 +02:00
DieEvent().publish()
}
2020-01-13 15:01:46 +01:00
}
2020-09-11 12:09:01 +02:00
private fun showDialog(policy: SuPolicy) {
2020-09-11 12:17:43 +02:00
icon = policy.icon
2020-09-11 12:09:01 +02:00
title = policy.appName
packageName = policy.packageName
selectedItemPosition = timeoutPrefs.getInt(policy.packageName, 0)
2020-09-11 12:09:01 +02:00
// Set timer
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
timer = SuTimer(millis, 1000).apply { start() }
2020-09-11 12:09:01 +02:00
// Actually show the UI
2020-10-22 11:40:47 +02:00
ShowUIEvent(if (Config.suTapjack) EmptyAccessibilityDelegate else null).publish()
2020-09-11 12:09:01 +02:00
}
2019-11-12 09:20:07 +01:00
2020-09-11 12:09:01 +02:00
private fun respond(action: Int) {
timer.cancel()
2020-01-13 15:01:46 +01:00
2020-09-11 12:09:01 +02:00
val pos = selectedItemPosition
timeoutPrefs.edit().putInt(handler.policy.packageName, pos).apply()
handler.respond(action, Config.Value.TIMEOUT_LIST[pos])
2020-09-11 11:31:41 +02:00
2020-09-11 12:09:01 +02:00
// Kill activity after response
DieEvent().publish()
}
2020-09-11 12:09:01 +02:00
private fun cancelTimer() {
timer.cancel()
2021-04-18 11:12:53 +02:00
denyText.seconds = 0
2020-09-11 12:09:01 +02:00
}
2020-09-11 12:09:01 +02:00
private inner class SuTimer(
private val millis: Long,
interval: Long
) : CountDownTimer(millis, interval) {
2020-09-11 12:09:01 +02:00
override fun onTick(remains: Long) {
if (!grantEnabled && remains <= millis - 1000) {
grantEnabled = true
}
2021-04-18 11:12:53 +02:00
denyText.seconds = (remains / 1000).toInt() + 1
2020-09-11 12:09:01 +02:00
}
2020-09-11 12:09:01 +02:00
override fun onFinish() {
2021-04-18 11:12:53 +02:00
denyText.seconds = 0
2020-09-11 12:09:01 +02:00
respond(DENY)
}
2019-11-12 09:20:07 +01:00
2020-09-11 12:09:01 +02:00
}
2020-10-22 11:40:47 +02:00
2021-04-18 11:12:53 +02:00
inner class DenyText : TextHolder() {
var seconds = 0
set(value) = set(value, field, { field = it }, BR.denyText)
override val isEmpty get() = false
override fun getText(resources: Resources): CharSequence {
return if (seconds != 0)
"${resources.getString(R.string.deny)} ($seconds)"
else
resources.getString(R.string.deny)
}
}
2020-10-22 11:40:47 +02:00
// Invisible for accessibility services
object EmptyAccessibilityDelegate : View.AccessibilityDelegate() {
override fun sendAccessibilityEvent(host: View?, eventType: Int) {}
override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?) = true
override fun sendAccessibilityEventUnchecked(host: View?, event: AccessibilityEvent?) {}
override fun dispatchPopulateAccessibilityEvent(host: View?, event: AccessibilityEvent?) = true
override fun onPopulateAccessibilityEvent(host: View?, event: AccessibilityEvent?) {}
override fun onInitializeAccessibilityEvent(host: View?, event: AccessibilityEvent?) {}
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {}
override fun addExtraDataToAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo, extraDataKey: String, arguments: Bundle?) {}
override fun onRequestSendAccessibilityEvent(host: ViewGroup?, child: View?, event: AccessibilityEvent?): Boolean = false
override fun getAccessibilityNodeProvider(host: View?): AccessibilityNodeProvider? = null
}
2019-11-12 09:20:07 +01:00
}