mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-02-19 20:18:29 +00:00
Update UI for sharedUID support
This commit is contained in:
parent
9f1740cc4f
commit
31f88e0f05
@ -2,9 +2,7 @@ package com.topjohnwu.magisk.core.magiskdb
|
||||
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import com.topjohnwu.magisk.core.model.su.createPolicy
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class PolicyDao : MagiskDB() {
|
||||
@ -23,28 +21,31 @@ class PolicyDao : MagiskDB() {
|
||||
|
||||
suspend fun fetch(uid: Int): SuPolicy? {
|
||||
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1"
|
||||
return exec(query) { it.toPolicyOrNull() }.firstOrNull()
|
||||
return exec(query, ::toPolicy).firstOrNull()
|
||||
}
|
||||
|
||||
suspend fun update(policy: SuPolicy) {
|
||||
val query = "REPLACE INTO ${Table.POLICY} ${policy.toMap().toQuery()}"
|
||||
val map = policy.toMap()
|
||||
// Put in package_name for old database
|
||||
map["package_name"] = AppContext.packageManager.getNameForUid(policy.uid)!!
|
||||
val query = "REPLACE INTO ${Table.POLICY} ${map.toQuery()}"
|
||||
exec(query)
|
||||
}
|
||||
|
||||
suspend fun fetchAll(): List<SuPolicy> {
|
||||
val query = "SELECT * FROM ${Table.POLICY} WHERE uid/100000 == ${Const.USER_ID}"
|
||||
return exec(query) { it.toPolicyOrNull() }.filterNotNull()
|
||||
return exec(query, ::toPolicy).filterNotNull()
|
||||
}
|
||||
|
||||
private suspend fun Map<String, String>.toPolicyOrNull(): SuPolicy? {
|
||||
try {
|
||||
return AppContext.packageManager.createPolicy(this)
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e)
|
||||
val uid = get("uid") ?: return null
|
||||
delete(uid.toInt())
|
||||
return null
|
||||
}
|
||||
private fun toPolicy(map: Map<String, String>): SuPolicy? {
|
||||
val uid = map["uid"]?.toInt() ?: return null
|
||||
val policy = SuPolicy(uid)
|
||||
|
||||
map["policy"]?.toInt()?.let { policy.policy = it }
|
||||
map["until"]?.toLong()?.let { policy.until = it }
|
||||
map["logging"]?.toInt()?.let { policy.logging = it != 0 }
|
||||
map["notification"]?.toInt()?.let { policy.notification = it != 0 }
|
||||
return policy
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,75 +1,22 @@
|
||||
package com.topjohnwu.magisk.core.model.su
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.topjohnwu.magisk.ktx.getLabel
|
||||
import com.topjohnwu.magisk.ktx.getPackageInfo
|
||||
|
||||
class SuPolicy(
|
||||
val uid: Int,
|
||||
val packageName: String,
|
||||
val appName: String,
|
||||
val icon: Drawable,
|
||||
var policy: Int = INTERACTIVE,
|
||||
var until: Long = -1L,
|
||||
var logging: Boolean = true,
|
||||
var notification: Boolean = true
|
||||
) {
|
||||
|
||||
class SuPolicy(val uid: Int) {
|
||||
companion object {
|
||||
const val INTERACTIVE = 0
|
||||
const val DENY = 1
|
||||
const val ALLOW = 2
|
||||
}
|
||||
|
||||
fun toMap() = mapOf(
|
||||
var policy: Int = INTERACTIVE
|
||||
var until: Long = -1L
|
||||
var logging: Boolean = true
|
||||
var notification: Boolean = true
|
||||
|
||||
fun toMap(): MutableMap<String, Any> = mutableMapOf(
|
||||
"uid" to uid,
|
||||
"package_name" to packageName,
|
||||
"policy" to policy,
|
||||
"until" to until,
|
||||
"logging" to logging,
|
||||
"notification" to notification
|
||||
)
|
||||
}
|
||||
|
||||
fun PackageManager.createPolicy(info: PackageInfo): SuPolicy {
|
||||
val appInfo = info.applicationInfo
|
||||
val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
|
||||
return SuPolicy(
|
||||
uid = appInfo.uid,
|
||||
packageName = getNameForUid(appInfo.uid)!!,
|
||||
appName = "$prefix${appInfo.getLabel(this)}",
|
||||
icon = appInfo.loadIcon(this),
|
||||
)
|
||||
}
|
||||
|
||||
@Throws(PackageManager.NameNotFoundException::class)
|
||||
fun PackageManager.createPolicy(uid: Int): SuPolicy {
|
||||
val info = getPackageInfo(uid, -1)
|
||||
return if (info == null) {
|
||||
// We can assert getNameForUid does not return null because
|
||||
// getPackageInfo will already throw if UID does not exist
|
||||
val name = getNameForUid(uid)!!
|
||||
SuPolicy(
|
||||
uid = uid,
|
||||
packageName = name,
|
||||
appName = "[SharedUID] $name",
|
||||
icon = defaultActivityIcon,
|
||||
)
|
||||
} else {
|
||||
createPolicy(info)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(PackageManager.NameNotFoundException::class)
|
||||
fun PackageManager.createPolicy(map: Map<String, String>): SuPolicy {
|
||||
val uid = map["uid"]?.toIntOrNull() ?: throw IllegalArgumentException()
|
||||
val policy = createPolicy(uid)
|
||||
|
||||
map["policy"]?.toInt()?.let { policy.policy = it }
|
||||
map["until"]?.toLong()?.let { policy.until = it }
|
||||
map["logging"]?.toInt()?.let { policy.logging = it != 0 }
|
||||
map["notification"]?.toInt()?.let { policy.notification = it != 0 }
|
||||
return policy
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package com.topjohnwu.magisk.core.su
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import com.topjohnwu.magisk.core.model.su.createPolicy
|
||||
import com.topjohnwu.magisk.ktx.getPackageInfo
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -19,13 +19,15 @@ import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SuRequestHandler(
|
||||
private val pm: PackageManager,
|
||||
val pm: PackageManager,
|
||||
private val policyDB: PolicyDao
|
||||
) : Closeable {
|
||||
|
||||
private lateinit var output: DataOutputStream
|
||||
lateinit var policy: SuPolicy
|
||||
private set
|
||||
lateinit var pkgInfo: PackageInfo
|
||||
private set
|
||||
|
||||
// Return true to indicate undetermined policy, require user interaction
|
||||
suspend fun start(intent: Intent): Boolean {
|
||||
@ -33,7 +35,7 @@ class SuRequestHandler(
|
||||
return false
|
||||
|
||||
// Never allow com.topjohnwu.magisk (could be malware)
|
||||
if (policy.packageName == BuildConfig.APPLICATION_ID) {
|
||||
if (pkgInfo.packageName == BuildConfig.APPLICATION_ID) {
|
||||
Shell.cmd("(pm uninstall ${BuildConfig.APPLICATION_ID})& >/dev/null 2>&1").exec()
|
||||
return false
|
||||
}
|
||||
@ -64,9 +66,9 @@ class SuRequestHandler(
|
||||
val fifo = intent.getStringExtra("fifo") ?: throw SuRequestError()
|
||||
val uid = intent.getIntExtra("uid", -1).also { if (it < 0) throw SuRequestError() }
|
||||
val pid = intent.getIntExtra("pid", -1)
|
||||
val info = pm.getPackageInfo(uid, pid) ?: throw SuRequestError()
|
||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: throw SuRequestError()
|
||||
output = DataOutputStream(FileOutputStream(fifo).buffered())
|
||||
policy = pm.createPolicy(info)
|
||||
policy = SuPolicy(uid)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
|
@ -10,37 +10,49 @@ import com.topjohnwu.magisk.databinding.RvContainer
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
|
||||
class PolicyRvItem(
|
||||
private val viewModel: SuperuserViewModel,
|
||||
override val item: SuPolicy,
|
||||
val packageName: String,
|
||||
private val isSharedUid: Boolean,
|
||||
val icon: Drawable,
|
||||
val viewModel: SuperuserViewModel
|
||||
val appName: String
|
||||
) : ObservableDiffRvItem<PolicyRvItem>(), RvContainer<SuPolicy> {
|
||||
|
||||
override val layoutRes = R.layout.item_policy_md2
|
||||
|
||||
val title get() = if (isSharedUid) "[SharedUID] $appName" else appName
|
||||
|
||||
private inline fun <reified T> setImpl(new: T, old: T, setter: (T) -> Unit) {
|
||||
if (old != new) {
|
||||
setter(new)
|
||||
}
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var isExpanded = false
|
||||
set(value) = set(value, field, { field = it }, BR.expanded)
|
||||
|
||||
// This property binds with the UI state
|
||||
@get:Bindable
|
||||
var isEnabled
|
||||
get() = item.policy == SuPolicy.ALLOW
|
||||
set(value) {
|
||||
if (value != isEnabled)
|
||||
viewModel.togglePolicy(this, value)
|
||||
set(value) = setImpl(value, isEnabled) {
|
||||
viewModel.togglePolicy(this, value)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var shouldNotify
|
||||
get() = item.notification
|
||||
set(value) = set(value, shouldNotify, { item.notification = it }, BR.shouldNotify) {
|
||||
viewModel.updatePolicy(item, isLogging = false)
|
||||
private set(value) = setImpl(value, shouldNotify) {
|
||||
item.notification = it
|
||||
viewModel.updateNotify(this)
|
||||
}
|
||||
|
||||
@get:Bindable
|
||||
var shouldLog
|
||||
get() = item.logging
|
||||
set(value) = set(value, shouldLog, { item.logging = it }, BR.shouldLog) {
|
||||
viewModel.updatePolicy(item, isLogging = true)
|
||||
private set(value) = setImpl(value, shouldLog) {
|
||||
item.logging = it
|
||||
viewModel.updateLogging(this)
|
||||
}
|
||||
|
||||
fun toggleExpand() {
|
||||
|
@ -1,5 +1,8 @@
|
||||
package com.topjohnwu.magisk.ui.superuser
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.BR
|
||||
@ -12,9 +15,11 @@ import com.topjohnwu.magisk.core.utils.currentLocale
|
||||
import com.topjohnwu.magisk.databinding.AnyDiffRvItem
|
||||
import com.topjohnwu.magisk.databinding.diffListOf
|
||||
import com.topjohnwu.magisk.databinding.itemBindingOf
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
||||
import com.topjohnwu.magisk.events.dialog.SuperuserRevokeDialog
|
||||
import com.topjohnwu.magisk.ktx.getLabel
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import com.topjohnwu.magisk.utils.asText
|
||||
import com.topjohnwu.magisk.view.TextItem
|
||||
@ -41,6 +46,7 @@ class SuperuserViewModel(
|
||||
|
||||
// ---
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
override fun refresh() = viewModelScope.launch {
|
||||
if (!Utils.showSuperUser()) {
|
||||
state = State.LOADING_FAILED
|
||||
@ -49,11 +55,28 @@ class SuperuserViewModel(
|
||||
state = State.LOADING
|
||||
val (policies, diff) = withContext(Dispatchers.IO) {
|
||||
db.deleteOutdated()
|
||||
val policies = db.fetchAll().map {
|
||||
PolicyRvItem(it, it.icon, this@SuperuserViewModel)
|
||||
}.sortedWith(compareBy(
|
||||
{ it.item.appName.lowercase(currentLocale) },
|
||||
{ it.item.packageName }
|
||||
val policies = ArrayList<PolicyRvItem>()
|
||||
val pm = AppContext.packageManager
|
||||
for (policy in db.fetchAll()) {
|
||||
val pkgs = pm.getPackagesForUid(policy.uid) ?: continue
|
||||
policies.addAll(pkgs.mapNotNull { pkg ->
|
||||
try {
|
||||
val info = pm.getPackageInfo(pkg, MATCH_UNINSTALLED_PACKAGES)
|
||||
PolicyRvItem(
|
||||
this@SuperuserViewModel, policy,
|
||||
info.packageName,
|
||||
info.sharedUserId != null,
|
||||
info.applicationInfo.loadIcon(pm),
|
||||
info.applicationInfo.getLabel(pm)
|
||||
)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
})
|
||||
}
|
||||
policies.sortWith(compareBy(
|
||||
{ it.appName.lowercase(currentLocale) },
|
||||
{ it.packageName }
|
||||
))
|
||||
policies to itemsPolicies.calculateDiff(policies)
|
||||
}
|
||||
@ -82,40 +105,56 @@ class SuperuserViewModel(
|
||||
}.publish()
|
||||
} else {
|
||||
SuperuserRevokeDialog {
|
||||
appName = item.item.appName
|
||||
appName = item.title
|
||||
onSuccess { updateState() }
|
||||
}.publish()
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
|
||||
fun updatePolicy(policy: SuPolicy, isLogging: Boolean) = viewModelScope.launch {
|
||||
db.update(policy)
|
||||
val res = when {
|
||||
isLogging -> when {
|
||||
policy.logging -> R.string.su_snack_log_on
|
||||
fun updateNotify(item: PolicyRvItem) {
|
||||
viewModelScope.launch {
|
||||
db.update(item.item)
|
||||
val res = when {
|
||||
item.item.logging -> R.string.su_snack_log_on
|
||||
else -> R.string.su_snack_log_off
|
||||
}
|
||||
else -> when {
|
||||
policy.notification -> R.string.su_snack_notif_on
|
||||
itemsPolicies.forEach {
|
||||
if (it.item.uid == item.item.uid) {
|
||||
it.notifyPropertyChanged(BR.shouldNotify)
|
||||
}
|
||||
}
|
||||
SnackbarEvent(res.asText(item.appName)).publish()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLogging(item: PolicyRvItem) {
|
||||
viewModelScope.launch {
|
||||
db.update(item.item)
|
||||
val res = when {
|
||||
item.item.notification -> R.string.su_snack_notif_on
|
||||
else -> R.string.su_snack_notif_off
|
||||
}
|
||||
itemsPolicies.forEach {
|
||||
if (it.item.uid == item.item.uid) {
|
||||
it.notifyPropertyChanged(BR.shouldLog)
|
||||
}
|
||||
}
|
||||
SnackbarEvent(res.asText(item.appName)).publish()
|
||||
}
|
||||
SnackbarEvent(res.asText(policy.appName)).publish()
|
||||
}
|
||||
|
||||
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
||||
fun updateState() {
|
||||
val policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
||||
item.item.policy = policy
|
||||
item.notifyPropertyChanged(BR.enabled)
|
||||
|
||||
viewModelScope.launch {
|
||||
val res = if (enable) R.string.su_snack_grant else R.string.su_snack_deny
|
||||
item.item.policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
||||
db.update(item.item)
|
||||
val res = if (item.item.policy == SuPolicy.ALLOW) R.string.su_snack_grant
|
||||
else R.string.su_snack_deny
|
||||
SnackbarEvent(res.asText(item.item.appName)).publish()
|
||||
itemsPolicies.forEach {
|
||||
if (it.item.uid == item.item.uid) {
|
||||
it.notifyPropertyChanged(BR.enabled)
|
||||
}
|
||||
}
|
||||
SnackbarEvent(res.asText(item.appName)).publish()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||
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
|
||||
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||
@ -31,6 +30,7 @@ import com.topjohnwu.magisk.di.AppContext
|
||||
import com.topjohnwu.magisk.events.DieEvent
|
||||
import com.topjohnwu.magisk.events.ShowUIEvent
|
||||
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
||||
import com.topjohnwu.magisk.ktx.getLabel
|
||||
import com.topjohnwu.magisk.utils.TextHolder
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import kotlinx.coroutines.launch
|
||||
@ -100,17 +100,21 @@ class SuRequestViewModel(
|
||||
fun handleRequest(intent: Intent) {
|
||||
viewModelScope.launch {
|
||||
if (handler.start(intent))
|
||||
showDialog(handler.policy)
|
||||
showDialog()
|
||||
else
|
||||
DieEvent().publish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog(policy: SuPolicy) {
|
||||
icon = policy.icon
|
||||
title = policy.appName
|
||||
packageName = policy.packageName
|
||||
selectedItemPosition = timeoutPrefs.getInt(policy.packageName, 0)
|
||||
private fun showDialog() {
|
||||
val pm = handler.pm
|
||||
val info = handler.pkgInfo
|
||||
val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
|
||||
|
||||
icon = info.applicationInfo.loadIcon(pm)
|
||||
title = "$prefix${info.applicationInfo.getLabel(pm)}"
|
||||
packageName = info.packageName
|
||||
selectedItemPosition = timeoutPrefs.getInt(packageName, 0)
|
||||
|
||||
// Set timer
|
||||
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||
@ -124,7 +128,7 @@ class SuRequestViewModel(
|
||||
timer?.cancel()
|
||||
|
||||
val pos = selectedItemPosition
|
||||
timeoutPrefs.edit().putInt(handler.policy.packageName, pos).apply()
|
||||
timeoutPrefs.edit().putInt(handler.pkgInfo.packageName, pos).apply()
|
||||
|
||||
viewModelScope.launch {
|
||||
handler.respond(action, Config.Value.TIMEOUT_LIST[pos])
|
||||
|
@ -54,7 +54,7 @@
|
||||
android:ellipsize="middle"
|
||||
android:gravity="start"
|
||||
android:maxLines="2"
|
||||
android:text="@{item.item.appName}"
|
||||
android:text="@{item.title}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Body"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold"
|
||||
@ -71,7 +71,7 @@
|
||||
android:ellipsize="middle"
|
||||
android:gravity="start"
|
||||
android:maxLines="2"
|
||||
android:text="@{item.item.packageName}"
|
||||
android:text="@{item.packageName}"
|
||||
android:textAppearance="@style/AppearanceFoundation.Caption.Variant"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
|
Loading…
x
Reference in New Issue
Block a user