Files
OwnDroid/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt
BinTianqi 690ac876ab Encode security log data payload to json
Initialize Dhizuku with context
fix #85
2024-11-30 15:12:04 +08:00

692 lines
30 KiB
Kotlin

package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build.VERSION
import android.os.RemoteException
import android.os.UserManager
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.backToHomeStateFlow
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.writeClipBoard
import com.bintianqi.owndroid.yesOrNo
import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
import kotlinx.coroutines.launch
@Composable
fun DpmPermissions(navCtrl:NavHostController) {
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
val scrollState = rememberScrollState()
Scaffold(
topBar = {
TopBar(backStackEntry,navCtrl,localNavCtrl) {
if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue > 100) {
Text(
text = stringResource(R.string.permission),
modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80)
)
}
}
}
) {
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations.navHostEnterTransition,
exitTransition = Animations.navHostExitTransition,
popEnterTransition = Animations.navHostPopEnterTransition,
popExitTransition = Animations.navHostPopExitTransition,
modifier = Modifier.padding(top = it.calculateTopPadding())
) {
composable(route = "Home") { Home(localNavCtrl,scrollState) }
composable(route = "Shizuku") { ShizukuActivate() }
composable(route = "DeviceAdmin") { DeviceAdmin() }
composable(route = "ProfileOwner") { ProfileOwner() }
composable(route = "DeviceOwner") { DeviceOwner() }
composable(route = "DeviceInfo") { DeviceInfo() }
composable(route = "LockScreenInfo") { LockScreenInfo() }
composable(route = "SupportMsg") { SupportMsg() }
composable(route = "TransformOwnership") { TransferOwnership() }
}
}
}
@SuppressLint("NewApi")
@Composable
private fun Home(localNavCtrl:NavHostController,listScrollState:ScrollState) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val deviceAdmin = context.isDeviceAdmin
val deviceOwner = context.isDeviceOwner
val profileOwner = context.isProfileOwner
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
var dialog by remember { mutableIntStateOf(0) }
val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) dpm.enrollmentSpecificId else ""
Column(modifier = Modifier.fillMaxSize().verticalScroll(listScrollState)) {
Text(
text = stringResource(R.string.permission),
style = typography.headlineLarge,
modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp)
)
if(!dpm.isDeviceOwnerApp(context.packageName)) {
SwitchItem(
R.string.dhizuku, "", null,
{ sharedPref.getBoolean("dhizuku", false) },
{ toggleDhizukuMode(it, context) },
onClickBlank = { dialog = 4 }
)
}
SubPageItem(
R.string.device_admin, stringResource(if(deviceAdmin) R.string.activated else R.string.deactivated),
operation = { localNavCtrl.navigate("DeviceAdmin") }
)
if(profileOwner || !userManager.isSystemUser) {
SubPageItem(
R.string.profile_owner, stringResource(if(profileOwner) R.string.activated else R.string.deactivated),
operation = { localNavCtrl.navigate("ProfileOwner") }
)
}
if(!profileOwner && userManager.isSystemUser) {
SubPageItem(
R.string.device_owner, stringResource(if(deviceOwner) R.string.activated else R.string.deactivated),
operation = { localNavCtrl.navigate("DeviceOwner") }
)
}
SubPageItem(R.string.shizuku,"") { localNavCtrl.navigate("Shizuku") }
SubPageItem(R.string.device_info, "", R.drawable.perm_device_information_fill0) { localNavCtrl.navigate("DeviceInfo") }
if((VERSION.SDK_INT >= 26 && deviceOwner) || (VERSION.SDK_INT>=24 && profileOwner)) {
SubPageItem(R.string.org_name, "", R.drawable.corporate_fare_fill0) { dialog = 2 }
}
if(VERSION.SDK_INT >= 31 && (profileOwner || deviceOwner)) {
SubPageItem(R.string.org_id, "", R.drawable.corporate_fare_fill0) { dialog = 3 }
}
if(enrollmentSpecificId != "") {
SubPageItem(R.string.enrollment_specific_id, "", R.drawable.id_card_fill0) { dialog = 1 }
}
if(VERSION.SDK_INT >= 24 && (deviceOwner || dpm.isOrgProfile(receiver))) {
SubPageItem(R.string.device_owner_lock_screen_info, "", R.drawable.screen_lock_portrait_fill0) { localNavCtrl.navigate("LockScreenInfo") }
}
if(VERSION.SDK_INT >= 24 && deviceAdmin) {
SubPageItem(R.string.support_msg, "", R.drawable.chat_fill0) { localNavCtrl.navigate("SupportMsg") }
}
if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) {
SubPageItem(R.string.transfer_ownership, "", R.drawable.admin_panel_settings_fill0) { localNavCtrl.navigate("TransformOwnership") }
}
Spacer(Modifier.padding(vertical = 30.dp))
}
if(dialog != 0) {
var input by remember { mutableStateOf("") }
AlertDialog(
title = {
Text(stringResource(
when(dialog){
1 -> R.string.enrollment_specific_id
2 -> R.string.org_name
3 -> R.string.org_id
4 -> R.string.dhizuku
else -> R.string.permission
}
))
},
text = {
val focusMgr = LocalFocusManager.current
LaunchedEffect(Unit) {
if(dialog == 1) input = dpm.enrollmentSpecificId
}
Column {
if(dialog != 4) OutlinedTextField(
value = input,
onValueChange = { input = it }, readOnly = dialog == 1,
label = {
Text(stringResource(
when(dialog){
1 -> R.string.enrollment_specific_id
2 -> R.string.org_name
3 -> R.string.org_id
else -> R.string.permission
}
))
},
trailingIcon = {
if(dialog == 1) IconButton(onClick = { writeClipBoard(context, input) }) {
Icon(painter = painterResource(R.drawable.content_copy_fill0), contentDescription = stringResource(R.string.copy))
}
},
supportingText = {
if(dialog == 3) Text(stringResource(R.string.length_6_to_64))
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
textStyle = typography.bodyLarge,
modifier = Modifier.fillMaxWidth().padding(bottom = if(dialog == 2) 0.dp else 10.dp)
)
if(dialog == 1) Text(stringResource(R.string.info_enrollment_specific_id))
if(dialog == 3) Text(stringResource(R.string.info_org_id))
if(dialog == 4) Text(stringResource(R.string.info_dhizuku))
}
},
onDismissRequest = { dialog = 0 },
dismissButton = {
if(dialog != 4) TextButton(
onClick = { dialog = 0 }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
try {
if(dialog == 2) dpm.setOrganizationName(receiver, input)
if(dialog == 3) dpm.setOrganizationId(input)
dialog = 0
} catch(_: IllegalStateException) {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
},
enabled = (dialog == 3 && input.length in 6..64) || dialog != 3
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
private fun toggleDhizukuMode(status: Boolean, context: Context) {
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(!status) {
sharedPref.edit().putBoolean("dhizuku", false).apply()
backToHomeStateFlow.value = true
return
}
if(!Dhizuku.init(context)) {
dhizukuErrorStatus.value = 1
return
}
if(Dhizuku.isPermissionGranted()) {
sharedPref.edit().putBoolean("dhizuku", true).apply()
Dhizuku.init(context)
backToHomeStateFlow.value = true
} else {
Dhizuku.requestPermission(object: DhizukuRequestPermissionListener() {
@Throws(RemoteException::class)
override fun onRequestPermission(grantResult: Int) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
sharedPref.edit().putBoolean("dhizuku", true).apply()
Dhizuku.init(context)
context.toggleInstallAppActivity()
backToHomeStateFlow.value = true
} else {
dhizukuErrorStatus.value = 2
}
}
})
}
}
@SuppressLint("NewApi")
@Composable
private fun LockScreenInfo() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
var infoText by remember { mutableStateOf(dpm.deviceOwnerLockScreenInfo?.toString() ?: "") }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Text(text = stringResource(R.string.device_owner_lock_screen_info), style = typography.headlineLarge)
OutlinedTextField(
value = infoText,
label = { Text(stringResource(R.string.device_owner_lock_screen_info)) },
onValueChange = { infoText = it },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
)
Button(
onClick = {
focusMgr.clearFocus()
dpm.setDeviceOwnerLockScreenInfo(receiver,infoText)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.apply))
}
Button(
onClick = {
focusMgr.clearFocus()
dpm.setDeviceOwnerLockScreenInfo(receiver,null)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.reset))
}
Spacer(Modifier.padding(vertical = 10.dp))
InfoCard(R.string.info_lock_screen_info)
}
}
@Composable
private fun DeviceAdmin() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var deactivateDialog by remember { mutableStateOf(false) }
val deviceAdmin = context.isDeviceAdmin
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.device_admin), style = typography.headlineLarge)
Text(text = stringResource(if(context.isDeviceAdmin) R.string.activated else R.string.deactivated), style = typography.titleLarge)
Spacer(Modifier.padding(vertical = 5.dp))
AnimatedVisibility(deviceAdmin) {
Button(
onClick = { deactivateDialog = true },
enabled = !context.isProfileOwner && !context.isDeviceOwner,
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(stringResource(R.string.deactivate))
}
}
AnimatedVisibility(!deviceAdmin) {
Column {
Button(onClick = { activateDeviceAdmin(context, receiver) }, modifier = Modifier.fillMaxWidth()) {
Text(stringResource(R.string.activate_jump))
}
Spacer(Modifier.padding(vertical = 5.dp))
SelectionContainer {
Text(text = stringResource(R.string.activate_device_admin_command))
}
CopyTextButton(R.string.copy_command, stringResource(R.string.activate_device_admin_command))
}
}
}
if(deactivateDialog) {
AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
onDismissRequest = { deactivateDialog = false },
dismissButton = {
TextButton(
onClick = { deactivateDialog = false }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
dpm.removeActiveAdmin(receiver)
deactivateDialog = false
}
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
@Composable
private fun ProfileOwner() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var deactivateDialog by remember { mutableStateOf(false) }
val profileOwner = context.isProfileOwner
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.profile_owner), style = typography.headlineLarge)
Text(stringResource(if(profileOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge)
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 24 && profileOwner) {
Button(
onClick = { deactivateDialog = true },
enabled = !dpm.isManagedProfile(receiver),
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(stringResource(R.string.deactivate))
}
}
if(!profileOwner) {
val command = context.getString(R.string.activate_profile_owner_command, (Binder.getCallingUid() / 100000).toString())
SelectionContainer {
Text(command)
}
CopyTextButton(R.string.copy_command, command)
}
}
if(deactivateDialog && VERSION.SDK_INT >= 24) {
AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
onDismissRequest = { deactivateDialog = false },
dismissButton = {
TextButton(
onClick = { deactivateDialog = false }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
dpm.clearProfileOwner(receiver)
deactivateDialog = false
},
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
@Composable
private fun DeviceOwner() {
val context = LocalContext.current
val dpm = context.getDPM()
var deactivateDialog by remember { mutableStateOf(false) }
val deviceOwner = context.isDeviceOwner
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.device_owner), style = typography.headlineLarge)
Text(text = stringResource(if(deviceOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge)
Spacer(Modifier.padding(vertical = 5.dp))
AnimatedVisibility(deviceOwner) {
Button(
onClick = { deactivateDialog = true },
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(text = stringResource(R.string.deactivate))
}
}
AnimatedVisibility(!deviceOwner) {
Column {
SelectionContainer{
Text(text = stringResource(R.string.activate_device_owner_command))
}
CopyTextButton(R.string.copy_command, stringResource(R.string.activate_device_owner_command))
}
}
}
if(deactivateDialog) {
var resetPolicy by remember { mutableStateOf(false) }
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val coroutine = rememberCoroutineScope()
AlertDialog(
title = { Text(stringResource(R.string.deactivate)) },
text = {
Column {
if(sharedPref.getBoolean("dhizuku", false)) Text(stringResource(R.string.dhizuku_will_be_deactivated))
Spacer(Modifier.padding(vertical = 4.dp))
CheckBoxItem(text = R.string.reset_device_policy, checked = resetPolicy, operation = { resetPolicy = it })
}
},
onDismissRequest = { deactivateDialog = false },
dismissButton = {
TextButton(
onClick = { deactivateDialog = false }
) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
coroutine.launch {
if(resetPolicy) context.resetDevicePolicy()
dpm.clearDeviceOwnerApp(context.dpcPackageName)
if(sharedPref.getBoolean("dhizuku", false)) {
if (!Dhizuku.init(context)) {
sharedPref.edit().putBoolean("dhizuku", false).apply()
backToHomeStateFlow.value = true
}
}
deactivateDialog = false
}
}
) {
Text(stringResource(R.string.confirm))
}
}
)
}
}
@Composable
fun DeviceInfo() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var dialog by remember { mutableIntStateOf(0) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.device_info), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT>=34 && (context.isDeviceOwner || dpm.isOrgProfile(receiver))) {
CardItem(R.string.financed_device, dpm.isDeviceFinanced.yesOrNo)
}
if(VERSION.SDK_INT >= 33) {
val dpmRole = dpm.devicePolicyManagementRoleHolderPackage
CardItem(R.string.dpmrh, if(dpmRole == null) stringResource(R.string.none) else dpmRole)
}
val encryptionStatus = mutableMapOf(
DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE to R.string.es_inactive,
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE to R.string.es_active,
DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED to R.string.es_unsupported
)
if(VERSION.SDK_INT >= 23) { encryptionStatus[DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY] = R.string.es_active_default_key }
if(VERSION.SDK_INT >= 24) { encryptionStatus[DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER] = R.string.es_active_per_user }
CardItem(R.string.encryption_status, encryptionStatus[dpm.storageEncryptionStatus] ?: R.string.unknown)
if(VERSION.SDK_INT >= 28) {
CardItem(R.string.support_device_id_attestation, dpm.isDeviceIdAttestationSupported.yesOrNo) { dialog = 1 }
}
if (VERSION.SDK_INT >= 30) {
CardItem(R.string.support_unique_device_attestation, dpm.isUniqueDeviceAttestationSupported.yesOrNo) { dialog = 2 }
}
val adminList = dpm.activeAdmins
if(adminList != null) {
CardItem(R.string.activated_device_admin, adminList.map { it.flattenToShortString() }.joinToString("\n"))
}
}
if(dialog != 0) AlertDialog(
text = { Text(stringResource(if(dialog == 1) R.string.info_device_id_attestation else R.string.info_unique_device_attestation)) },
confirmButton = { TextButton(onClick = { dialog = 0 }) { Text(stringResource(R.string.confirm)) } },
onDismissRequest = { dialog = 0 }
)
}
@SuppressLint("NewApi")
@Composable
private fun SupportMsg() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var shortMsg by remember { mutableStateOf("") }
var longMsg by remember { mutableStateOf("") }
val refreshMsg = {
shortMsg = dpm.getShortSupportMessage(receiver)?.toString() ?: ""
longMsg = dpm.getLongSupportMessage(receiver)?.toString() ?: ""
}
LaunchedEffect(Unit) { refreshMsg() }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.support_msg), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = shortMsg,
label = { Text(stringResource(R.string.short_support_msg)) },
onValueChange = { shortMsg = it },
minLines = 2,
modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
dpm.setShortSupportMessage(receiver, shortMsg)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(text = stringResource(R.string.apply))
}
Button(
onClick = {
dpm.setShortSupportMessage(receiver, null)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(text = stringResource(R.string.reset))
}
}
InfoCard(R.string.info_short_support_message)
Spacer(Modifier.padding(vertical = 8.dp))
OutlinedTextField(
value = longMsg,
label = { Text(stringResource(R.string.long_support_msg)) },
onValueChange = { longMsg = it },
minLines = 3,
modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
dpm.setLongSupportMessage(receiver, longMsg)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(text = stringResource(R.string.apply))
}
Button(
onClick = {
dpm.setLongSupportMessage(receiver, null)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(text = stringResource(R.string.reset))
}
}
InfoCard(R.string.info_long_support_message)
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun TransferOwnership() {
val context = LocalContext.current
val focusMgr = LocalFocusManager.current
var input by remember { mutableStateOf("") }
val componentName = ComponentName.unflattenFromString(input)
var dialog by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.transfer_ownership), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = input, onValueChange = { input = it }, label = { Text(stringResource(R.string.target_component_name)) },
modifier = Modifier.fillMaxWidth(),
isError = input != "" && componentName == null,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = { focusMgr.clearFocus() })
)
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = { dialog = true },
modifier = Modifier.fillMaxWidth(),
enabled = componentName != null
) {
Text(stringResource(R.string.transfer))
}
Spacer(Modifier.padding(vertical = 10.dp))
InfoCard(R.string.info_transfer_ownership)
}
if(dialog) AlertDialog(
text = {
Text(stringResource(
R.string.transfer_ownership_warning,
stringResource(if(context.isDeviceOwner) R.string.device_owner else R.string.profile_owner),
ComponentName.unflattenFromString(input)!!.packageName
))
},
confirmButton = {
TextButton(
onClick = {
val dpm = context.getDPM()
val receiver = context.getReceiver()
try {
dpm.transferOwnership(receiver, componentName!!, null)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
dialog = false
backToHomeStateFlow.value = true
} catch(e: Exception) {
e.printStackTrace()
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
},
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton(onClick = { dialog = false }) {
Text(stringResource(R.string.cancel))
}
},
onDismissRequest = { dialog = false }
)
}
private fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName) {
try {
val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent)
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, inputContext.getString(R.string.activate_device_admin_here))
addDeviceAdmin.launch(intent)
} catch(_:ActivityNotFoundException) {
Toast.makeText(inputContext, R.string.unsupported, Toast.LENGTH_SHORT).show()
}
}