From 31f88e0f0575904596923940367cf8ff52fcb799 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 25 Mar 2022 16:56:21 -0700 Subject: [PATCH] Update UI for sharedUID support --- .../magisk/core/magiskdb/PolicyDao.kt | 29 ++++--- .../magisk/core/model/su/SuPolicy.kt | 67 ++------------- .../magisk/core/su/SuRequestHandler.kt | 12 +-- .../magisk/ui/superuser/PolicyRvItem.kt | 30 +++++-- .../magisk/ui/superuser/SuperuserViewModel.kt | 85 ++++++++++++++----- .../magisk/ui/surequest/SuRequestViewModel.kt | 20 +++-- app/src/main/res/layout/item_policy_md2.xml | 4 +- 7 files changed, 126 insertions(+), 121 deletions(-) diff --git a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/PolicyDao.kt b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/PolicyDao.kt index 42bd611c9..afce024af 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/PolicyDao.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/magiskdb/PolicyDao.kt @@ -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 { 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.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): 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 } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/model/su/SuPolicy.kt b/app/src/main/java/com/topjohnwu/magisk/core/model/su/SuPolicy.kt index 55b137aa2..61dc8545a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/model/su/SuPolicy.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/model/su/SuPolicy.kt @@ -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 = 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): 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 -} diff --git a/app/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt b/app/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt index f41c0c9d2..af3dbe24b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt @@ -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) { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/PolicyRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/PolicyRvItem.kt index 27b675e1c..dd587ad70 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/PolicyRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/PolicyRvItem.kt @@ -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(), RvContainer { + override val layoutRes = R.layout.item_policy_md2 + val title get() = if (isSharedUid) "[SharedUID] $appName" else appName + + private inline fun 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() { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt index a2551d062..afa8993db 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt @@ -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() + 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() } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt index 7941526ec..56c3dbd5b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt @@ -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]) diff --git a/app/src/main/res/layout/item_policy_md2.xml b/app/src/main/res/layout/item_policy_md2.xml index 8d44ff09d..72faa823d 100644 --- a/app/src/main/res/layout/item_policy_md2.xml +++ b/app/src/main/res/layout/item_policy_md2.xml @@ -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"