mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-27 12:26:11 +00:00
ViewModel refactoring: Permissions part
Add MyDbHelper and MyRepository, use database to store dhizuku clients, fix #168
This commit is contained in:
@@ -7,7 +7,6 @@ import androidx.activity.viewModels
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import com.bintianqi.owndroid.ui.AppInstaller
|
import com.bintianqi.owndroid.ui.AppInstaller
|
||||||
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
||||||
|
|
||||||
@@ -15,11 +14,10 @@ class AppInstallerActivity:FragmentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val myVm by viewModels<MyViewModel>()
|
|
||||||
val vm by viewModels<AppInstallerViewModel>()
|
val vm by viewModels<AppInstallerViewModel>()
|
||||||
vm.initialize(intent)
|
vm.initialize(intent)
|
||||||
|
val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)
|
||||||
setContent {
|
setContent {
|
||||||
val theme by myVm.theme.collectAsStateWithLifecycle()
|
|
||||||
OwnDroidTheme(theme) {
|
OwnDroidTheme(theme) {
|
||||||
val uiState by vm.uiState.collectAsState()
|
val uiState by vm.uiState.collectAsState()
|
||||||
AppInstaller(
|
AppInstaller(
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import android.util.Log
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
@@ -24,7 +23,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
||||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
import com.rosan.dhizuku.aidl.IDhizukuClient
|
import com.rosan.dhizuku.aidl.IDhizukuClient
|
||||||
@@ -34,12 +32,9 @@ import com.rosan.dhizuku.server_api.DhizukuService
|
|||||||
import com.rosan.dhizuku.shared.DhizukuVariables
|
import com.rosan.dhizuku.shared.DhizukuVariables
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
private const val TAG = "DhizukuServer"
|
private const val TAG = "DhizukuServer"
|
||||||
|
|
||||||
const val DHIZUKU_CLIENTS_FILE = "dhizuku_clients.json"
|
|
||||||
|
|
||||||
class MyDhizukuProvider(): DhizukuProvider() {
|
class MyDhizukuProvider(): DhizukuProvider() {
|
||||||
override fun onCreateService(client: IDhizukuClient): DhizukuService? {
|
override fun onCreateService(client: IDhizukuClient): DhizukuService? {
|
||||||
Log.d(TAG, "Creating MyDhizukuService")
|
Log.d(TAG, "Creating MyDhizukuService")
|
||||||
@@ -56,8 +51,6 @@ class MyDhizukuService(context: Context, admin: ComponentName, client: IDhizukuC
|
|||||||
pm.getNameForUid(callingUid) ?: return false,
|
pm.getNameForUid(callingUid) ?: return false,
|
||||||
if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES else PackageManager.GET_SIGNATURES
|
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 signature = getPackageSignature(packageInfo)
|
||||||
val requiredPermission = when (func) {
|
val requiredPermission = when (func) {
|
||||||
"remote_transact", "remote_process" -> func
|
"remote_transact", "remote_process" -> func
|
||||||
@@ -65,9 +58,10 @@ class MyDhizukuService(context: Context, admin: ComponentName, client: IDhizukuC
|
|||||||
"get_delegated_scopes", "set_delegated_scopes" -> "delegated_scopes"
|
"get_delegated_scopes", "set_delegated_scopes" -> "delegated_scopes"
|
||||||
else -> "other"
|
else -> "other"
|
||||||
}
|
}
|
||||||
val hasPermission = clients.find {
|
val hasPermission = (mContext.applicationContext as MyApplication).myRepo
|
||||||
callingUid == it.uid && signature == it.signature && requiredPermission in it.permissions
|
.checkDhizukuClientPermission(
|
||||||
} != null
|
callingUid, signature, requiredPermission
|
||||||
|
)
|
||||||
Log.d(TAG, "UID $callingUid, PID $callingPid, required permission: $requiredPermission, has permission: $hasPermission")
|
Log.d(TAG, "UID $callingUid, PID $callingPid, required permission: $requiredPermission, has permission: $hasPermission")
|
||||||
return hasPermission
|
return hasPermission
|
||||||
}
|
}
|
||||||
@@ -97,26 +91,19 @@ class DhizukuActivity : ComponentActivity() {
|
|||||||
val icon = appInfo.loadIcon(packageManager)
|
val icon = appInfo.loadIcon(packageManager)
|
||||||
val label = appInfo.loadLabel(packageManager).toString()
|
val label = appInfo.loadLabel(packageManager).toString()
|
||||||
fun close(grantPermission: Boolean) {
|
fun close(grantPermission: Boolean) {
|
||||||
val file = filesDir.resolve(DHIZUKU_CLIENTS_FILE)
|
|
||||||
val json = Json { ignoreUnknownKeys = true }
|
|
||||||
val clients = json.decodeFromString<MutableList<DhizukuClientInfo>>(file.readText())
|
|
||||||
val index = clients.indexOfFirst { it.uid == uid }
|
|
||||||
val clientInfo = DhizukuClientInfo(
|
val clientInfo = DhizukuClientInfo(
|
||||||
uid, getPackageSignature(packageInfo), if (grantPermission) DhizukuPermissions else emptyList()
|
uid, getPackageSignature(packageInfo), if (grantPermission) DhizukuPermissions else emptyList()
|
||||||
)
|
)
|
||||||
if (index == -1) clients += clientInfo
|
(application as MyApplication).myRepo.setDhizukuClient(clientInfo)
|
||||||
else clients[index] = clientInfo
|
|
||||||
file.writeText(Json.encodeToString(clients))
|
|
||||||
finish()
|
finish()
|
||||||
listener.onRequestPermission(
|
listener.onRequestPermission(
|
||||||
if (grantPermission) PackageManager.PERMISSION_GRANTED else PackageManager.PERMISSION_DENIED
|
if (grantPermission) PackageManager.PERMISSION_GRANTED else PackageManager.PERMISSION_DENIED
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val vm by viewModels<MyViewModel>()
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)
|
||||||
setContent {
|
setContent {
|
||||||
var appLockDialog by remember { mutableStateOf(false) }
|
var appLockDialog by remember { mutableStateOf(false) }
|
||||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
|
||||||
OwnDroidTheme(theme) {
|
OwnDroidTheme(theme) {
|
||||||
if (!appLockDialog) AlertDialog(
|
if (!appLockDialog) AlertDialog(
|
||||||
icon = {
|
icon = {
|
||||||
|
|||||||
@@ -293,23 +293,40 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
) {
|
) {
|
||||||
composable<Home> { HomeScreen(::navigate) }
|
composable<Home> { HomeScreen(::navigate) }
|
||||||
composable<WorkModes> {
|
composable<WorkModes> {
|
||||||
WorkModesScreen(it.toRoute(), ::navigateUp, {
|
WorkModesScreen(vm, it.toRoute(), ::navigateUp, {
|
||||||
navController.navigate(Home) {
|
navController.navigate(Home) {
|
||||||
popUpTo<WorkModes> { inclusive = true }
|
popUpTo<WorkModes> { inclusive = true }
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
navController.navigate(WorkModes(false)) {
|
||||||
|
popUpTo(Home) { inclusive = true }
|
||||||
|
}
|
||||||
}, ::navigate)
|
}, ::navigate)
|
||||||
}
|
}
|
||||||
composable<DhizukuServerSettings> { DhizukuServerSettingsScreen(::navigateUp) }
|
composable<DhizukuServerSettings> {
|
||||||
|
DhizukuServerSettingsScreen(vm.dhizukuClients, vm::getDhizukuClients,
|
||||||
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
|
vm::updateDhizukuClient, vm::getDhizukuServerEnabled, vm::setDhizukuServerEnabled,
|
||||||
composable<AddDelegatedAdmin>{
|
::navigateUp)
|
||||||
AddDelegatedAdminScreen(vm.chosenPackage, ::choosePackage, it.toRoute(), ::navigateUp)
|
}
|
||||||
|
|
||||||
|
composable<DelegatedAdmins> {
|
||||||
|
DelegatedAdminsScreen(vm.delegatedAdmins, vm::getDelegatedAdmins, ::navigateUp, ::navigate)
|
||||||
|
}
|
||||||
|
composable<AddDelegatedAdmin>{
|
||||||
|
AddDelegatedAdminScreen(vm.chosenPackage, ::choosePackage, it.toRoute(),
|
||||||
|
vm::setDelegatedAdmin, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<DeviceInfo> { DeviceInfoScreen(vm, ::navigateUp) }
|
||||||
|
composable<LockScreenInfo> {
|
||||||
|
LockScreenInfoScreen(vm::getLockScreenInfo, vm::setLockScreenInfo, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<SupportMessage> {
|
||||||
|
SupportMessageScreen(vm::getShortSupportMessage, vm::getLongSupportMessage,
|
||||||
|
vm::setShortSupportMessage, vm::setLongSupportMessage, ::navigateUp)
|
||||||
}
|
}
|
||||||
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
|
|
||||||
composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
|
|
||||||
composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
|
|
||||||
composable<TransferOwnership> {
|
composable<TransferOwnership> {
|
||||||
TransferOwnershipScreen(::navigateUp) {
|
TransferOwnershipScreen(vm.deviceAdminReceivers, vm::getDeviceAdminReceivers,
|
||||||
|
vm::transferOwnership, ::navigateUp) {
|
||||||
navController.navigate(WorkModes(false)) {
|
navController.navigate(WorkModes(false)) {
|
||||||
popUpTo(Home) { inclusive = true }
|
popUpTo(Home) { inclusive = true }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import android.os.Build.VERSION
|
|||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
|
||||||
class MyApplication : Application() {
|
class MyApplication : Application() {
|
||||||
|
lateinit var myRepo: MyRepository
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
if (VERSION.SDK_INT >= 28) HiddenApiBypass.setHiddenApiExemptions("")
|
if (VERSION.SDK_INT >= 28) HiddenApiBypass.setHiddenApiExemptions("")
|
||||||
SP = SharedPrefs(applicationContext)
|
SP = SharedPrefs(applicationContext)
|
||||||
|
val dbHelper = MyDbHelper(this)
|
||||||
|
myRepo = MyRepository(dbHelper)
|
||||||
Privilege.initialize(applicationContext)
|
Privilege.initialize(applicationContext)
|
||||||
Privilege.updateStatus()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
app/src/main/java/com/bintianqi/owndroid/MyDbHelper.kt
Normal file
15
app/src/main/java/com/bintianqi/owndroid/MyDbHelper.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
|
||||||
|
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 1) {
|
||||||
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
|
db.execSQL("CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
|
||||||
|
"signature TEXT, permissions TEXT)")
|
||||||
|
}
|
||||||
|
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
46
app/src/main/java/com/bintianqi/owndroid/MyRepository.kt
Normal file
46
app/src/main/java/com/bintianqi/owndroid/MyRepository.kt
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
|
||||||
|
class MyRepository(val dbHelper: MyDbHelper) {
|
||||||
|
fun getDhizukuClients(): List<DhizukuClientInfo> {
|
||||||
|
val list = mutableListOf<DhizukuClientInfo>()
|
||||||
|
dbHelper.readableDatabase.rawQuery("SELECT * FROM dhizuku_clients", null).use { cursor ->
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
list += DhizukuClientInfo(
|
||||||
|
cursor.getInt(0), cursor.getString(1),
|
||||||
|
cursor.getString(2).split(",").filter { it.isNotEmpty() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
fun checkDhizukuClientPermission(uid: Int, signature: String?, permission: String): Boolean {
|
||||||
|
val cursor = if (signature == null) {
|
||||||
|
dbHelper.readableDatabase.rawQuery(
|
||||||
|
"SELECT permissions FROM dhizuku_clients WHERE uid = $uid AND signature IS NULL",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
dbHelper.readableDatabase.rawQuery(
|
||||||
|
"SELECT permissions FROM dhizuku_clients WHERE uid = $uid AND signature = ?",
|
||||||
|
arrayOf(signature)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return cursor.use {
|
||||||
|
it.moveToNext() && permission in it.getString(0).split(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setDhizukuClient(info: DhizukuClientInfo) {
|
||||||
|
val cv = ContentValues()
|
||||||
|
cv.put("uid", info.uid)
|
||||||
|
cv.put("signature", info.signature)
|
||||||
|
cv.put("permissions", info.permissions.joinToString(","))
|
||||||
|
dbHelper.writableDatabase.insertWithOnConflict("dhizuku_clients", null, cv,
|
||||||
|
SQLiteDatabase.CONFLICT_REPLACE)
|
||||||
|
}
|
||||||
|
fun deleteDhizukuClient(info: DhizukuClientInfo) {
|
||||||
|
dbHelper.writableDatabase.delete("dhizuku_clients", "uid = ${info.uid}", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package com.bintianqi.owndroid
|
|||||||
import android.app.ActivityOptions
|
import android.app.ActivityOptions
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.admin.DeviceAdminInfo
|
||||||
|
import android.app.admin.DeviceAdminReceiver
|
||||||
import android.app.admin.DevicePolicyManager
|
import android.app.admin.DevicePolicyManager
|
||||||
import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback
|
import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback
|
||||||
import android.app.admin.FactoryResetProtectionPolicy
|
import android.app.admin.FactoryResetProtectionPolicy
|
||||||
@@ -30,18 +32,25 @@ import androidx.lifecycle.application
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.bintianqi.owndroid.Privilege.DAR
|
import com.bintianqi.owndroid.Privilege.DAR
|
||||||
import com.bintianqi.owndroid.Privilege.DPM
|
import com.bintianqi.owndroid.Privilege.DPM
|
||||||
|
import com.bintianqi.owndroid.dpm.ACTIVATE_DEVICE_OWNER_COMMAND
|
||||||
import com.bintianqi.owndroid.dpm.AppStatus
|
import com.bintianqi.owndroid.dpm.AppStatus
|
||||||
import com.bintianqi.owndroid.dpm.CaCertInfo
|
import com.bintianqi.owndroid.dpm.CaCertInfo
|
||||||
|
import com.bintianqi.owndroid.dpm.DelegatedAdmin
|
||||||
|
import com.bintianqi.owndroid.dpm.DeviceAdmin
|
||||||
import com.bintianqi.owndroid.dpm.FrpPolicyInfo
|
import com.bintianqi.owndroid.dpm.FrpPolicyInfo
|
||||||
import com.bintianqi.owndroid.dpm.HardwareProperties
|
import com.bintianqi.owndroid.dpm.HardwareProperties
|
||||||
import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo
|
import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo
|
||||||
import com.bintianqi.owndroid.dpm.SystemOptionsStatus
|
import com.bintianqi.owndroid.dpm.SystemOptionsStatus
|
||||||
import com.bintianqi.owndroid.dpm.SystemUpdatePolicyInfo
|
import com.bintianqi.owndroid.dpm.SystemUpdatePolicyInfo
|
||||||
|
import com.bintianqi.owndroid.dpm.delegatedScopesList
|
||||||
import com.bintianqi.owndroid.dpm.getPackageInstaller
|
import com.bintianqi.owndroid.dpm.getPackageInstaller
|
||||||
import com.bintianqi.owndroid.dpm.isValidPackageName
|
import com.bintianqi.owndroid.dpm.isValidPackageName
|
||||||
import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
|
import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
|
||||||
import com.bintianqi.owndroid.dpm.permissionList
|
import com.bintianqi.owndroid.dpm.permissionList
|
||||||
import com.bintianqi.owndroid.dpm.temperatureTypes
|
import com.bintianqi.owndroid.dpm.temperatureTypes
|
||||||
|
import com.rosan.dhizuku.api.Dhizuku
|
||||||
|
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
@@ -56,6 +65,7 @@ import java.security.cert.X509Certificate
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class MyViewModel(application: Application): AndroidViewModel(application) {
|
class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||||
|
val myRepo = getApplication<MyApplication>().myRepo
|
||||||
val PM = application.packageManager
|
val PM = application.packageManager
|
||||||
val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
|
val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
|
||||||
fun changeTheme(newTheme: ThemeSettings) {
|
fun changeTheme(newTheme: ThemeSettings) {
|
||||||
@@ -787,6 +797,205 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
DPM.installSystemUpdate(DAR, uri, application.mainExecutor, callback)
|
DPM.installSystemUpdate(DAR, uri, application.mainExecutor, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun isCreatingWorkProfileAllowed(): Boolean {
|
||||||
|
return DPM.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
|
||||||
|
}
|
||||||
|
fun activateDoByShizuku(callback: (Boolean, String?) -> Unit) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
useShizuku(application) { service ->
|
||||||
|
try {
|
||||||
|
val result = IUserService.Stub.asInterface(service)
|
||||||
|
.execute(ACTIVATE_DEVICE_OWNER_COMMAND)
|
||||||
|
if (result == null || result.getInt("code", -1) != 0) {
|
||||||
|
callback(false, null)
|
||||||
|
} else {
|
||||||
|
Privilege.updateStatus()
|
||||||
|
callback(
|
||||||
|
true, result.getString("output") + "\n" + result.getString("error")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
callback(false, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun activateDoByRoot(callback: (Boolean, String?) -> Unit) {
|
||||||
|
Shell.getShell { shell ->
|
||||||
|
if(shell.isRoot) {
|
||||||
|
val result = Shell.cmd(ACTIVATE_DEVICE_OWNER_COMMAND).exec()
|
||||||
|
val output = result.out.joinToString("\n") + "\n" + result.err.joinToString("\n")
|
||||||
|
Privilege.updateStatus()
|
||||||
|
callback(result.isSuccess, output)
|
||||||
|
} else {
|
||||||
|
callback(false, application.getString(R.string.permission_denied))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun activateDoByDhizuku(callback: (Boolean, String?) -> Unit) {
|
||||||
|
DPM.transferOwnership(DAR, MyAdminComponent, null)
|
||||||
|
SP.dhizuku = false
|
||||||
|
Privilege.initialize(application)
|
||||||
|
callback(true, null)
|
||||||
|
}
|
||||||
|
fun activateDhizukuMode(callback: (Boolean, String?) -> Unit) {
|
||||||
|
fun onSucceed() {
|
||||||
|
SP.dhizuku = true
|
||||||
|
Privilege.initialize(application)
|
||||||
|
callback(true, null)
|
||||||
|
}
|
||||||
|
if (Dhizuku.init(application)) {
|
||||||
|
if (Dhizuku.isPermissionGranted()) {
|
||||||
|
onSucceed()
|
||||||
|
} else {
|
||||||
|
Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() {
|
||||||
|
override fun onRequestPermission(grantResult: Int) {
|
||||||
|
if(grantResult == PackageManager.PERMISSION_GRANTED) onSucceed()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback(false, application.getString(R.string.failed_to_init_dhizuku))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun clearDeviceOwner() {
|
||||||
|
DPM.clearDeviceOwnerApp(application.packageName)
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun clearProfileOwner() {
|
||||||
|
DPM.clearProfileOwner(MyAdminComponent)
|
||||||
|
}
|
||||||
|
fun deactivateDhizukuMode() {
|
||||||
|
SP.dhizuku = false
|
||||||
|
Privilege.initialize(application)
|
||||||
|
}
|
||||||
|
val dhizukuClients = MutableStateFlow(emptyList<Pair<DhizukuClientInfo, AppInfo>>())
|
||||||
|
fun getDhizukuClients() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dhizukuClients.value = myRepo.getDhizukuClients().mapNotNull {
|
||||||
|
val packageName = PM.getNameForUid(it.uid)
|
||||||
|
if (packageName == null) {
|
||||||
|
myRepo.deleteDhizukuClient(it)
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
it to getAppInfo(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun getDhizukuServerEnabled(): Boolean {
|
||||||
|
return SP.dhizukuServer
|
||||||
|
}
|
||||||
|
fun setDhizukuServerEnabled(status: Boolean) {
|
||||||
|
SP.dhizukuServer = status
|
||||||
|
}
|
||||||
|
fun updateDhizukuClient(info: DhizukuClientInfo) {
|
||||||
|
myRepo.setDhizukuClient(info)
|
||||||
|
dhizukuClients.update { list ->
|
||||||
|
val ml = list.toMutableList()
|
||||||
|
val index = ml.indexOfFirst { it.first.uid == info.uid }
|
||||||
|
ml[index] = info to ml[index].second
|
||||||
|
ml
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun getLockScreenInfo(): String {
|
||||||
|
return DPM.deviceOwnerLockScreenInfo?.toString() ?: ""
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun setLockScreenInfo(text: String) {
|
||||||
|
DPM.setDeviceOwnerLockScreenInfo(DAR, text)
|
||||||
|
}
|
||||||
|
val delegatedAdmins = MutableStateFlow(emptyList<DelegatedAdmin>())
|
||||||
|
@RequiresApi(26)
|
||||||
|
fun getDelegatedAdmins() {
|
||||||
|
val list = mutableListOf<DelegatedAdmin>()
|
||||||
|
delegatedScopesList.forEach { scope ->
|
||||||
|
DPM.getDelegatePackages(DAR, scope.id)?.forEach { pkg ->
|
||||||
|
val index = list.indexOfFirst { it.app.name == pkg }
|
||||||
|
if (index == -1) {
|
||||||
|
list += DelegatedAdmin(getAppInfo(pkg), listOf(scope.id))
|
||||||
|
} else {
|
||||||
|
list[index] = DelegatedAdmin(list[index].app, list[index].scopes + scope.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegatedAdmins.value = list
|
||||||
|
}
|
||||||
|
@RequiresApi(26)
|
||||||
|
fun setDelegatedAdmin(name: String, scopes: List<String>) {
|
||||||
|
DPM.setDelegatedScopes(DAR, name, scopes)
|
||||||
|
getDelegatedAdmins()
|
||||||
|
}
|
||||||
|
@RequiresApi(34)
|
||||||
|
fun getDeviceFinanced(): Boolean {
|
||||||
|
return DPM.isDeviceFinanced
|
||||||
|
}
|
||||||
|
@RequiresApi(33)
|
||||||
|
fun getDpmRh(): String? {
|
||||||
|
return DPM.devicePolicyManagementRoleHolderPackage
|
||||||
|
}
|
||||||
|
fun getStorageEncryptionStatus(): Int {
|
||||||
|
return DPM.storageEncryptionStatus
|
||||||
|
}
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun getDeviceIdAttestationSupported(): Boolean {
|
||||||
|
return DPM.isDeviceIdAttestationSupported
|
||||||
|
}
|
||||||
|
@RequiresApi(30)
|
||||||
|
fun getUniqueDeviceAttestationSupported(): Boolean {
|
||||||
|
return DPM.isUniqueDeviceAttestationSupported
|
||||||
|
}
|
||||||
|
fun getActiveAdmins(): String {
|
||||||
|
return DPM.activeAdmins?.joinToString("\n") {
|
||||||
|
it.flattenToShortString()
|
||||||
|
} ?: application.getString(R.string.none)
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun getShortSupportMessage(): String {
|
||||||
|
return DPM.getShortSupportMessage(DAR)?.toString() ?: ""
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun getLongSupportMessage(): String {
|
||||||
|
return DPM.getLongSupportMessage(DAR)?.toString() ?: ""
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun setShortSupportMessage(text: String?) {
|
||||||
|
DPM.setShortSupportMessage(DAR, text)
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun setLongSupportMessage(text: String?) {
|
||||||
|
DPM.setLongSupportMessage(DAR, text)
|
||||||
|
}
|
||||||
|
val deviceAdminReceivers = MutableStateFlow(emptyList<DeviceAdmin>())
|
||||||
|
fun getDeviceAdminReceivers() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
deviceAdminReceivers.value = PM.queryBroadcastReceivers(
|
||||||
|
Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
|
||||||
|
PackageManager.GET_META_DATA
|
||||||
|
).mapNotNull {
|
||||||
|
try {
|
||||||
|
DeviceAdminInfo(application, it)
|
||||||
|
} catch(_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.filter {
|
||||||
|
it.isVisible && it.packageName != "com.bintianqi.owndroid" &&
|
||||||
|
it.activityInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 0
|
||||||
|
}.map {
|
||||||
|
DeviceAdmin(getAppInfo(it.packageName), it.component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun transferOwnership(component: ComponentName) {
|
||||||
|
DPM.transferOwnership(DAR, component, null)
|
||||||
|
Privilege.updateStatus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ThemeSettings(
|
data class ThemeSettings(
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ object Privilege {
|
|||||||
}
|
}
|
||||||
DPM = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
|
DPM = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
|
||||||
DAR = MyAdminComponent
|
DAR = MyAdminComponent
|
||||||
|
updateStatus()
|
||||||
}
|
}
|
||||||
lateinit var DPM: DevicePolicyManager
|
lateinit var DPM: DevicePolicyManager
|
||||||
private set
|
private set
|
||||||
|
|||||||
@@ -2,13 +2,8 @@ package com.bintianqi.owndroid.dpm
|
|||||||
|
|
||||||
import android.app.admin.DevicePolicyManager
|
import android.app.admin.DevicePolicyManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import android.os.PersistableBundle
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
@@ -24,9 +19,11 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.ime
|
import androidx.compose.foundation.layout.ime
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
@@ -57,6 +54,7 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||||
import androidx.compose.material3.MaterialTheme.typography
|
import androidx.compose.material3.MaterialTheme.typography
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@@ -67,10 +65,9 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateMapOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -85,15 +82,13 @@ import androidx.compose.ui.state.ToggleableState
|
|||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE
|
import com.bintianqi.owndroid.AppInfo
|
||||||
import com.bintianqi.owndroid.DhizukuClientInfo
|
import com.bintianqi.owndroid.DhizukuClientInfo
|
||||||
import com.bintianqi.owndroid.DhizukuPermissions
|
import com.bintianqi.owndroid.DhizukuPermissions
|
||||||
import com.bintianqi.owndroid.HorizontalPadding
|
import com.bintianqi.owndroid.HorizontalPadding
|
||||||
import com.bintianqi.owndroid.IUserService
|
import com.bintianqi.owndroid.MyViewModel
|
||||||
import com.bintianqi.owndroid.MyAdminComponent
|
|
||||||
import com.bintianqi.owndroid.Privilege
|
import com.bintianqi.owndroid.Privilege
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
import com.bintianqi.owndroid.SP
|
|
||||||
import com.bintianqi.owndroid.Settings
|
import com.bintianqi.owndroid.Settings
|
||||||
import com.bintianqi.owndroid.showOperationResultToast
|
import com.bintianqi.owndroid.showOperationResultToast
|
||||||
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
||||||
@@ -104,36 +99,32 @@ import com.bintianqi.owndroid.ui.MySmallTitleScaffold
|
|||||||
import com.bintianqi.owndroid.ui.NavIcon
|
import com.bintianqi.owndroid.ui.NavIcon
|
||||||
import com.bintianqi.owndroid.ui.Notes
|
import com.bintianqi.owndroid.ui.Notes
|
||||||
import com.bintianqi.owndroid.ui.SwitchItem
|
import com.bintianqi.owndroid.ui.SwitchItem
|
||||||
import com.bintianqi.owndroid.useShizuku
|
|
||||||
import com.bintianqi.owndroid.yesOrNo
|
import com.bintianqi.owndroid.yesOrNo
|
||||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
import com.rosan.dhizuku.api.Dhizuku
|
|
||||||
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
|
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
@Serializable data class WorkModes(val canNavigateUp: Boolean)
|
@Serializable data class WorkModes(val canNavigateUp: Boolean)
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun WorkModesScreen(
|
fun WorkModesScreen(
|
||||||
params: WorkModes, onNavigateUp: () -> Unit, onActivate: () -> Unit, onNavigate: (Any) -> Unit
|
vm: MyViewModel, params: WorkModes, onNavigateUp: () -> Unit, onActivate: () -> Unit,
|
||||||
|
onDeactivate: () -> Unit, onNavigate: (Any) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val coroutine = rememberCoroutineScope()
|
|
||||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||||
/** 0: none, 1: device owner, 2: circular progress indicator, 3: result, 4: deactivate, 5: command */
|
/** 0: none, 1: device owner, 2: circular progress indicator, 3: result, 4: deactivate, 5: command */
|
||||||
var dialog by remember { mutableIntStateOf(0) }
|
var dialog by remember { mutableIntStateOf(0) }
|
||||||
var operationSucceed by remember { mutableStateOf(false) }
|
var operationSucceed by remember { mutableStateOf(false) }
|
||||||
|
var resultText by remember { mutableStateOf("") }
|
||||||
LaunchedEffect(privilege) {
|
LaunchedEffect(privilege) {
|
||||||
if (!params.canNavigateUp && privilege.device) {
|
if (!params.canNavigateUp && privilege.device) {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
if (dialog != 3) { // Activated by ADB command
|
if (dialog != 3) { // Activated by ADB command
|
||||||
operationSucceed = true
|
operationSucceed = true
|
||||||
|
resultText = ""
|
||||||
dialog = 3
|
dialog = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,90 +184,36 @@ fun WorkModesScreen(
|
|||||||
},
|
},
|
||||||
contentWindowInsets = WindowInsets.ime
|
contentWindowInsets = WindowInsets.ime
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
var navigateUpOnSucceed by remember { mutableStateOf(true) }
|
fun handleResult(succeeded: Boolean, output: String?) {
|
||||||
var resultText by remember { mutableStateOf("") }
|
operationSucceed = succeeded
|
||||||
fun handleResult(succeeded: Boolean, activateSucceeded: Boolean, output: String?) {
|
|
||||||
if(succeeded) {
|
|
||||||
operationSucceed = activateSucceeded
|
|
||||||
resultText = output ?: ""
|
resultText = output ?: ""
|
||||||
dialog = 3
|
dialog = 3
|
||||||
Privilege.updateStatus()
|
}
|
||||||
} else {
|
Column(Modifier.fillMaxSize().padding(paddingValues)) {
|
||||||
dialog = 0
|
if (!privilege.profile) {
|
||||||
context.showOperationResultToast(false)
|
WorkingModeItem(R.string.device_owner, privilege.device) {
|
||||||
|
if (!privilege.device || (VERSION.SDK_INT >= 28 && privilege.dhizuku)) {
|
||||||
|
dialog = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(paddingValues)) {
|
|
||||||
if(!privilege.profile && (VERSION.SDK_INT >= 28 || !privilege.dhizuku)) Row(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable(!privilege.device || privilege.dhizuku) { dialog = 1 }
|
|
||||||
.background(if (privilege.device) colorScheme.primaryContainer else Color.Transparent)
|
|
||||||
.padding(HorizontalPadding, 10.dp),
|
|
||||||
Arrangement.SpaceBetween, Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Text(stringResource(R.string.device_owner), style = typography.titleLarge)
|
|
||||||
if(!privilege.device || privilege.dhizuku) Text(
|
|
||||||
stringResource(R.string.recommended), color = colorScheme.primary, style = typography.labelLarge
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Icon(
|
if (privilege.profile) WorkingModeItem(R.string.profile_owner, true) { }
|
||||||
if(privilege.device) Icons.Default.Check else Icons.AutoMirrored.Default.KeyboardArrowRight, null,
|
if (privilege.dhizuku || !privilege.activated) {
|
||||||
tint = if(privilege.device) colorScheme.primary else colorScheme.onBackground
|
WorkingModeItem(R.string.dhizuku, privilege.dhizuku) {
|
||||||
)
|
if (!privilege.dhizuku) {
|
||||||
}
|
|
||||||
if(privilege.profile) Row(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(colorScheme.primaryContainer)
|
|
||||||
.padding(HorizontalPadding, 10.dp),
|
|
||||||
Arrangement.SpaceBetween, Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Text(stringResource(R.string.profile_owner), style = typography.titleLarge)
|
|
||||||
}
|
|
||||||
Icon(Icons.Default.Check, null, tint = colorScheme.primary)
|
|
||||||
}
|
|
||||||
if(privilege.dhizuku || !(privilege.device || privilege.profile)) Row(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable(!privilege.dhizuku) {
|
|
||||||
dialog = 2
|
dialog = 2
|
||||||
activateDhizukuMode(context, ::handleResult)
|
vm.activateDhizukuMode(::handleResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.background(if (privilege.dhizuku) colorScheme.primaryContainer else Color.Transparent)
|
|
||||||
.padding(HorizontalPadding, 10.dp),
|
|
||||||
Arrangement.SpaceBetween, Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.dhizuku), style = typography.titleLarge)
|
|
||||||
Icon(
|
|
||||||
if(privilege.dhizuku) Icons.Default.Check else Icons.AutoMirrored.Default.KeyboardArrowRight, null,
|
|
||||||
tint = if(privilege.dhizuku) colorScheme.primary else colorScheme.onBackground
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if(
|
if(
|
||||||
privilege.work || (VERSION.SDK_INT < 24 ||
|
privilege.work || (VERSION.SDK_INT < 24 || vm.isCreatingWorkProfileAllowed())
|
||||||
Privilege.DPM.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE))
|
|
||||||
) Row(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable(!privilege.work) { onNavigate(CreateWorkProfile) }
|
|
||||||
.background(if (privilege.device) colorScheme.primaryContainer else Color.Transparent)
|
|
||||||
.padding(HorizontalPadding, 10.dp),
|
|
||||||
Arrangement.SpaceBetween, Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Column {
|
WorkingModeItem(R.string.work_profile, privilege.work) {
|
||||||
Text(stringResource(R.string.work_profile), style = typography.titleLarge)
|
if (!privilege.work) onNavigate(CreateWorkProfile)
|
||||||
}
|
}
|
||||||
Icon(
|
|
||||||
if(privilege.work) Icons.Default.Check else Icons.AutoMirrored.Default.KeyboardArrowRight, null,
|
|
||||||
tint = if(privilege.device) colorScheme.primary else colorScheme.onBackground
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if ((privilege.device || privilege.profile) && !privilege.dhizuku) Row(
|
if (privilege.activated && !privilege.dhizuku) Row(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(top = 20.dp)
|
.padding(top = 20.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -302,27 +239,29 @@ fun WorkModesScreen(
|
|||||||
title = { Text(stringResource(R.string.activate_method)) },
|
title = { Text(stringResource(R.string.activate_method)) },
|
||||||
text = {
|
text = {
|
||||||
FlowRow(Modifier.fillMaxWidth()) {
|
FlowRow(Modifier.fillMaxWidth()) {
|
||||||
if(!privilege.dhizuku) Button({
|
if (!privilege.dhizuku) {
|
||||||
dialog = 2
|
Button({ dialog = 5 }, Modifier.padding(end = 8.dp)) {
|
||||||
coroutine.launch {
|
Text(stringResource(R.string.adb_command))
|
||||||
activateUsingShizuku(context, ::handleResult)
|
|
||||||
}
|
}
|
||||||
|
Button({
|
||||||
|
dialog = 2
|
||||||
|
vm.activateDoByShizuku(::handleResult)
|
||||||
}, Modifier.padding(end = 8.dp)) {
|
}, Modifier.padding(end = 8.dp)) {
|
||||||
Text(stringResource(R.string.shizuku))
|
Text(stringResource(R.string.shizuku))
|
||||||
}
|
}
|
||||||
if(!privilege.dhizuku) Button({
|
Button({
|
||||||
dialog = 2
|
dialog = 2
|
||||||
activateUsingRoot(context, ::handleResult)
|
vm.activateDoByRoot(::handleResult)
|
||||||
}, Modifier.padding(end = 8.dp)) {
|
}, Modifier.padding(end = 8.dp)) {
|
||||||
Text("Root")
|
Text("Root")
|
||||||
}
|
}
|
||||||
if(VERSION.SDK_INT >= 28) Button({
|
}
|
||||||
|
if (VERSION.SDK_INT >= 28 && privilege.dhizuku) Button({
|
||||||
dialog = 2
|
dialog = 2
|
||||||
activateUsingDhizuku(context, ::handleResult)
|
vm.activateDoByDhizuku(::handleResult)
|
||||||
}, Modifier.padding(end = 8.dp)) {
|
}, Modifier.padding(end = 8.dp)) {
|
||||||
Text(stringResource(R.string.dhizuku))
|
Text(stringResource(R.string.dhizuku))
|
||||||
}
|
}
|
||||||
if (!privilege.dhizuku) Button({ dialog = 5 }) { Text(stringResource(R.string.adb_command)) }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@@ -334,16 +273,14 @@ fun WorkModesScreen(
|
|||||||
if(dialog == 3) AlertDialog(
|
if(dialog == 3) AlertDialog(
|
||||||
title = { Text(stringResource(if(operationSucceed) R.string.succeeded else R.string.failed)) },
|
title = { Text(stringResource(if(operationSucceed) R.string.succeeded else R.string.failed)) },
|
||||||
text = {
|
text = {
|
||||||
Column(Modifier
|
Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
|
||||||
.fillMaxWidth()
|
|
||||||
.verticalScroll(rememberScrollState())) {
|
|
||||||
Text(resultText)
|
Text(resultText)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton({
|
TextButton({
|
||||||
dialog = 0
|
dialog = 0
|
||||||
if(navigateUpOnSucceed && operationSucceed && !params.canNavigateUp) onActivate()
|
if (operationSucceed && !params.canNavigateUp) onActivate()
|
||||||
}) {
|
}) {
|
||||||
Text(stringResource(R.string.confirm))
|
Text(stringResource(R.string.confirm))
|
||||||
}
|
}
|
||||||
@@ -365,18 +302,17 @@ fun WorkModesScreen(
|
|||||||
TextButton(
|
TextButton(
|
||||||
{
|
{
|
||||||
if(privilege.dhizuku) {
|
if(privilege.dhizuku) {
|
||||||
SP.dhizuku = false
|
vm.deactivateDhizukuMode()
|
||||||
Privilege.initialize(context)
|
|
||||||
Privilege.updateStatus()
|
|
||||||
} else {
|
} else {
|
||||||
if(privilege.device) {
|
if(privilege.device) {
|
||||||
Privilege.DPM.clearDeviceOwnerApp(context.packageName)
|
vm.clearDeviceOwner()
|
||||||
} else if(VERSION.SDK_INT >= 24) {
|
} else if(VERSION.SDK_INT >= 24) {
|
||||||
Privilege.DPM.clearProfileOwner(MyAdminComponent)
|
vm.clearProfileOwner()
|
||||||
}
|
}
|
||||||
// Status updated in Receiver.onDisabled()
|
// Status updated in Receiver.onDisabled()
|
||||||
}
|
}
|
||||||
dialog = 0
|
dialog = 0
|
||||||
|
onDeactivate()
|
||||||
},
|
},
|
||||||
enabled = time == 0,
|
enabled = time == 0,
|
||||||
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
|
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
|
||||||
@@ -403,95 +339,22 @@ fun WorkModesScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun activateUsingShizuku(context: Context, callback: (Boolean, Boolean, String?) -> Unit) {
|
@Composable
|
||||||
useShizuku(context) { service ->
|
fun WorkingModeItem(text: Int, active: Boolean, onClick: () -> Unit) {
|
||||||
try {
|
Row(
|
||||||
val result = IUserService.Stub.asInterface(service).execute(ACTIVATE_DEVICE_OWNER_COMMAND)
|
Modifier
|
||||||
if (result == null) {
|
.fillMaxWidth()
|
||||||
callback(false, false, null)
|
.clickable(onClick = onClick)
|
||||||
} else {
|
.background(if (active) colorScheme.primaryContainer else Color.Transparent)
|
||||||
callback(
|
.padding(HorizontalPadding, 10.dp),
|
||||||
true, result.getInt("code", -1) == 0,
|
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||||
result.getString("output") + "\n" + result.getString("error")
|
) {
|
||||||
|
Text(stringResource(text), style = typography.titleLarge)
|
||||||
|
Icon(
|
||||||
|
if(active) Icons.Default.Check else Icons.AutoMirrored.Default.KeyboardArrowRight, null,
|
||||||
|
tint = if(active) colorScheme.primary else colorScheme.onBackground
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
callback(false, false, null)
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun activateUsingRoot(context: Context, callback: (Boolean, Boolean, String?) -> Unit) {
|
|
||||||
Shell.getShell { shell ->
|
|
||||||
if(shell.isRoot) {
|
|
||||||
val result = Shell.cmd(ACTIVATE_DEVICE_OWNER_COMMAND).exec()
|
|
||||||
val output = result.out.joinToString("\n") + "\n" + result.err.joinToString("\n")
|
|
||||||
callback(true, result.isSuccess, output)
|
|
||||||
} else {
|
|
||||||
callback(true, false, context.getString(R.string.permission_denied))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(28)
|
|
||||||
fun activateUsingDhizuku(context: Context, callback: (Boolean, Boolean, String?) -> Unit) {
|
|
||||||
fun doTransfer() {
|
|
||||||
try {
|
|
||||||
if (SP.dhizuku) {
|
|
||||||
Privilege.DPM.transferOwnership(Privilege.DAR, MyAdminComponent, PersistableBundle())
|
|
||||||
SP.dhizuku = false
|
|
||||||
Privilege.initialize(context)
|
|
||||||
} else {
|
|
||||||
val dpm = binderWrapperDevicePolicyManager(context)
|
|
||||||
if (dpm == null) {
|
|
||||||
callback(false, false, null)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
dpm.transferOwnership(Dhizuku.getOwnerComponent(), MyAdminComponent, PersistableBundle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback(true, true, null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
callback(false, false, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(Dhizuku.init(context)) {
|
|
||||||
if(Dhizuku.isPermissionGranted()) {
|
|
||||||
doTransfer()
|
|
||||||
} else {
|
|
||||||
Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() {
|
|
||||||
override fun onRequestPermission(grantResult: Int) {
|
|
||||||
if(grantResult == PackageManager.PERMISSION_GRANTED) doTransfer()
|
|
||||||
else callback(false, false, null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
callback(true, false, context.getString(R.string.failed_to_init_dhizuku))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun activateDhizukuMode(context: Context, callback: (Boolean, Boolean, String?) -> Unit) {
|
|
||||||
fun onSucceed() {
|
|
||||||
SP.dhizuku = true
|
|
||||||
Privilege.initialize(context)
|
|
||||||
callback(true, true, null)
|
|
||||||
}
|
|
||||||
if(Dhizuku.init(context)) {
|
|
||||||
if(Dhizuku.isPermissionGranted()) {
|
|
||||||
onSucceed()
|
|
||||||
} else {
|
|
||||||
Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() {
|
|
||||||
override fun onRequestPermission(grantResult: Int) {
|
|
||||||
if(grantResult == PackageManager.PERMISSION_GRANTED) onSucceed()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
callback(true, false, context.getString(R.string.failed_to_init_dhizuku))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const val ACTIVATE_DEVICE_OWNER_COMMAND = "dpm set-device-owner com.bintianqi.owndroid/.Receiver"
|
const val ACTIVATE_DEVICE_OWNER_COMMAND = "dpm set-device-owner com.bintianqi.owndroid/.Receiver"
|
||||||
@@ -499,41 +362,23 @@ const val ACTIVATE_DEVICE_OWNER_COMMAND = "dpm set-device-owner com.bintianqi.ow
|
|||||||
@Serializable object DhizukuServerSettings
|
@Serializable object DhizukuServerSettings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
|
fun DhizukuServerSettingsScreen(
|
||||||
val context = LocalContext.current
|
dhizukuClients: StateFlow<List<Pair<DhizukuClientInfo, AppInfo>>>,
|
||||||
val pm = context.packageManager
|
getDhizukuClients: () -> Unit, updateDhizukuClient: (DhizukuClientInfo) -> Unit,
|
||||||
val file = context.filesDir.resolve(DHIZUKU_CLIENTS_FILE)
|
getServerEnabled: () -> Boolean, setServerEnabled: (Boolean) -> Unit, onNavigateUp: () -> Unit
|
||||||
var enabled by remember { mutableStateOf(SP.dhizukuServer) }
|
) {
|
||||||
val clients = remember { mutableStateListOf<DhizukuClientInfo>() }
|
var enabled by remember { mutableStateOf(getServerEnabled()) }
|
||||||
fun changeEnableState(status: Boolean) {
|
val clients by dhizukuClients.collectAsStateWithLifecycle()
|
||||||
enabled = status
|
LaunchedEffect(Unit) { getDhizukuClients() }
|
||||||
SP.dhizukuServer = status
|
|
||||||
}
|
|
||||||
fun writeList() {
|
|
||||||
file.writeText(Json.encodeToString(clients.toList()))
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (!file.exists()) file.writeText("[]")
|
|
||||||
}
|
|
||||||
LaunchedEffect(enabled) {
|
|
||||||
if (enabled) {
|
|
||||||
clients.clear()
|
|
||||||
val json = Json { ignoreUnknownKeys = true }
|
|
||||||
clients.addAll(json.decodeFromString<List<DhizukuClientInfo>>(file.readText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MyLazyScaffold(R.string.dhizuku_server, onNavigateUp) {
|
MyLazyScaffold(R.string.dhizuku_server, onNavigateUp) {
|
||||||
item {
|
item {
|
||||||
SwitchItem(R.string.enable, getState = { SP.dhizukuServer }, onCheckedChange = ::changeEnableState)
|
SwitchItem(R.string.enable, enabled, {
|
||||||
|
setServerEnabled(it)
|
||||||
|
enabled = it
|
||||||
|
})
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
||||||
}
|
}
|
||||||
if (enabled) itemsIndexed(clients) { index, client ->
|
if (enabled) items(clients) { (client, app) ->
|
||||||
val name = pm.getNameForUid(client.uid)
|
|
||||||
if (name == null) {
|
|
||||||
clients.dropWhile { it.uid == client.uid }
|
|
||||||
writeList()
|
|
||||||
} else {
|
|
||||||
val info = pm.getApplicationInfo(name, 0)
|
|
||||||
var expand by remember { mutableStateOf(false) }
|
var expand by remember { mutableStateOf(false) }
|
||||||
Card(
|
Card(
|
||||||
Modifier
|
Modifier
|
||||||
@@ -548,14 +393,12 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
|
|||||||
) {
|
) {
|
||||||
Row(Modifier.weight(1F), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.weight(1F), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Image(
|
Image(
|
||||||
rememberDrawablePainter(info.loadIcon(pm)), null,
|
rememberDrawablePainter(app.icon), null,
|
||||||
Modifier
|
Modifier.padding(end = 16.dp).size(45.dp)
|
||||||
.padding(end = 16.dp)
|
|
||||||
.size(45.dp)
|
|
||||||
)
|
)
|
||||||
Column {
|
Column {
|
||||||
Text(info.loadLabel(pm).toString(), style = typography.titleMedium)
|
Text(app.label, style = typography.titleMedium)
|
||||||
Text(name, Modifier.alpha(0.7F), style = typography.bodyMedium)
|
Text(app.name, Modifier.alpha(0.7F), style = typography.bodyMedium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val ts = when (DhizukuPermissions.filter { it !in client.permissions }.size) {
|
val ts = when (DhizukuPermissions.filter { it !in client.permissions }.size) {
|
||||||
@@ -565,9 +408,10 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
|
|||||||
}
|
}
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
TriStateCheckbox(ts, {
|
TriStateCheckbox(ts, {
|
||||||
clients[index] = when (ts) {
|
if (ts == ToggleableState.Off) {
|
||||||
ToggleableState.On, ToggleableState.Indeterminate -> client.copy(permissions = emptyList())
|
updateDhizukuClient(client.copy(permissions = DhizukuPermissions))
|
||||||
ToggleableState.Off -> client.copy(permissions = DhizukuPermissions)
|
} else {
|
||||||
|
updateDhizukuClient(client.copy(permissions = emptyList()))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
val degrees by animateFloatAsState(if(expand) 180F else 0F)
|
val degrees by animateFloatAsState(if(expand) 180F else 0F)
|
||||||
@@ -581,14 +425,17 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
|
|||||||
mapOf(
|
mapOf(
|
||||||
"remote_transact" to "Remote transact", "remote_process" to "Remote process",
|
"remote_transact" to "Remote transact", "remote_process" to "Remote process",
|
||||||
"user_service" to "User service", "delegated_scopes" to "Delegated scopes",
|
"user_service" to "User service", "delegated_scopes" to "Delegated scopes",
|
||||||
"other" to context.getString(R.string.other)
|
"other" to "Other"
|
||||||
).forEach { (k, v) ->
|
).forEach { (k, v) ->
|
||||||
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
Row(
|
||||||
|
Modifier.fillMaxWidth(), Arrangement.SpaceBetween,
|
||||||
|
Alignment.CenterVertically
|
||||||
|
) {
|
||||||
Text(v)
|
Text(v)
|
||||||
Checkbox(k in client.permissions, {
|
Checkbox(k in client.permissions, {
|
||||||
val newPermissions = if (it) client.permissions.plus(k) else client.permissions.minus(k)
|
updateDhizukuClient(client.copy(
|
||||||
clients[index] = client.copy(permissions = newPermissions)
|
permissions = client.permissions.run { if (it) plus(k) else minus(k) }
|
||||||
writeList()
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -597,17 +444,18 @@ fun DhizukuServerSettingsScreen(onNavigateUp: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable object LockScreenInfo
|
@Serializable object LockScreenInfo
|
||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
@Composable
|
@Composable
|
||||||
fun LockScreenInfoScreen(onNavigateUp: () -> Unit) {
|
fun LockScreenInfoScreen(
|
||||||
|
getText: () -> String, setText: (String) -> Unit, onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
var infoText by remember { mutableStateOf(Privilege.DPM.deviceOwnerLockScreenInfo?.toString() ?: "") }
|
var infoText by remember { mutableStateOf(getText()) }
|
||||||
MyScaffold(R.string.lock_screen_info, onNavigateUp) {
|
MyScaffold(R.string.lock_screen_info, onNavigateUp) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = infoText,
|
value = infoText,
|
||||||
@@ -622,7 +470,7 @@ fun LockScreenInfoScreen(onNavigateUp: () -> Unit) {
|
|||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
focusMgr.clearFocus()
|
focusMgr.clearFocus()
|
||||||
Privilege.DPM.setDeviceOwnerLockScreenInfo(Privilege.DAR, infoText)
|
setText(infoText)
|
||||||
context.showOperationResultToast(true)
|
context.showOperationResultToast(true)
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
@@ -632,7 +480,7 @@ fun LockScreenInfoScreen(onNavigateUp: () -> Unit) {
|
|||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
focusMgr.clearFocus()
|
focusMgr.clearFocus()
|
||||||
Privilege.DPM.setDeviceOwnerLockScreenInfo(Privilege.DAR, null)
|
setText("")
|
||||||
infoText = ""
|
infoText = ""
|
||||||
context.showOperationResultToast(true)
|
context.showOperationResultToast(true)
|
||||||
},
|
},
|
||||||
@@ -645,73 +493,56 @@ fun LockScreenInfoScreen(onNavigateUp: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
data class DelegatedScope(val id: String, val string: Int, val requiresApi: Int = 26)
|
||||||
@Suppress("InlinedApi")
|
@Suppress("InlinedApi")
|
||||||
enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) {
|
val delegatedScopesList = listOf(
|
||||||
AppRestrictions(DevicePolicyManager.DELEGATION_APP_RESTRICTIONS, R.string.manage_application_restrictions),
|
DelegatedScope(DevicePolicyManager.DELEGATION_APP_RESTRICTIONS, R.string.manage_application_restrictions),
|
||||||
BlockUninstall(DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL, R.string.block_uninstall),
|
DelegatedScope(DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL, R.string.block_uninstall),
|
||||||
CertInstall(DevicePolicyManager.DELEGATION_CERT_INSTALL, R.string.manage_certificates),
|
DelegatedScope(DevicePolicyManager.DELEGATION_CERT_INSTALL, R.string.manage_certificates),
|
||||||
CertSelection(DevicePolicyManager.DELEGATION_CERT_SELECTION, R.string.select_keychain_certificates, 29),
|
DelegatedScope(DevicePolicyManager.DELEGATION_CERT_SELECTION, R.string.select_keychain_certificates, 29),
|
||||||
EnableSystemApp(DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP, R.string.enable_system_app),
|
DelegatedScope(DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP, R.string.enable_system_app),
|
||||||
InstallExistingPackage(DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE, R.string.install_existing_packages, 28),
|
DelegatedScope(DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE, R.string.install_existing_packages, 28),
|
||||||
KeepUninstalledPackages(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES, R.string.manage_uninstalled_packages, 28),
|
DelegatedScope(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES, R.string.manage_uninstalled_packages, 28),
|
||||||
NetworkLogging(DevicePolicyManager.DELEGATION_NETWORK_LOGGING, R.string.network_logging, 29),
|
DelegatedScope(DevicePolicyManager.DELEGATION_NETWORK_LOGGING, R.string.network_logging, 29),
|
||||||
PackageAccess(DevicePolicyManager.DELEGATION_PACKAGE_ACCESS, R.string.change_package_state),
|
DelegatedScope(DevicePolicyManager.DELEGATION_PACKAGE_ACCESS, R.string.change_package_state),
|
||||||
PermissionGrant(DevicePolicyManager.DELEGATION_PERMISSION_GRANT, R.string.grant_permissions),
|
DelegatedScope(DevicePolicyManager.DELEGATION_PERMISSION_GRANT, R.string.grant_permissions),
|
||||||
SecurityLogging(DevicePolicyManager.DELEGATION_SECURITY_LOGGING, R.string.security_logging, 31)
|
DelegatedScope(DevicePolicyManager.DELEGATION_SECURITY_LOGGING, R.string.security_logging, 31)
|
||||||
}
|
).filter { VERSION.SDK_INT >= it.requiresApi }
|
||||||
|
|
||||||
|
data class DelegatedAdmin(val app: AppInfo, val scopes: List<String>)
|
||||||
|
|
||||||
@Serializable object DelegatedAdmins
|
@Serializable object DelegatedAdmins
|
||||||
|
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
@Composable
|
@Composable
|
||||||
fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdmin) -> Unit) {
|
fun DelegatedAdminsScreen(
|
||||||
val packages = remember { mutableStateMapOf<String, MutableList<DelegatedScope>>() }
|
delegatedAdmins: StateFlow<List<DelegatedAdmin>>, getDelegatedAdmins: () -> Unit,
|
||||||
fun refresh() {
|
onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdmin) -> Unit
|
||||||
val list = mutableMapOf<String, MutableList<DelegatedScope>>()
|
) {
|
||||||
DelegatedScope.entries.forEach { ds ->
|
val admins by delegatedAdmins.collectAsStateWithLifecycle()
|
||||||
if(VERSION.SDK_INT >= ds.requiresApi) {
|
LaunchedEffect(Unit) { getDelegatedAdmins() }
|
||||||
Privilege.DPM.getDelegatePackages(Privilege.DAR, ds.id)?.forEach { pkg ->
|
MyLazyScaffold(R.string.delegated_admins, onNavigateUp) {
|
||||||
if(list[pkg] != null) {
|
items(admins, { it.app.name }) { (app, scopes) ->
|
||||||
list[pkg]!!.add(ds)
|
|
||||||
} else {
|
|
||||||
list[pkg] = mutableListOf(ds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packages.clear()
|
|
||||||
packages.putAll(list)
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) { refresh() }
|
|
||||||
MyScaffold(R.string.delegated_admins, onNavigateUp, 0.dp) {
|
|
||||||
packages.forEach { (pkg, scopes) ->
|
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 6.dp).animateItem(),
|
||||||
.fillMaxWidth()
|
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||||
.padding(vertical = 8.dp)
|
|
||||||
.padding(start = 14.dp, end = 8.dp),
|
|
||||||
Arrangement.SpaceBetween
|
|
||||||
) {
|
) {
|
||||||
|
Row(Modifier.weight(1F), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Image(
|
||||||
|
painter = rememberDrawablePainter(app.icon), contentDescription = null,
|
||||||
|
modifier = Modifier.padding(start = 12.dp, end = 18.dp).size(40.dp)
|
||||||
|
)
|
||||||
Column {
|
Column {
|
||||||
Text(pkg, style = typography.titleMedium)
|
Text(app.label)
|
||||||
Text(
|
Text(app.name, Modifier.alpha(0.8F), style = typography.bodyMedium)
|
||||||
scopes.size.toString() + " " + stringResource(R.string.delegated_scope),
|
|
||||||
color = colorScheme.onSurfaceVariant, style = typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
IconButton({ onNavigate(AddDelegatedAdmin(pkg, scopes)) }) {
|
}
|
||||||
Icon(Icons.Outlined.Edit, stringResource(R.string.edit))
|
IconButton({ onNavigate(AddDelegatedAdmin(app.name, scopes)) }) {
|
||||||
|
Icon(Icons.Outlined.Edit, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(packages.isEmpty()) Text(
|
item {
|
||||||
stringResource(R.string.none),
|
|
||||||
color = colorScheme.onSurfaceVariant,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(vertical = 4.dp)
|
|
||||||
)
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -723,35 +554,42 @@ fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdm
|
|||||||
Text(stringResource(R.string.add_delegated_admin), style = typography.titleMedium)
|
Text(stringResource(R.string.add_delegated_admin), style = typography.titleMedium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable data class AddDelegatedAdmin(val pkg: String = "", val scopes: List<DelegatedScope> = emptyList())
|
@Serializable data class AddDelegatedAdmin(val pkg: String = "", val scopes: List<String> = emptyList())
|
||||||
|
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
@Composable
|
@Composable
|
||||||
fun AddDelegatedAdminScreen(
|
fun AddDelegatedAdminScreen(
|
||||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
|
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, data: AddDelegatedAdmin,
|
||||||
data: AddDelegatedAdmin, onNavigateUp: () -> Unit
|
setDelegatedAdmin: (String, List<String>) -> Unit, onNavigateUp: () -> Unit
|
||||||
) {
|
) {
|
||||||
val updateMode = data.pkg.isNotEmpty()
|
val updateMode = data.pkg.isNotEmpty()
|
||||||
var input by remember { mutableStateOf(data.pkg) }
|
var input by remember { mutableStateOf(data.pkg) }
|
||||||
val scopes = remember { mutableStateListOf(*data.scopes.toTypedArray()) }
|
val scopes = rememberSaveable { mutableStateListOf(*data.scopes.toTypedArray()) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
input = chosenPackage.receive()
|
input = chosenPackage.receive()
|
||||||
}
|
}
|
||||||
MySmallTitleScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, onNavigateUp, 0.dp) {
|
MySmallTitleScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, onNavigateUp, 0.dp) {
|
||||||
|
if (updateMode) {
|
||||||
|
OutlinedTextField(input, {}, Modifier.fillMaxWidth().padding(HorizontalPadding, 8.dp),
|
||||||
|
enabled = false, label = { Text(stringResource(R.string.package_name)) })
|
||||||
|
} else {
|
||||||
PackageNameTextField(input, onChoosePackage,
|
PackageNameTextField(input, onChoosePackage,
|
||||||
Modifier.padding(HorizontalPadding, 8.dp)) { input = it }
|
Modifier.padding(HorizontalPadding, 8.dp)) { input = it }
|
||||||
DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { scope ->
|
}
|
||||||
val checked = scope in scopes
|
delegatedScopesList.forEach { scope ->
|
||||||
|
val checked = scope.id in scopes
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { if (!checked) scopes += scope else scopes -= scope }
|
.clickable { if (!checked) scopes += scope.id else scopes -= scope.id }
|
||||||
.padding(vertical = 4.dp),
|
.padding(vertical = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Checkbox(checked, { if(it) scopes += scope else scopes -= scope }, modifier = Modifier.padding(horizontal = 4.dp))
|
Checkbox(checked, { if(it) scopes += scope.id else scopes -= scope.id },
|
||||||
|
modifier = Modifier.padding(horizontal = 4.dp))
|
||||||
Column {
|
Column {
|
||||||
Text(stringResource(scope.string))
|
Text(stringResource(scope.string))
|
||||||
Text(scope.id, style = typography.bodyMedium, color = colorScheme.onSurfaceVariant)
|
Text(scope.id, style = typography.bodyMedium, color = colorScheme.onSurfaceVariant)
|
||||||
@@ -760,63 +598,57 @@ fun AddDelegatedAdminScreen(
|
|||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
Privilege.DPM.setDelegatedScopes(Privilege.DAR, input, scopes.map { it.id })
|
setDelegatedAdmin(input, scopes)
|
||||||
onNavigateUp()
|
onNavigateUp()
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(HorizontalPadding, vertical = 4.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(HorizontalPadding, vertical = 4.dp),
|
|
||||||
enabled = input.isNotBlank() && (!updateMode || scopes.toList() != data.scopes)
|
enabled = input.isNotBlank() && (!updateMode || scopes.toList() != data.scopes)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(if(updateMode) R.string.update else R.string.add))
|
Text(stringResource(if(updateMode) R.string.update else R.string.add))
|
||||||
}
|
}
|
||||||
if(updateMode) Button(
|
if(updateMode) Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
Privilege.DPM.setDelegatedScopes(Privilege.DAR, input, emptyList())
|
setDelegatedAdmin(input, emptyList())
|
||||||
onNavigateUp()
|
onNavigateUp()
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(HorizontalPadding),
|
|
||||||
colors = ButtonDefaults.buttonColors(colorScheme.error, colorScheme.onError)
|
colors = ButtonDefaults.buttonColors(colorScheme.error, colorScheme.onError)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.delete))
|
Text(stringResource(R.string.delete))
|
||||||
}
|
}
|
||||||
|
Spacer(Modifier.height(40.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable object DeviceInfo
|
@Serializable object DeviceInfo
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeviceInfoScreen(onNavigateUp: () -> Unit) {
|
fun DeviceInfoScreen(vm: MyViewModel, onNavigateUp: () -> Unit) {
|
||||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||||
var dialog by remember { mutableIntStateOf(0) }
|
var dialog by remember { mutableIntStateOf(0) }
|
||||||
MyScaffold(R.string.device_info, onNavigateUp, 0.dp) {
|
MyScaffold(R.string.device_info, onNavigateUp, 0.dp) {
|
||||||
if(VERSION.SDK_INT>=34 && (privilege.device || privilege.org)) {
|
if (VERSION.SDK_INT >= 34 && (privilege.device || privilege.org)) {
|
||||||
InfoItem(R.string.financed_device, Privilege.DPM.isDeviceFinanced.yesOrNo)
|
InfoItem(R.string.financed_device, vm.getDeviceFinanced().yesOrNo)
|
||||||
}
|
}
|
||||||
if(VERSION.SDK_INT >= 33) {
|
if (VERSION.SDK_INT >= 33) {
|
||||||
val dpmRole = Privilege.DPM.devicePolicyManagementRoleHolderPackage
|
InfoItem(R.string.dpmrh, vm.getDpmRh() ?: stringResource(R.string.none))
|
||||||
InfoItem(R.string.dpmrh, dpmRole ?: stringResource(R.string.none))
|
|
||||||
}
|
}
|
||||||
val encryptionStatus = mutableMapOf(
|
val encryptionStatus = when (vm.getStorageEncryptionStatus()) {
|
||||||
DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE to R.string.es_inactive,
|
DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE -> R.string.es_inactive
|
||||||
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE to R.string.es_active,
|
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE -> R.string.es_active
|
||||||
DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED to R.string.es_unsupported
|
DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED -> R.string.es_unsupported
|
||||||
)
|
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY -> R.string.es_active_default_key
|
||||||
if(VERSION.SDK_INT >= 23) { encryptionStatus[DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY] = R.string.es_active_default_key }
|
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER -> R.string.es_active_per_user
|
||||||
if(VERSION.SDK_INT >= 24) { encryptionStatus[DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER] = R.string.es_active_per_user }
|
else -> R.string.unknown
|
||||||
InfoItem(R.string.encryption_status, encryptionStatus[Privilege.DPM.storageEncryptionStatus] ?: R.string.unknown)
|
}
|
||||||
if(VERSION.SDK_INT >= 28) {
|
InfoItem(R.string.encryption_status, encryptionStatus)
|
||||||
InfoItem(R.string.support_device_id_attestation, Privilege.DPM.isDeviceIdAttestationSupported.yesOrNo, true) { dialog = 1 }
|
if (VERSION.SDK_INT >= 28) {
|
||||||
|
InfoItem(R.string.support_device_id_attestation, vm.getDeviceIdAttestationSupported().yesOrNo, true) { dialog = 1 }
|
||||||
}
|
}
|
||||||
if (VERSION.SDK_INT >= 30) {
|
if (VERSION.SDK_INT >= 30) {
|
||||||
InfoItem(R.string.support_unique_device_attestation, Privilege.DPM.isUniqueDeviceAttestationSupported.yesOrNo, true) { dialog = 2 }
|
InfoItem(R.string.support_unique_device_attestation, vm.getUniqueDeviceAttestationSupported().yesOrNo, true) { dialog = 2 }
|
||||||
}
|
|
||||||
val adminList = Privilege.DPM.activeAdmins
|
|
||||||
if(adminList != null) {
|
|
||||||
InfoItem(R.string.activated_device_admin, adminList.joinToString("\n") { it.flattenToShortString() })
|
|
||||||
}
|
}
|
||||||
|
InfoItem(R.string.activated_device_admin, vm.getActiveAdmins())
|
||||||
}
|
}
|
||||||
if(dialog != 0) AlertDialog(
|
if(dialog != 0) AlertDialog(
|
||||||
text = { Text(stringResource(if(dialog == 1) R.string.info_device_id_attestation else R.string.info_unique_device_attestation)) },
|
text = { Text(stringResource(if(dialog == 1) R.string.info_device_id_attestation else R.string.info_unique_device_attestation)) },
|
||||||
@@ -829,15 +661,17 @@ fun DeviceInfoScreen(onNavigateUp: () -> Unit) {
|
|||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
@Composable
|
@Composable
|
||||||
fun SupportMessageScreen(onNavigateUp: () -> Unit) {
|
fun SupportMessageScreen(
|
||||||
|
getShortMessage: () -> String, getLongMessage: () -> String, setShortMessage: (String?) -> Unit,
|
||||||
|
setLongMessage: (String?) -> Unit, onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var shortMsg by remember { mutableStateOf("") }
|
var shortMsg by remember { mutableStateOf("") }
|
||||||
var longMsg by remember { mutableStateOf("") }
|
var longMsg by remember { mutableStateOf("") }
|
||||||
val refreshMsg = {
|
LaunchedEffect(Unit) {
|
||||||
shortMsg = Privilege.DPM.getShortSupportMessage(Privilege.DAR)?.toString() ?: ""
|
shortMsg = getShortMessage()
|
||||||
longMsg = Privilege.DPM.getLongSupportMessage(Privilege.DAR)?.toString() ?: ""
|
longMsg = getLongMessage()
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) { refreshMsg() }
|
|
||||||
MyScaffold(R.string.support_messages, onNavigateUp) {
|
MyScaffold(R.string.support_messages, onNavigateUp) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = shortMsg,
|
value = shortMsg,
|
||||||
@@ -851,8 +685,7 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) {
|
|||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
Privilege.DPM.setShortSupportMessage(Privilege.DAR, shortMsg)
|
setShortMessage(shortMsg)
|
||||||
refreshMsg()
|
|
||||||
context.showOperationResultToast(true)
|
context.showOperationResultToast(true)
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(0.49F)
|
modifier = Modifier.fillMaxWidth(0.49F)
|
||||||
@@ -861,8 +694,8 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) {
|
|||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
Privilege.DPM.setShortSupportMessage(Privilege.DAR, null)
|
setShortMessage(null)
|
||||||
refreshMsg()
|
shortMsg = ""
|
||||||
context.showOperationResultToast(true)
|
context.showOperationResultToast(true)
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(0.96F)
|
modifier = Modifier.fillMaxWidth(0.96F)
|
||||||
@@ -884,8 +717,7 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) {
|
|||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
Privilege.DPM.setLongSupportMessage(Privilege.DAR, longMsg)
|
setLongMessage(longMsg)
|
||||||
refreshMsg()
|
|
||||||
context.showOperationResultToast(true)
|
context.showOperationResultToast(true)
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(0.49F)
|
modifier = Modifier.fillMaxWidth(0.49F)
|
||||||
@@ -894,8 +726,8 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) {
|
|||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
Privilege.DPM.setLongSupportMessage(Privilege.DAR, null)
|
setLongMessage(null)
|
||||||
refreshMsg()
|
longMsg = ""
|
||||||
context.showOperationResultToast(true)
|
context.showOperationResultToast(true)
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(0.96F)
|
modifier = Modifier.fillMaxWidth(0.96F)
|
||||||
@@ -907,57 +739,60 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class DeviceAdmin(val app: AppInfo, val admin: ComponentName)
|
||||||
|
|
||||||
@Serializable object TransferOwnership
|
@Serializable object TransferOwnership
|
||||||
|
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
@Composable
|
@Composable
|
||||||
fun TransferOwnershipScreen(onNavigateUp: () -> Unit, onTransferred: () -> Unit) {
|
fun TransferOwnershipScreen(
|
||||||
val context = LocalContext.current
|
deviceAdmins: StateFlow<List<DeviceAdmin>>, getDeviceAdmins: () -> Unit,
|
||||||
|
transferOwnership: (ComponentName) -> Unit, onNavigateUp: () -> Unit, onTransferred: () -> Unit
|
||||||
|
) {
|
||||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||||
val focusMgr = LocalFocusManager.current
|
var selectedIndex by remember { mutableIntStateOf(-1) }
|
||||||
var input by remember { mutableStateOf("") }
|
|
||||||
val componentName = ComponentName.unflattenFromString(input)
|
|
||||||
var dialog by remember { mutableStateOf(false) }
|
var dialog by remember { mutableStateOf(false) }
|
||||||
MyScaffold(R.string.transfer_ownership, onNavigateUp) {
|
val receivers by deviceAdmins.collectAsStateWithLifecycle()
|
||||||
OutlinedTextField(
|
LaunchedEffect(Unit) { getDeviceAdmins() }
|
||||||
value = input, onValueChange = { input = it }, label = { Text(stringResource(R.string.target_component_name)) },
|
MyLazyScaffold(R.string.transfer_ownership, onNavigateUp) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
itemsIndexed(receivers) { index, admin ->
|
||||||
isError = input != "" && componentName == null,
|
Row(
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
Modifier.fillMaxWidth().clickable { selectedIndex = index }.padding(8.dp),
|
||||||
keyboardActions = KeyboardActions(onNext = { focusMgr.clearFocus() })
|
verticalAlignment = Alignment.CenterVertically
|
||||||
)
|
) {
|
||||||
Spacer(Modifier.padding(vertical = 5.dp))
|
RadioButton(selectedIndex == index, { selectedIndex = index })
|
||||||
|
Image(rememberDrawablePainter(admin.app.icon), null, Modifier.size(40.dp))
|
||||||
|
Column(Modifier.padding(start = 8.dp)) {
|
||||||
|
Text(admin.app.label)
|
||||||
|
Text(admin.app.name, Modifier.alpha(0.7F), style = typography.bodyMedium)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
Button(
|
Button(
|
||||||
onClick = { dialog = true },
|
onClick = { dialog = true },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().padding(HorizontalPadding, 10.dp),
|
||||||
enabled = componentName != null
|
enabled = receivers.getOrNull(selectedIndex) != null
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.transfer))
|
Text(stringResource(R.string.transfer))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.padding(vertical = 10.dp))
|
Notes(R.string.info_transfer_ownership, HorizontalPadding)
|
||||||
Notes(R.string.info_transfer_ownership)
|
|
||||||
}
|
}
|
||||||
if(dialog) AlertDialog(
|
}
|
||||||
|
if (dialog) AlertDialog(
|
||||||
text = {
|
text = {
|
||||||
Text(stringResource(
|
Text(stringResource(
|
||||||
R.string.transfer_ownership_warning,
|
R.string.transfer_ownership_warning,
|
||||||
stringResource(if(privilege.device) R.string.device_owner else R.string.profile_owner),
|
stringResource(if(privilege.device) R.string.device_owner else R.string.profile_owner),
|
||||||
ComponentName.unflattenFromString(input)!!.packageName
|
receivers[selectedIndex].app.name
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
transferOwnership(receivers[selectedIndex].admin)
|
||||||
Privilege.DPM.transferOwnership(Privilege.DAR, componentName!!, null)
|
|
||||||
Privilege.updateStatus()
|
|
||||||
context.showOperationResultToast(true)
|
|
||||||
dialog = false
|
dialog = false
|
||||||
onTransferred()
|
onTransferred()
|
||||||
} catch(e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
context.showOperationResultToast(false)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
|
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import android.app.admin.DevicePolicyManager.WIPE_EUICC
|
|||||||
import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE
|
import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE
|
||||||
import android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA
|
import android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA
|
||||||
import android.app.admin.DevicePolicyManager.WIPE_SILENTLY
|
import android.app.admin.DevicePolicyManager.WIPE_SILENTLY
|
||||||
import android.app.admin.SystemUpdateInfo
|
|
||||||
import android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC
|
import android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC
|
||||||
import android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_WINDOWED
|
import android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_WINDOWED
|
||||||
import android.app.admin.SystemUpdatePolicy.TYPE_POSTPONE
|
import android.app.admin.SystemUpdatePolicy.TYPE_POSTPONE
|
||||||
@@ -135,7 +134,6 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.util.Date
|
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ fun InfoItem(title: Int, text: String, withInfo: Boolean = false, onClick: () ->
|
|||||||
Modifier.fillMaxWidth().padding(vertical = 6.dp).padding(start = HorizontalPadding, end = 8.dp),
|
Modifier.fillMaxWidth().padding(vertical = 6.dp).padding(start = HorizontalPadding, end = 8.dp),
|
||||||
Arrangement.SpaceBetween, Alignment.CenterVertically
|
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(Modifier.weight(1F)) {
|
||||||
Text(stringResource(title), style = typography.titleLarge)
|
Text(stringResource(title), style = typography.titleLarge)
|
||||||
Text(text, Modifier.alpha(0.8F))
|
Text(text, Modifier.alpha(0.8F))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user