mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-03-28 01:32:14 +00:00
Add preliminary shared UID app support
This commit is contained in:
parent
f2c15c7701
commit
9f1740cc4f
@ -0,0 +1,8 @@
|
|||||||
|
// IRootUtils.aidl
|
||||||
|
package com.topjohnwu.magisk.core.utils;
|
||||||
|
|
||||||
|
// Declare any non-default types here with import statements
|
||||||
|
|
||||||
|
interface IRootUtils {
|
||||||
|
android.app.ActivityManager.RunningAppProcessInfo getAppProcess(int pid);
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.core.magiskdb
|
|
||||||
|
|
||||||
import androidx.annotation.StringDef
|
|
||||||
|
|
||||||
abstract class BaseDao {
|
|
||||||
|
|
||||||
object Table {
|
|
||||||
const val POLICY = "policies"
|
|
||||||
const val LOG = "logs"
|
|
||||||
const val SETTINGS = "settings"
|
|
||||||
const val STRINGS = "strings"
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringDef(Table.POLICY, Table.LOG, Table.SETTINGS, Table.STRINGS)
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
annotation class TableStrict
|
|
||||||
|
|
||||||
@TableStrict
|
|
||||||
abstract val table: String
|
|
||||||
|
|
||||||
inline fun <reified Builder : Query.Builder> buildQuery(builder: Builder.() -> Unit = {}) =
|
|
||||||
Builder::class.java.newInstance()
|
|
||||||
.apply { table = this@BaseDao.table }
|
|
||||||
.apply(builder)
|
|
||||||
.toString()
|
|
||||||
.let { Query(it) }
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.ktx.await
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
open class MagiskDB {
|
||||||
|
|
||||||
|
suspend fun <R> exec(
|
||||||
|
query: String,
|
||||||
|
mapper: suspend (Map<String, String>) -> R
|
||||||
|
): List<R> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val out = Shell.cmd("magisk --sqlite '$query'").await().out
|
||||||
|
out.map { line ->
|
||||||
|
line.split("\\|".toRegex())
|
||||||
|
.map { it.split("=", limit = 2) }
|
||||||
|
.filter { it.size == 2 }
|
||||||
|
.associate { it[0] to it[1] }
|
||||||
|
.let { mapper(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun exec(query: String) {
|
||||||
|
exec(query) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Map<String, Any>.toQuery(): String {
|
||||||
|
val keys = this.keys.joinToString(",")
|
||||||
|
val values = this.values.joinToString(",") {
|
||||||
|
when (it) {
|
||||||
|
is Boolean -> if (it) "1" else "0"
|
||||||
|
is Number -> it.toString()
|
||||||
|
else -> "\"$it\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "($keys) VALUES($values)"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Table {
|
||||||
|
const val POLICY = "policies"
|
||||||
|
const val SETTINGS = "settings"
|
||||||
|
const val STRINGS = "strings"
|
||||||
|
}
|
||||||
|
}
|
@ -2,61 +2,48 @@ package com.topjohnwu.magisk.core.magiskdb
|
|||||||
|
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||||
import com.topjohnwu.magisk.core.model.su.toPolicy
|
import com.topjohnwu.magisk.core.model.su.createPolicy
|
||||||
import com.topjohnwu.magisk.di.AppContext
|
import com.topjohnwu.magisk.di.AppContext
|
||||||
import com.topjohnwu.magisk.ktx.now
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class PolicyDao : MagiskDB() {
|
||||||
|
|
||||||
class PolicyDao : BaseDao() {
|
suspend fun deleteOutdated() {
|
||||||
|
val nowSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
|
||||||
override val table: String = Table.POLICY
|
val query = "DELETE FROM ${Table.POLICY} WHERE " +
|
||||||
|
"(until > 0 AND until < $nowSeconds) OR until < 0"
|
||||||
suspend fun deleteOutdated() = buildQuery<Delete> {
|
exec(query)
|
||||||
condition {
|
|
||||||
greaterThan("until", "0")
|
|
||||||
and {
|
|
||||||
lessThan("until", TimeUnit.MILLISECONDS.toSeconds(now).toString())
|
|
||||||
}
|
|
||||||
or {
|
|
||||||
lessThan("until", "0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.commit()
|
|
||||||
|
|
||||||
suspend fun delete(uid: Int) = buildQuery<Delete> {
|
|
||||||
condition {
|
|
||||||
equals("uid", uid)
|
|
||||||
}
|
|
||||||
}.commit()
|
|
||||||
|
|
||||||
suspend fun fetch(uid: Int) = buildQuery<Select> {
|
|
||||||
condition {
|
|
||||||
equals("uid", uid)
|
|
||||||
}
|
|
||||||
}.query().first().toPolicyOrNull()
|
|
||||||
|
|
||||||
suspend fun update(policy: SuPolicy) = buildQuery<Replace> {
|
|
||||||
values(policy.toMap())
|
|
||||||
}.commit()
|
|
||||||
|
|
||||||
suspend fun <R: Any> fetchAll(mapper: (SuPolicy) -> R) = buildQuery<Select> {
|
|
||||||
condition {
|
|
||||||
equals("uid/100000", Const.USER_ID)
|
|
||||||
}
|
|
||||||
}.query {
|
|
||||||
it.toPolicyOrNull()?.let(mapper)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Map<String, String>.toPolicyOrNull(): SuPolicy? {
|
suspend fun delete(uid: Int) {
|
||||||
return runCatching { toPolicy(AppContext.packageManager) }.getOrElse {
|
val query = "DELETE FROM ${Table.POLICY} WHERE uid == $uid"
|
||||||
Timber.w(it)
|
exec(query)
|
||||||
val uid = getOrElse("uid") { return null }
|
}
|
||||||
GlobalScope.launch { delete(uid.toInt()) }
|
|
||||||
null
|
suspend fun fetch(uid: Int): SuPolicy? {
|
||||||
|
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1"
|
||||||
|
return exec(query) { it.toPolicyOrNull() }.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun update(policy: SuPolicy) {
|
||||||
|
val query = "REPLACE INTO ${Table.POLICY} ${policy.toMap().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()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,161 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.core.magiskdb
|
|
||||||
|
|
||||||
import androidx.annotation.StringDef
|
|
||||||
import com.topjohnwu.magisk.ktx.await
|
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.awaitAll
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class Query(private val _query: String) {
|
|
||||||
val query get() = "magisk --sqlite '$_query'"
|
|
||||||
|
|
||||||
interface Builder {
|
|
||||||
val requestType: String
|
|
||||||
var table: String
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun <R : Any> query(crossinline mapper: (Map<String, String>) -> R?): List<R> =
|
|
||||||
withContext(Dispatchers.Default) {
|
|
||||||
Shell.cmd(query).await().out.map { line ->
|
|
||||||
async {
|
|
||||||
line.split("\\|".toRegex())
|
|
||||||
.map { it.split("=", limit = 2) }
|
|
||||||
.filter { it.size == 2 }
|
|
||||||
.map { it[0] to it[1] }
|
|
||||||
.toMap()
|
|
||||||
.let(mapper)
|
|
||||||
}
|
|
||||||
}.awaitAll().filterNotNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun query() = query { it }
|
|
||||||
|
|
||||||
suspend inline fun commit() = Shell.cmd(query).to(null).await()
|
|
||||||
}
|
|
||||||
|
|
||||||
class Delete : Query.Builder {
|
|
||||||
override val requestType: String = "DELETE FROM"
|
|
||||||
override var table = ""
|
|
||||||
|
|
||||||
private var condition = ""
|
|
||||||
|
|
||||||
fun condition(builder: Condition.() -> Unit) {
|
|
||||||
condition = Condition().apply(builder).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return listOf(requestType, table, condition).joinToString(" ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Select : Query.Builder {
|
|
||||||
override val requestType: String get() = "SELECT $fields FROM"
|
|
||||||
override lateinit var table: String
|
|
||||||
|
|
||||||
private var fields = "*"
|
|
||||||
private var condition = ""
|
|
||||||
private var orderField = ""
|
|
||||||
|
|
||||||
fun fields(vararg newFields: String) {
|
|
||||||
if (newFields.isEmpty()) {
|
|
||||||
fields = "*"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fields = newFields.joinToString(", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun condition(builder: Condition.() -> Unit) {
|
|
||||||
condition = Condition().apply(builder).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun orderBy(field: String, @OrderStrict order: String) {
|
|
||||||
orderField = "ORDER BY $field $order"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return listOf(requestType, table, condition, orderField).joinToString(" ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Replace : Insert() {
|
|
||||||
override val requestType: String = "REPLACE INTO"
|
|
||||||
}
|
|
||||||
|
|
||||||
open class Insert : Query.Builder {
|
|
||||||
override val requestType: String = "INSERT INTO"
|
|
||||||
override lateinit var table: String
|
|
||||||
|
|
||||||
private val keys get() = _values.keys.joinToString(",")
|
|
||||||
private val values get() = _values.values.joinToString(",") {
|
|
||||||
when (it) {
|
|
||||||
is Boolean -> if (it) "1" else "0"
|
|
||||||
is Number -> it.toString()
|
|
||||||
else -> "\"$it\""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var _values: Map<String, Any> = mapOf()
|
|
||||||
|
|
||||||
fun values(vararg pairs: Pair<String, Any>) {
|
|
||||||
_values = pairs.toMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun values(values: Map<String, Any>) {
|
|
||||||
_values = values
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return listOf(requestType, table, "($keys) VALUES($values)").joinToString(" ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Condition {
|
|
||||||
|
|
||||||
private val conditionWord = "WHERE %s"
|
|
||||||
private var condition: String = ""
|
|
||||||
|
|
||||||
fun equals(field: String, value: Any) {
|
|
||||||
condition = when (value) {
|
|
||||||
is String -> "$field=\"$value\""
|
|
||||||
else -> "$field=$value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun greaterThan(field: String, value: String) {
|
|
||||||
condition = "$field > $value"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lessThan(field: String, value: String) {
|
|
||||||
condition = "$field < $value"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun greaterOrEqualTo(field: String, value: String) {
|
|
||||||
condition = "$field >= $value"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lessOrEqualTo(field: String, value: String) {
|
|
||||||
condition = "$field <= $value"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun and(builder: Condition.() -> Unit) {
|
|
||||||
condition = "($condition AND ${Condition().apply(builder).condition})"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun or(builder: Condition.() -> Unit) {
|
|
||||||
condition = "($condition OR ${Condition().apply(builder).condition})"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return conditionWord.format(condition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object Order {
|
|
||||||
const val ASC = "ASC"
|
|
||||||
const val DESC = "DESC"
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringDef(Order.ASC, Order.DESC)
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
annotation class OrderStrict
|
|
@ -1,22 +1,20 @@
|
|||||||
package com.topjohnwu.magisk.core.magiskdb
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
class SettingsDao : BaseDao() {
|
class SettingsDao : MagiskDB() {
|
||||||
|
|
||||||
override val table = Table.SETTINGS
|
suspend fun delete(key: String) {
|
||||||
|
val query = "DELETE FROM ${Table.SETTINGS} WHERE key == \"$key\""
|
||||||
|
exec(query)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun delete(key: String) = buildQuery<Delete> {
|
suspend fun put(key: String, value: Int) {
|
||||||
condition { equals("key", key) }
|
val kv = mapOf("key" to key, "value" to value)
|
||||||
}.commit()
|
val query = "REPLACE INTO ${Table.SETTINGS} ${kv.toQuery()}"
|
||||||
|
exec(query)
|
||||||
suspend fun put(key: String, value: Int) = buildQuery<Replace> {
|
}
|
||||||
values("key" to key, "value" to value)
|
|
||||||
}.commit()
|
|
||||||
|
|
||||||
suspend fun fetch(key: String, default: Int = -1) = buildQuery<Select> {
|
|
||||||
fields("value")
|
|
||||||
condition { equals("key", key) }
|
|
||||||
}.query {
|
|
||||||
it["value"]?.toIntOrNull()
|
|
||||||
}.firstOrNull() ?: default
|
|
||||||
|
|
||||||
|
suspend fun fetch(key: String, default: Int = -1): Int {
|
||||||
|
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key == \"$key\" LIMIT = 1"
|
||||||
|
return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
package com.topjohnwu.magisk.core.magiskdb
|
package com.topjohnwu.magisk.core.magiskdb
|
||||||
|
|
||||||
class StringDao : BaseDao() {
|
class StringDao : MagiskDB() {
|
||||||
|
|
||||||
override val table = Table.STRINGS
|
suspend fun delete(key: String) {
|
||||||
|
val query = "DELETE FROM ${Table.STRINGS} WHERE key == \"$key\""
|
||||||
|
exec(query)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun delete(key: String) = buildQuery<Delete> {
|
suspend fun put(key: String, value: String) {
|
||||||
condition { equals("key", key) }
|
val kv = mapOf("key" to key, "value" to value)
|
||||||
}.commit()
|
val query = "REPLACE INTO ${Table.STRINGS} ${kv.toQuery()}"
|
||||||
|
exec(query)
|
||||||
suspend fun put(key: String, value: String) = buildQuery<Replace> {
|
}
|
||||||
values("key" to key, "value" to value)
|
|
||||||
}.commit()
|
|
||||||
|
|
||||||
suspend fun fetch(key: String, default: String = "") = buildQuery<Select> {
|
|
||||||
fields("value")
|
|
||||||
condition { equals("key", key) }
|
|
||||||
}.query {
|
|
||||||
it["value"]
|
|
||||||
}.firstOrNull() ?: default
|
|
||||||
|
|
||||||
|
suspend fun fetch(key: String, default: String = ""): String {
|
||||||
|
val query = "SELECT value FROM ${Table.STRINGS} WHERE key == \"$key\" LIMIT = 1"
|
||||||
|
return exec(query) { it["value"] }.firstOrNull() ?: default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.core.model.su
|
package com.topjohnwu.magisk.core.model.su
|
||||||
|
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.topjohnwu.magisk.ktx.now
|
import com.topjohnwu.magisk.ktx.getLabel
|
||||||
|
|
||||||
@Entity(tableName = "logs")
|
@Entity(tableName = "logs")
|
||||||
data class SuLog(
|
class SuLog(
|
||||||
val fromUid: Int,
|
val fromUid: Int,
|
||||||
val toUid: Int,
|
val toUid: Int,
|
||||||
val fromPid: Int,
|
val fromPid: Int,
|
||||||
@ -13,7 +15,44 @@ data class SuLog(
|
|||||||
val appName: String,
|
val appName: String,
|
||||||
val command: String,
|
val command: String,
|
||||||
val action: Boolean,
|
val action: Boolean,
|
||||||
val time: Long = now
|
val time: Long = System.currentTimeMillis()
|
||||||
) {
|
) {
|
||||||
@PrimaryKey(autoGenerate = true) var id: Int = 0
|
@PrimaryKey(autoGenerate = true) var id: Int = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun PackageManager.createSuLog(
|
||||||
|
info: PackageInfo,
|
||||||
|
toUid: Int,
|
||||||
|
fromPid: Int,
|
||||||
|
command: String,
|
||||||
|
policy: Int
|
||||||
|
): SuLog {
|
||||||
|
val appInfo = info.applicationInfo
|
||||||
|
return SuLog(
|
||||||
|
fromUid = appInfo.uid,
|
||||||
|
toUid = toUid,
|
||||||
|
fromPid = fromPid,
|
||||||
|
packageName = getNameForUid(appInfo.uid)!!,
|
||||||
|
appName = appInfo.getLabel(this),
|
||||||
|
command = command,
|
||||||
|
action = policy == SuPolicy.ALLOW
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createSuLog(
|
||||||
|
fromUid: Int,
|
||||||
|
toUid: Int,
|
||||||
|
fromPid: Int,
|
||||||
|
command: String,
|
||||||
|
policy: Int
|
||||||
|
): SuLog {
|
||||||
|
return SuLog(
|
||||||
|
fromUid = fromUid,
|
||||||
|
toUid = toUid,
|
||||||
|
fromPid = fromPid,
|
||||||
|
packageName = "[UID] $fromUid",
|
||||||
|
appName = "[UID] $fromUid",
|
||||||
|
command = command,
|
||||||
|
action = policy == SuPolicy.ALLOW
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
@file:SuppressLint("InlinedApi")
|
|
||||||
|
|
||||||
package com.topjohnwu.magisk.core.model.su
|
package com.topjohnwu.magisk.core.model.su
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.INTERACTIVE
|
|
||||||
import com.topjohnwu.magisk.ktx.getLabel
|
import com.topjohnwu.magisk.ktx.getLabel
|
||||||
|
import com.topjohnwu.magisk.ktx.getPackageInfo
|
||||||
|
|
||||||
data class SuPolicy(
|
class SuPolicy(
|
||||||
val uid: Int,
|
val uid: Int,
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val appName: String,
|
val appName: String,
|
||||||
val icon: Drawable,
|
val icon: Drawable,
|
||||||
var policy: Int = INTERACTIVE,
|
var policy: Int = INTERACTIVE,
|
||||||
var until: Long = -1L,
|
var until: Long = -1L,
|
||||||
val logging: Boolean = true,
|
var logging: Boolean = true,
|
||||||
val notification: Boolean = true
|
var notification: Boolean = true
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -25,10 +23,6 @@ data class SuPolicy(
|
|||||||
const val ALLOW = 2
|
const val ALLOW = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toLog(toUid: Int, fromPid: Int, command: String) = SuLog(
|
|
||||||
uid, toUid, fromPid, packageName, appName,
|
|
||||||
command, policy == ALLOW)
|
|
||||||
|
|
||||||
fun toMap() = mapOf(
|
fun toMap() = mapOf(
|
||||||
"uid" to uid,
|
"uid" to uid,
|
||||||
"package_name" to packageName,
|
"package_name" to packageName,
|
||||||
@ -39,47 +33,43 @@ data class SuPolicy(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PackageManager.NameNotFoundException::class)
|
fun PackageManager.createPolicy(info: PackageInfo): SuPolicy {
|
||||||
fun Map<String, String>.toPolicy(pm: PackageManager): SuPolicy {
|
val appInfo = info.applicationInfo
|
||||||
val uid = get("uid")?.toIntOrNull() ?: -1
|
val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
|
||||||
val packageName = get("package_name").orEmpty()
|
|
||||||
val info = pm.getApplicationInfo(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES)
|
|
||||||
|
|
||||||
if (info.uid != uid)
|
|
||||||
throw PackageManager.NameNotFoundException()
|
|
||||||
|
|
||||||
return SuPolicy(
|
return SuPolicy(
|
||||||
uid = uid,
|
uid = appInfo.uid,
|
||||||
packageName = packageName,
|
packageName = getNameForUid(appInfo.uid)!!,
|
||||||
appName = info.getLabel(pm),
|
appName = "$prefix${appInfo.getLabel(this)}",
|
||||||
icon = info.loadIcon(pm),
|
icon = appInfo.loadIcon(this),
|
||||||
policy = get("policy")?.toIntOrNull() ?: INTERACTIVE,
|
|
||||||
until = get("until")?.toLongOrNull() ?: -1L,
|
|
||||||
logging = get("logging")?.toIntOrNull() != 0,
|
|
||||||
notification = get("notification")?.toIntOrNull() != 0
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PackageManager.NameNotFoundException::class)
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): SuPolicy {
|
fun PackageManager.createPolicy(uid: Int): SuPolicy {
|
||||||
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
|
val info = getPackageInfo(uid, -1)
|
||||||
?: throw PackageManager.NameNotFoundException()
|
return if (info == null) {
|
||||||
val info = pm.getApplicationInfo(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES)
|
// We can assert getNameForUid does not return null because
|
||||||
return SuPolicy(
|
// getPackageInfo will already throw if UID does not exist
|
||||||
uid = info.uid,
|
val name = getNameForUid(uid)!!
|
||||||
packageName = pkg,
|
SuPolicy(
|
||||||
appName = info.getLabel(pm),
|
uid = uid,
|
||||||
icon = info.loadIcon(pm),
|
packageName = name,
|
||||||
policy = policy
|
appName = "[SharedUID] $name",
|
||||||
)
|
icon = defaultActivityIcon,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
createPolicy(info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Int.toUidPolicy(pm: PackageManager, policy: Int): SuPolicy {
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
return SuPolicy(
|
fun PackageManager.createPolicy(map: Map<String, String>): SuPolicy {
|
||||||
uid = this,
|
val uid = map["uid"]?.toIntOrNull() ?: throw IllegalArgumentException()
|
||||||
packageName = "[UID] $this",
|
val policy = createPolicy(uid)
|
||||||
appName = "[UID] $this",
|
|
||||||
icon = pm.defaultActivityIcon,
|
map["policy"]?.toInt()?.let { policy.policy = it }
|
||||||
policy = policy
|
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
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@ import com.topjohnwu.magisk.BuildConfig
|
|||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||||
import com.topjohnwu.magisk.core.model.su.toPolicy
|
import com.topjohnwu.magisk.core.model.su.createSuLog
|
||||||
import com.topjohnwu.magisk.core.model.su.toUidPolicy
|
|
||||||
import com.topjohnwu.magisk.di.ServiceLocator
|
import com.topjohnwu.magisk.di.ServiceLocator
|
||||||
|
import com.topjohnwu.magisk.ktx.getLabel
|
||||||
|
import com.topjohnwu.magisk.ktx.getPackageInfo
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
object SuCallbackHandler {
|
object SuCallbackHandler {
|
||||||
@ -53,59 +53,47 @@ object SuCallbackHandler {
|
|||||||
private fun handleLogging(context: Context, data: Bundle) {
|
private fun handleLogging(context: Context, data: Bundle) {
|
||||||
val fromUid = data.getIntComp("from.uid", -1)
|
val fromUid = data.getIntComp("from.uid", -1)
|
||||||
val notify = data.getBoolean("notify", true)
|
val notify = data.getBoolean("notify", true)
|
||||||
val allow = data.getIntComp("policy", SuPolicy.ALLOW)
|
val policy = data.getIntComp("policy", SuPolicy.ALLOW)
|
||||||
|
val toUid = data.getIntComp("to.uid", -1)
|
||||||
|
val pid = data.getIntComp("pid", -1)
|
||||||
|
val command = data.getString("command", "")
|
||||||
|
|
||||||
val pm = context.packageManager
|
val pm = context.packageManager
|
||||||
|
|
||||||
val policy = runCatching {
|
val log = runCatching {
|
||||||
fromUid.toPolicy(pm, allow)
|
pm.getPackageInfo(fromUid, pid)?.let {
|
||||||
}.getOrElse {
|
pm.createSuLog(it, toUid, pid, command, policy)
|
||||||
GlobalScope.launch { ServiceLocator.policyDB.delete(fromUid) }
|
}
|
||||||
fromUid.toUidPolicy(pm, allow)
|
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy)
|
||||||
}
|
|
||||||
|
|
||||||
if (notify)
|
if (notify)
|
||||||
notify(context, policy)
|
notify(context, log.action, log.appName)
|
||||||
|
|
||||||
val toUid = data.getIntComp("to.uid", -1)
|
runBlocking { ServiceLocator.logRepo.insert(log) }
|
||||||
val pid = data.getIntComp("pid", -1)
|
|
||||||
|
|
||||||
val command = data.getString("command", "")
|
|
||||||
val log = policy.toLog(
|
|
||||||
toUid = toUid,
|
|
||||||
fromPid = pid,
|
|
||||||
command = command
|
|
||||||
)
|
|
||||||
|
|
||||||
GlobalScope.launch {
|
|
||||||
ServiceLocator.logRepo.insert(log)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleNotify(context: Context, data: Bundle) {
|
private fun handleNotify(context: Context, data: Bundle) {
|
||||||
val fromUid = data.getIntComp("from.uid", -1)
|
val uid = data.getIntComp("from.uid", -1)
|
||||||
val allow = data.getIntComp("policy", SuPolicy.ALLOW)
|
val pid = data.getIntComp("pid", -1)
|
||||||
|
val policy = data.getIntComp("policy", SuPolicy.ALLOW)
|
||||||
|
|
||||||
val pm = context.packageManager
|
val pm = context.packageManager
|
||||||
|
|
||||||
|
val appName = runCatching {
|
||||||
|
pm.getPackageInfo(uid, pid)?.applicationInfo?.getLabel(pm)
|
||||||
|
}.getOrNull() ?: "[UID] $uid"
|
||||||
|
|
||||||
val policy = runCatching {
|
notify(context, policy == SuPolicy.ALLOW, appName)
|
||||||
fromUid.toPolicy(pm, allow)
|
|
||||||
}.getOrElse {
|
|
||||||
GlobalScope.launch { ServiceLocator.policyDB.delete(fromUid) }
|
|
||||||
fromUid.toUidPolicy(pm, allow)
|
|
||||||
}
|
|
||||||
notify(context, policy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notify(context: Context, policy: SuPolicy) {
|
private fun notify(context: Context, granted: Boolean, appName: String) {
|
||||||
if (Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
|
if (Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
|
||||||
val resId = if (policy.policy == SuPolicy.ALLOW)
|
val resId = if (granted)
|
||||||
R.string.su_allow_toast
|
R.string.su_allow_toast
|
||||||
else
|
else
|
||||||
R.string.su_deny_toast
|
R.string.su_deny_toast
|
||||||
|
|
||||||
Utils.toast(context.getString(resId, policy.appName), Toast.LENGTH_SHORT)
|
Utils.toast(context.getString(resId, appName), Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@ import com.topjohnwu.magisk.BuildConfig
|
|||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||||
import com.topjohnwu.magisk.core.model.su.toPolicy
|
import com.topjohnwu.magisk.core.model.su.createPolicy
|
||||||
|
import com.topjohnwu.magisk.ktx.getPackageInfo
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -60,10 +61,12 @@ class SuRequestHandler(
|
|||||||
|
|
||||||
private suspend fun init(intent: Intent) = withContext(Dispatchers.IO) {
|
private suspend fun init(intent: Intent) = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val name = intent.getStringExtra("fifo") ?: throw SuRequestError()
|
val fifo = intent.getStringExtra("fifo") ?: throw SuRequestError()
|
||||||
val uid = intent.getIntExtra("uid", -1).also { if (it < 0) throw SuRequestError() }
|
val uid = intent.getIntExtra("uid", -1).also { if (it < 0) throw SuRequestError() }
|
||||||
output = DataOutputStream(FileOutputStream(name).buffered())
|
val pid = intent.getIntExtra("pid", -1)
|
||||||
policy = uid.toPolicy(pm)
|
val info = pm.getPackageInfo(uid, pid) ?: throw SuRequestError()
|
||||||
|
output = DataOutputStream(FileOutputStream(fifo).buffered())
|
||||||
|
policy = pm.createPolicy(info)
|
||||||
true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
when (e) {
|
when (e) {
|
||||||
|
@ -1,30 +1,64 @@
|
|||||||
package com.topjohnwu.magisk.core.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.os.Binder
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import com.topjohnwu.superuser.ipc.RootService
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
import java.util.concurrent.locks.AbstractQueuedSynchronizer
|
import java.util.concurrent.locks.AbstractQueuedSynchronizer
|
||||||
|
|
||||||
class RootUtils(stub: Any?) : RootService() {
|
class RootUtils(stub: Any?) : RootService() {
|
||||||
|
|
||||||
private val className: String = stub?.javaClass?.name ?: javaClass.name
|
private val className: String = stub?.javaClass?.name ?: javaClass.name
|
||||||
|
private lateinit var am: ActivityManager
|
||||||
|
|
||||||
constructor() : this(null) {
|
constructor() : this(null) {
|
||||||
Timber.plant(Timber.DebugTree())
|
Timber.plant(Timber.DebugTree())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
am = getSystemService()!!
|
||||||
|
}
|
||||||
|
|
||||||
override fun getComponentName(): ComponentName {
|
override fun getComponentName(): ComponentName {
|
||||||
return ComponentName(packageName, className)
|
return ComponentName(packageName, className)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder {
|
override fun onBind(intent: Intent): IBinder {
|
||||||
return Binder()
|
return object : IRootUtils.Stub() {
|
||||||
|
override fun getAppProcess(pid: Int) = safe(null) { getAppProcessImpl(pid) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T> safe(default: T, block: () -> T): T {
|
||||||
|
return try {
|
||||||
|
block()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e)
|
||||||
|
default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAppProcessImpl(_pid: Int): ActivityManager.RunningAppProcessInfo? {
|
||||||
|
val procList = am.runningAppProcesses
|
||||||
|
var pid = _pid
|
||||||
|
while (pid > 1) {
|
||||||
|
val proc = procList.find { it.pid == pid }
|
||||||
|
if (proc != null)
|
||||||
|
return proc
|
||||||
|
// Find PPID
|
||||||
|
File("/proc/$pid/status").useLines {
|
||||||
|
val s = it.find { line -> line.startsWith("PPid:") }
|
||||||
|
pid = s?.substring(5)?.trim()?.toInt() ?: -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
object Connection : AbstractQueuedSynchronizer(), ServiceConnection {
|
object Connection : AbstractQueuedSynchronizer(), ServiceConnection {
|
||||||
|
@ -7,10 +7,10 @@ import android.content.Context
|
|||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.PackageManager.*
|
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
@ -19,40 +19,26 @@ import android.graphics.drawable.BitmapDrawable
|
|||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import android.text.PrecomputedText
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.ColorRes
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.text.PrecomputedTextCompat
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.widget.TextViewCompat
|
|
||||||
import androidx.databinding.BindingAdapter
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.transition.AutoTransition
|
import androidx.transition.AutoTransition
|
||||||
import androidx.transition.TransitionManager
|
import androidx.transition.TransitionManager
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
import com.topjohnwu.magisk.di.AppContext
|
|
||||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
import com.topjohnwu.magisk.utils.DynamicClassLoader
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.Array
|
||||||
|
import kotlin.String
|
||||||
import java.lang.reflect.Array as JArray
|
import java.lang.reflect.Array as JArray
|
||||||
|
|
||||||
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
||||||
@ -79,8 +65,6 @@ val Context.deviceProtectedContext: Context get() =
|
|||||||
createDeviceProtectedStorageContext()
|
createDeviceProtectedStorageContext()
|
||||||
} else { this }
|
} else { this }
|
||||||
|
|
||||||
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
|
||||||
|
|
||||||
fun Intent.startActivityWithRoot() {
|
fun Intent.startActivityWithRoot() {
|
||||||
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
|
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
|
||||||
val cmd = toCommand(args).joinToString(" ")
|
val cmd = toCommand(args).joinToString(" ")
|
||||||
@ -185,8 +169,6 @@ fun Intent.toCommand(args: MutableList<String> = mutableListOf()): MutableList<S
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title)
|
|
||||||
|
|
||||||
fun Context.cachedFile(name: String) = File(cacheDir, name)
|
fun Context.cachedFile(name: String) = File(cacheDir, name)
|
||||||
|
|
||||||
fun <Result> Cursor.toList(transformer: (Cursor) -> Result): List<Result> {
|
fun <Result> Cursor.toList(transformer: (Cursor) -> Result): List<Result> {
|
||||||
@ -209,34 +191,6 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String {
|
|||||||
return loadLabel(pm).toString()
|
return loadLabel(pm).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Intent.exists(packageManager: PackageManager) = resolveActivity(packageManager) != null
|
|
||||||
|
|
||||||
fun Context.colorCompat(@ColorRes id: Int) = try {
|
|
||||||
ContextCompat.getColor(this, id)
|
|
||||||
} catch (e: Resources.NotFoundException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.colorStateListCompat(@ColorRes id: Int) = try {
|
|
||||||
ContextCompat.getColorStateList(this, id)
|
|
||||||
} catch (e: Resources.NotFoundException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.drawableCompat(@DrawableRes id: Int) = AppCompatResources.getDrawable(this, id)
|
|
||||||
/**
|
|
||||||
* Pass [start] and [end] dimensions, function will return left and right
|
|
||||||
* with respect to RTL layout direction
|
|
||||||
*/
|
|
||||||
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
|
|
||||||
if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
|
|
||||||
return end to start
|
|
||||||
}
|
|
||||||
return start to end
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
|
|
||||||
|
|
||||||
inline fun <reified T> T.createClassLoader(apk: File) =
|
inline fun <reified T> T.createClassLoader(apk: File) =
|
||||||
DynamicClassLoader(apk, T::class.java.classLoader)
|
DynamicClassLoader(apk, T::class.java.classLoader)
|
||||||
|
|
||||||
@ -308,3 +262,20 @@ fun getProperty(key: String, def: String): String {
|
|||||||
}
|
}
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
|
fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
|
||||||
|
val flag = PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||||
|
val pkgs = getPackagesForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||||
|
return if (pkgs.size > 1) {
|
||||||
|
if (pid <= 0)
|
||||||
|
return null
|
||||||
|
// Try to find package name from PID
|
||||||
|
val proc = RootUtils.obj?.getAppProcess(pid) ?: return null
|
||||||
|
val pkg = proc.pkgList[0]
|
||||||
|
getPackageInfo(pkg, flag)
|
||||||
|
} else {
|
||||||
|
getPackageInfo(pkgs[0], flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,34 +20,28 @@ class PolicyRvItem(
|
|||||||
var isExpanded = false
|
var isExpanded = false
|
||||||
set(value) = set(value, field, { field = it }, BR.expanded)
|
set(value) = set(value, field, { field = it }, BR.expanded)
|
||||||
|
|
||||||
// This property hosts the policy state
|
|
||||||
var policyState = item.policy == SuPolicy.ALLOW
|
|
||||||
set(value) = set(value, field, { field = it }, BR.enabled)
|
|
||||||
|
|
||||||
// This property binds with the UI state
|
// This property binds with the UI state
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var isEnabled
|
var isEnabled
|
||||||
get() = policyState
|
get() = item.policy == SuPolicy.ALLOW
|
||||||
set(value) = set(value, policyState, { viewModel.togglePolicy(this, it) }, BR.enabled)
|
set(value) {
|
||||||
|
if (value != isEnabled)
|
||||||
@get:Bindable
|
viewModel.togglePolicy(this, value)
|
||||||
var shouldNotify = item.notification
|
|
||||||
set(value) = set(value, field, { field = it }, BR.shouldNotify) {
|
|
||||||
viewModel.updatePolicy(updatedPolicy, isLogging = false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var shouldLog = item.logging
|
var shouldNotify
|
||||||
set(value) = set(value, field, { field = it }, BR.shouldLog) {
|
get() = item.notification
|
||||||
viewModel.updatePolicy(updatedPolicy, isLogging = true)
|
set(value) = set(value, shouldNotify, { item.notification = it }, BR.shouldNotify) {
|
||||||
|
viewModel.updatePolicy(item, isLogging = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val updatedPolicy
|
@get:Bindable
|
||||||
get() = item.copy(
|
var shouldLog
|
||||||
policy = if (policyState) SuPolicy.ALLOW else SuPolicy.DENY,
|
get() = item.logging
|
||||||
notification = shouldNotify,
|
set(value) = set(value, shouldLog, { item.logging = it }, BR.shouldLog) {
|
||||||
logging = shouldLog
|
viewModel.updatePolicy(item, isLogging = true)
|
||||||
)
|
}
|
||||||
|
|
||||||
fun toggleExpand() {
|
fun toggleExpand() {
|
||||||
isExpanded = !isExpanded
|
isExpanded = !isExpanded
|
||||||
|
@ -47,12 +47,12 @@ class SuperuserViewModel(
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
state = State.LOADING
|
state = State.LOADING
|
||||||
val (policies, diff) = withContext(Dispatchers.Default) {
|
val (policies, diff) = withContext(Dispatchers.IO) {
|
||||||
db.deleteOutdated()
|
db.deleteOutdated()
|
||||||
val policies = db.fetchAll {
|
val policies = db.fetchAll().map {
|
||||||
PolicyRvItem(it, it.icon, this@SuperuserViewModel)
|
PolicyRvItem(it, it.icon, this@SuperuserViewModel)
|
||||||
}.sortedWith(compareBy(
|
}.sortedWith(compareBy(
|
||||||
{ it.item.appName.toLowerCase(currentLocale) },
|
{ it.item.appName.lowercase(currentLocale) },
|
||||||
{ it.item.packageName }
|
{ it.item.packageName }
|
||||||
))
|
))
|
||||||
policies to itemsPolicies.calculateDiff(policies)
|
policies to itemsPolicies.calculateDiff(policies)
|
||||||
@ -107,14 +107,13 @@ class SuperuserViewModel(
|
|||||||
|
|
||||||
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
||||||
fun updateState() {
|
fun updateState() {
|
||||||
item.policyState = enable
|
|
||||||
|
|
||||||
val policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
val policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
||||||
val app = item.item.copy(policy = policy)
|
item.item.policy = policy
|
||||||
|
item.notifyPropertyChanged(BR.enabled)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
db.update(app)
|
db.update(item.item)
|
||||||
val res = if (app.policy == SuPolicy.ALLOW) R.string.su_snack_grant
|
val res = if (item.item.policy == SuPolicy.ALLOW) R.string.su_snack_grant
|
||||||
else R.string.su_snack_deny
|
else R.string.su_snack_deny
|
||||||
SnackbarEvent(res.asText(item.item.appName)).publish()
|
SnackbarEvent(res.asText(item.item.appName)).publish()
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.UIActivity
|
import com.topjohnwu.magisk.arch.UIActivity
|
||||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
@ -14,6 +15,9 @@ import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
|
|||||||
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
import com.topjohnwu.magisk.di.viewModel
|
||||||
import com.topjohnwu.magisk.ui.theme.Theme
|
import com.topjohnwu.magisk.ui.theme.Theme
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
open class SuRequestActivity : UIActivity<ActivityRequestBinding>() {
|
open class SuRequestActivity : UIActivity<ActivityRequestBinding>() {
|
||||||
|
|
||||||
@ -40,8 +44,12 @@ open class SuRequestActivity : UIActivity<ActivityRequestBinding>() {
|
|||||||
if (action == REQUEST) {
|
if (action == REQUEST) {
|
||||||
viewModel.handleRequest(intent)
|
viewModel.handleRequest(intent)
|
||||||
} else {
|
} else {
|
||||||
SuCallbackHandler.run(this, action, intent.extras)
|
lifecycleScope.launch {
|
||||||
finish()
|
withContext(Dispatchers.IO) {
|
||||||
|
SuCallbackHandler.run(this@SuRequestActivity, action, intent.extras)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
|
@ -191,6 +191,7 @@ void app_notify(const su_context &ctx) {
|
|||||||
vector<Extra> extras;
|
vector<Extra> extras;
|
||||||
extras.reserve(2);
|
extras.reserve(2);
|
||||||
extras.emplace_back("from.uid", ctx.info->uid);
|
extras.emplace_back("from.uid", ctx.info->uid);
|
||||||
|
extras.emplace_back("pid", ctx.pid);
|
||||||
extras.emplace_back("policy", ctx.info->access.policy);
|
extras.emplace_back("policy", ctx.info->access.policy);
|
||||||
|
|
||||||
exec_cmd("notify", extras, ctx.info);
|
exec_cmd("notify", extras, ctx.info);
|
||||||
@ -198,21 +199,22 @@ void app_notify(const su_context &ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int app_request(const shared_ptr<su_info> &info) {
|
int app_request(const su_context &ctx) {
|
||||||
// Create FIFO
|
// Create FIFO
|
||||||
char fifo[64];
|
char fifo[64];
|
||||||
strcpy(fifo, "/dev/socket/");
|
strcpy(fifo, "/dev/socket/");
|
||||||
gen_rand_str(fifo + 12, 32, true);
|
gen_rand_str(fifo + 12, 32, true);
|
||||||
mkfifo(fifo, 0600);
|
mkfifo(fifo, 0600);
|
||||||
chown(fifo, info->mgr_st.st_uid, info->mgr_st.st_gid);
|
chown(fifo, ctx.info->mgr_st.st_uid, ctx.info->mgr_st.st_gid);
|
||||||
setfilecon(fifo, "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
setfilecon(fifo, "u:object_r:" SEPOL_FILE_TYPE ":s0");
|
||||||
|
|
||||||
// Send request
|
// Send request
|
||||||
vector<Extra> extras;
|
vector<Extra> extras;
|
||||||
extras.reserve(2);
|
extras.reserve(2);
|
||||||
extras.emplace_back("fifo", fifo);
|
extras.emplace_back("fifo", fifo);
|
||||||
extras.emplace_back("uid", info->eval_uid);
|
extras.emplace_back("uid", ctx.info->eval_uid);
|
||||||
exec_cmd("request", extras, info, false);
|
extras.emplace_back("pid", ctx.pid);
|
||||||
|
exec_cmd("request", extras, ctx.info, false);
|
||||||
|
|
||||||
// Wait for data input for at most 70 seconds
|
// Wait for data input for at most 70 seconds
|
||||||
int fd = xopen(fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
|
int fd = xopen(fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
|
||||||
|
@ -60,4 +60,4 @@ struct su_context {
|
|||||||
|
|
||||||
void app_log(const su_context &ctx);
|
void app_log(const su_context &ctx);
|
||||||
void app_notify(const su_context &ctx);
|
void app_notify(const su_context &ctx);
|
||||||
int app_request(const std::shared_ptr<su_info> &info);
|
int app_request(const su_context &ctx);
|
||||||
|
@ -193,20 +193,7 @@ static shared_ptr<su_info> get_su_info(unsigned uid) {
|
|||||||
info->access = NO_SU_ACCESS;
|
info->access = NO_SU_ACCESS;
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If still not determined, ask manager
|
|
||||||
int fd = app_request(info);
|
|
||||||
if (fd < 0) {
|
|
||||||
info->access.policy = DENY;
|
|
||||||
} else {
|
|
||||||
int ret = read_int_be(fd);
|
|
||||||
info->access.policy = ret < 0 ? DENY : static_cast<policy_t>(ret);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,6 +224,18 @@ void su_daemon_handler(int client, const sock_cred *cred) {
|
|||||||
read_string(client, ctx.req.shell);
|
read_string(client, ctx.req.shell);
|
||||||
read_string(client, ctx.req.command);
|
read_string(client, ctx.req.command);
|
||||||
|
|
||||||
|
// If still not determined, ask manager
|
||||||
|
if (ctx.info->access.policy == QUERY) {
|
||||||
|
int fd = app_request(ctx);
|
||||||
|
if (fd < 0) {
|
||||||
|
ctx.info->access.policy = DENY;
|
||||||
|
} else {
|
||||||
|
int ret = read_int_be(fd);
|
||||||
|
ctx.info->access.policy = ret < 0 ? DENY : static_cast<policy_t>(ret);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx.info->access.log)
|
if (ctx.info->access.log)
|
||||||
app_log(ctx);
|
app_log(ctx);
|
||||||
else if (ctx.info->access.notify)
|
else if (ctx.info->access.notify)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user