mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 19:15:58 +00:00
Integrate Dhizuku server
This commit is contained in:
147
app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt
Normal file
147
app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt
Normal file
@@ -0,0 +1,147 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import com.rosan.dhizuku.aidl.IDhizukuClient
|
||||
import com.rosan.dhizuku.aidl.IDhizukuRequestPermissionListener
|
||||
import com.rosan.dhizuku.server_api.DhizukuProvider
|
||||
import com.rosan.dhizuku.server_api.DhizukuService
|
||||
import com.rosan.dhizuku.shared.DhizukuVariables
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val TAG = "DhizukuServer"
|
||||
|
||||
const val DHIZUKU_CLIENTS_FILE = "dhizuku_clients.json"
|
||||
|
||||
class MyDhizukuProvider(): DhizukuProvider() {
|
||||
override fun onCreateService(client: IDhizukuClient): DhizukuService? {
|
||||
Log.d(TAG, "Creating MyDhizukuService")
|
||||
return if (SharedPrefs(context!!).dhizukuServer) MyDhizukuService(context!!, MyAdminComponent, client) else null
|
||||
}
|
||||
}
|
||||
|
||||
class MyDhizukuService(context: Context, admin: ComponentName, client: IDhizukuClient) :
|
||||
DhizukuService(context, admin, client) {
|
||||
override fun checkCallingPermission(func: String?, callingUid: Int, callingPid: Int): Boolean {
|
||||
if (!SharedPrefs(mContext).dhizukuServer) return false
|
||||
val pm = mContext.packageManager
|
||||
val packageInfo = pm.getPackageInfo(
|
||||
pm.getNameForUid(callingUid) ?: return false,
|
||||
if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES else PackageManager.GET_SIGNATURES
|
||||
)
|
||||
val file = mContext.filesDir.resolve(DHIZUKU_CLIENTS_FILE)
|
||||
val clients = Json.decodeFromString<List<DhizukuClientInfo>>(file.readText())
|
||||
val signature = getPackageSignature(packageInfo)
|
||||
val hasPermission = DhizukuClientInfo(callingUid, signature, true) in clients
|
||||
Log.d(TAG, "UID $callingUid, PID $callingPid, has permission: $hasPermission")
|
||||
return hasPermission
|
||||
}
|
||||
|
||||
override fun getVersionName() = "1.0"
|
||||
}
|
||||
|
||||
class DhizukuActivity : ComponentActivity() {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (!SharedPrefs(this).dhizukuServer) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
val bundle = intent.extras ?: return
|
||||
val uid = bundle.getInt(DhizukuVariables.PARAM_CLIENT_UID, -1)
|
||||
if (uid == -1) return
|
||||
val binder = bundle.getBinder(DhizukuVariables.PARAM_CLIENT_REQUEST_PERMISSION_BINDER) ?: return
|
||||
val listener = IDhizukuRequestPermissionListener.Stub.asInterface(binder)
|
||||
val packageName = packageManager.getPackagesForUid(uid)?.first() ?: return
|
||||
val packageInfo = packageManager.getPackageInfo(
|
||||
packageName,
|
||||
if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES else PackageManager.GET_SIGNATURES
|
||||
)
|
||||
val appInfo = packageManager.getApplicationInfo(packageName, 0)
|
||||
val icon = appInfo.loadIcon(packageManager)
|
||||
val label = appInfo.loadLabel(packageManager).toString()
|
||||
fun close(grantPermission: Boolean) {
|
||||
val file = filesDir.resolve(DHIZUKU_CLIENTS_FILE)
|
||||
val clients = Json.decodeFromString<MutableList<DhizukuClientInfo>>(file.readText())
|
||||
val index = clients.indexOfFirst { it.uid == uid }
|
||||
val clientInfo = DhizukuClientInfo(uid, getPackageSignature(packageInfo), grantPermission)
|
||||
if (index == -1) clients += clientInfo
|
||||
else clients[index] = clientInfo
|
||||
file.writeText(Json.encodeToString(clients))
|
||||
finish()
|
||||
listener.onRequestPermission(
|
||||
if (grantPermission) PackageManager.PERMISSION_GRANTED else PackageManager.PERMISSION_DENIED
|
||||
)
|
||||
}
|
||||
setContent {
|
||||
AlertDialog(
|
||||
icon = {
|
||||
Image(rememberDrawablePainter(icon), null, Modifier.size(35.dp))
|
||||
},
|
||||
title = {
|
||||
Text(stringResource(R.string.request_permission))
|
||||
},
|
||||
text = {
|
||||
Text("$label\n($packageName)")
|
||||
},
|
||||
confirmButton = {
|
||||
var time by remember { mutableIntStateOf(3) }
|
||||
LaunchedEffect(Unit) {
|
||||
(1..3).forEach {
|
||||
delay(1000)
|
||||
time -= 1
|
||||
}
|
||||
}
|
||||
TextButton({
|
||||
close(true)
|
||||
}, enabled = time == 0) {
|
||||
val append = if (time > 0) " (${time}s)" else ""
|
||||
Text(stringResource(R.string.allow) + append)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton({
|
||||
close(false)
|
||||
}) {
|
||||
Text(stringResource(R.string.reject))
|
||||
}
|
||||
},
|
||||
onDismissRequest = {
|
||||
finish()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class DhizukuClientInfo(
|
||||
val uid: Int,
|
||||
val signature: String?,
|
||||
val allow: Boolean
|
||||
)
|
||||
@@ -107,6 +107,8 @@ import com.bintianqi.owndroid.dpm.DeleteWorkProfile
|
||||
import com.bintianqi.owndroid.dpm.DeleteWorkProfileScreen
|
||||
import com.bintianqi.owndroid.dpm.DeviceInfo
|
||||
import com.bintianqi.owndroid.dpm.DeviceInfoScreen
|
||||
import com.bintianqi.owndroid.dpm.DhizukuServerSettings
|
||||
import com.bintianqi.owndroid.dpm.DhizukuServerSettingsScreen
|
||||
import com.bintianqi.owndroid.dpm.DisableAccountManagement
|
||||
import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen
|
||||
import com.bintianqi.owndroid.dpm.DisableMeteredData
|
||||
@@ -324,6 +326,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
}
|
||||
}, ::navigate)
|
||||
}
|
||||
composable<DhizukuServerSettings> { DhizukuServerSettingsScreen(::navigateUp) }
|
||||
|
||||
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
|
||||
composable<AddDelegatedAdmin>{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) }
|
||||
|
||||
@@ -64,7 +64,7 @@ class Receiver : DeviceAdminReceiver() {
|
||||
super.onSecurityLogsAvailable(context, intent)
|
||||
if(VERSION.SDK_INT >= 24) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val events = getManager(context).retrieveSecurityLogs(ComponentName(context, this@Receiver::class.java)) ?: return@launch
|
||||
val events = getManager(context).retrieveSecurityLogs(MyAdminComponent) ?: return@launch
|
||||
val file = context.filesDir.resolve("SecurityLogs.json")
|
||||
val fileExists = file.exists()
|
||||
file.outputStream().use {
|
||||
|
||||
@@ -23,7 +23,8 @@ class SharedPrefs(context: Context) {
|
||||
var biometricsUnlock by BooleanSharedPref("lock.biometrics")
|
||||
var lockWhenLeaving by BooleanSharedPref("lock.onleave")
|
||||
var applicationsListView by BooleanSharedPref("applications.list_view", true)
|
||||
var shortcuts by BooleanSharedPref("shortcuts", false)
|
||||
var shortcuts by BooleanSharedPref("shortcuts")
|
||||
var dhizukuServer by BooleanSharedPref("dhizuku_server")
|
||||
}
|
||||
|
||||
private class BooleanSharedPref(val key: String, val defValue: Boolean = false): ReadWriteProperty<SharedPrefs, Boolean> {
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.bintianqi.owndroid
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -131,3 +133,13 @@ fun String.hash(): String {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
return md.digest(this.encodeToByteArray()).toHexString()
|
||||
}
|
||||
|
||||
val MyAdminComponent = ComponentName.unflattenFromString("com.bintianqi.owndroid/.Receiver")!!
|
||||
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun getPackageSignature(info: PackageInfo): String? {
|
||||
val signatures = if (Build.VERSION.SDK_INT >= 28) info.signingInfo?.apkContentsSigners else info.signatures
|
||||
return signatures?.firstOrNull()?.toByteArray()
|
||||
?.let { MessageDigest.getInstance("SHA-256").digest(it) }?.toHexString()
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.bintianqi.owndroid.MyAdminComponent
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.Receiver
|
||||
import com.bintianqi.owndroid.SharedPrefs
|
||||
import com.bintianqi.owndroid.ShortcutsReceiverActivity
|
||||
import com.bintianqi.owndroid.backToHomeStateFlow
|
||||
import com.bintianqi.owndroid.createShortcuts
|
||||
import com.bintianqi.owndroid.myPrivilege
|
||||
import com.rosan.dhizuku.api.Dhizuku
|
||||
@@ -130,7 +130,7 @@ fun Context.getReceiver(): ComponentName {
|
||||
return if(SharedPrefs(this).dhizuku) {
|
||||
Dhizuku.getOwnerComponent()
|
||||
} else {
|
||||
ComponentName(this, Receiver::class.java)
|
||||
MyAdminComponent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -31,7 +32,10 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
@@ -57,6 +61,7 @@ import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -84,23 +89,28 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.ChoosePackageContract
|
||||
import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE
|
||||
import com.bintianqi.owndroid.DhizukuClientInfo
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
import com.bintianqi.owndroid.IUserService
|
||||
import com.bintianqi.owndroid.MyAdminComponent
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.Receiver
|
||||
import com.bintianqi.owndroid.Settings
|
||||
import com.bintianqi.owndroid.SharedPrefs
|
||||
import com.bintianqi.owndroid.myPrivilege
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
||||
import com.bintianqi.owndroid.ui.InfoItem
|
||||
import com.bintianqi.owndroid.ui.MyLazyScaffold
|
||||
import com.bintianqi.owndroid.ui.MyScaffold
|
||||
import com.bintianqi.owndroid.ui.MySmallTitleScaffold
|
||||
import com.bintianqi.owndroid.ui.NavIcon
|
||||
import com.bintianqi.owndroid.ui.Notes
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import com.bintianqi.owndroid.updatePrivilege
|
||||
import com.bintianqi.owndroid.useShizuku
|
||||
import com.bintianqi.owndroid.yesOrNo
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import com.rosan.dhizuku.api.Dhizuku
|
||||
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@@ -108,6 +118,8 @@ import com.topjohnwu.superuser.ipc.RootService
|
||||
import dalvik.system.DexClassLoader
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.lang.invoke.MethodHandles
|
||||
import java.lang.reflect.Proxy
|
||||
|
||||
@@ -258,6 +270,14 @@ fun WorkModesScreen(
|
||||
tint = if(privilege.device) colorScheme.primary else colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
if ((privilege.device || privilege.profile) && !privilege.dhizuku) Row(
|
||||
Modifier.padding(top = 20.dp).fillMaxWidth().clickable { onNavigate(DhizukuServerSettings) },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(painterResource(R.drawable.dhizuku_icon), null, Modifier.padding(8.dp).size(28.dp))
|
||||
Text(stringResource(R.string.dhizuku_server), style = typography.titleLarge)
|
||||
}
|
||||
|
||||
Column(Modifier.padding(HorizontalPadding, 20.dp)) {
|
||||
Row(Modifier.padding(bottom = 4.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Outlined.Warning, null, Modifier.padding(end = 4.dp), colorScheme.error)
|
||||
@@ -338,7 +358,7 @@ fun WorkModesScreen(
|
||||
if(privilege.device) {
|
||||
dpm.clearDeviceOwnerApp(context.packageName)
|
||||
} else if(VERSION.SDK_INT >= 24) {
|
||||
dpm.clearProfileOwner(ComponentName(context, Receiver::class.java))
|
||||
dpm.clearProfileOwner(MyAdminComponent)
|
||||
}
|
||||
}
|
||||
dialog = 0
|
||||
@@ -424,10 +444,7 @@ fun activateUsingDhizuku(context: Context, callback: (Boolean, Boolean, String?)
|
||||
if(dpm == null) {
|
||||
context.showOperationResultToast(false)
|
||||
} else {
|
||||
dpm.transferOwnership(
|
||||
Dhizuku.getOwnerComponent(),
|
||||
ComponentName(context, Receiver::class.java), PersistableBundle()
|
||||
)
|
||||
dpm.transferOwnership(Dhizuku.getOwnerComponent(), MyAdminComponent, PersistableBundle())
|
||||
callback(true, true, null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -542,6 +559,65 @@ fun activateDhizukuMode(context: Context, callback: (Boolean, Boolean, String?)
|
||||
|
||||
const val ACTIVATE_DEVICE_OWNER_COMMAND = "dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver"
|
||||
|
||||
@Serializable object DhizukuServerSettings
|
||||
|
||||
@Composable
|
||||
fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val pm = context.packageManager
|
||||
val sp = SharedPrefs(context)
|
||||
val file = context.filesDir.resolve(DHIZUKU_CLIENTS_FILE)
|
||||
var enabled by remember { mutableStateOf(sp.dhizukuServer) }
|
||||
val clients = remember { mutableStateListOf<DhizukuClientInfo>() }
|
||||
fun changeEnableState(status: Boolean) {
|
||||
enabled = status
|
||||
sp.dhizukuServer = status
|
||||
}
|
||||
fun writeList() {
|
||||
file.writeText(Json.encodeToString(clients))
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if (!file.exists()) file.writeText("[]")
|
||||
}
|
||||
LaunchedEffect(enabled) {
|
||||
if (enabled) {
|
||||
clients.clear()
|
||||
clients.addAll(Json.decodeFromString<List<DhizukuClientInfo>>(file.readText()))
|
||||
}
|
||||
}
|
||||
MyLazyScaffold(R.string.dhizuku_server, onNavigateUp) {
|
||||
item {
|
||||
SwitchItem(R.string.enable, getState = { sp.dhizukuServer }, onCheckedChange = ::changeEnableState)
|
||||
Spacer(Modifier.padding(vertical = 8.dp))
|
||||
}
|
||||
if (enabled) itemsIndexed(clients) { index, client ->
|
||||
val name = pm.getNameForUid(client.uid)
|
||||
if (name == null) {
|
||||
clients.dropWhile { it.uid == client.uid }
|
||||
writeList()
|
||||
} else {
|
||||
val info = pm.getApplicationInfo(name, 0)
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth().padding(8.dp)
|
||||
.background(colorScheme.surfaceVariant, RoundedCornerShape(8.dp))
|
||||
.padding(8.dp),
|
||||
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Image(
|
||||
rememberDrawablePainter(info.loadIcon(pm)), null,
|
||||
Modifier.padding(end = 12.dp).size(50.dp)
|
||||
)
|
||||
Text(info.loadLabel(pm).toString(), style = typography.titleLarge)
|
||||
}
|
||||
Switch(client.allow, { clients[index] = client.copy(allow = it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable object LockScreenInfo
|
||||
|
||||
@RequiresApi(24)
|
||||
|
||||
Reference in New Issue
Block a user