mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 11:05:59 +00:00
355 lines
16 KiB
Kotlin
355 lines
16 KiB
Kotlin
package com.bintianqi.owndroid.dpm
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE
|
|
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ALLOW_OFFLINE
|
|
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME
|
|
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
|
|
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION
|
|
import android.app.admin.DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
|
|
import android.app.admin.DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED
|
|
import android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED
|
|
import android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT
|
|
import android.app.admin.DevicePolicyManager.WIPE_EUICC
|
|
import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE
|
|
import android.app.admin.DevicePolicyManager.WIPE_SILENTLY
|
|
import android.content.*
|
|
import android.os.Binder
|
|
import android.os.Build.VERSION
|
|
import android.widget.Toast
|
|
import androidx.compose.animation.AnimatedVisibility
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Spacer
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.rememberScrollState
|
|
import androidx.compose.foundation.text.KeyboardActions
|
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
import androidx.compose.foundation.verticalScroll
|
|
import androidx.compose.material3.AlertDialog
|
|
import androidx.compose.material3.Button
|
|
import androidx.compose.material3.ButtonDefaults
|
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
|
import androidx.compose.material3.MaterialTheme.typography
|
|
import androidx.compose.material3.OutlinedTextField
|
|
import androidx.compose.material3.Scaffold
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.material3.TextButton
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.platform.LocalFocusManager
|
|
import androidx.compose.ui.res.stringResource
|
|
import androidx.compose.ui.text.input.ImeAction
|
|
import androidx.compose.ui.text.input.KeyboardType
|
|
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.ui.Animations
|
|
import com.bintianqi.owndroid.ui.CheckBoxItem
|
|
import com.bintianqi.owndroid.ui.CopyTextButton
|
|
import com.bintianqi.owndroid.ui.SubPageItem
|
|
import com.bintianqi.owndroid.ui.SwitchItem
|
|
import com.bintianqi.owndroid.ui.TopBar
|
|
|
|
@Composable
|
|
fun ManagedProfile(navCtrl: NavHostController) {
|
|
val localNavCtrl = rememberNavController()
|
|
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
|
|
Scaffold(
|
|
topBar = {
|
|
TopBar(backStackEntry, navCtrl, localNavCtrl)
|
|
}
|
|
) {
|
|
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) }
|
|
composable(route = "OrgOwnedWorkProfile") { OrgOwnedProfile() }
|
|
composable(route = "CreateWorkProfile") { CreateWorkProfile() }
|
|
composable(route = "SuspendPersonalApp") { SuspendPersonalApp() }
|
|
composable(route = "IntentFilter") { IntentFilter() }
|
|
composable(route = "DeleteWorkProfile") { DeleteWorkProfile() }
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun Home(navCtrl: NavHostController) {
|
|
val context = LocalContext.current
|
|
val dpm = context.getDPM()
|
|
val receiver = context.getReceiver()
|
|
Column(
|
|
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 30.dp, end = 12.dp)
|
|
) {
|
|
Text(
|
|
text = stringResource(R.string.work_profile),
|
|
style = typography.headlineLarge,
|
|
modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp)
|
|
)
|
|
if(VERSION.SDK_INT >= 30 && context.isProfileOwner && dpm.isManagedProfile(receiver)) {
|
|
SubPageItem(R.string.org_owned_work_profile, "", R.drawable.corporate_fare_fill0) { navCtrl.navigate("OrgOwnedWorkProfile") }
|
|
}
|
|
if(VERSION.SDK_INT<24 || (VERSION.SDK_INT>=24 && dpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))) {
|
|
SubPageItem(R.string.create_work_profile, "", R.drawable.work_fill0) { navCtrl.navigate("CreateWorkProfile") }
|
|
}
|
|
if(dpm.isOrgProfile(receiver)) {
|
|
SubPageItem(R.string.suspend_personal_app, "", R.drawable.block_fill0) { navCtrl.navigate("SuspendPersonalApp") }
|
|
}
|
|
if(context.isProfileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) {
|
|
SubPageItem(R.string.intent_filter, "", R.drawable.filter_alt_fill0) { navCtrl.navigate("IntentFilter") }
|
|
}
|
|
if(context.isProfileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) {
|
|
SubPageItem(R.string.delete_work_profile, "", R.drawable.delete_forever_fill0) { navCtrl.navigate("DeleteWorkProfile") }
|
|
}
|
|
Spacer(Modifier.padding(vertical = 30.dp))
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun CreateWorkProfile() {
|
|
val context = LocalContext.current
|
|
val receiver = context.getReceiver()
|
|
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
|
|
Spacer(Modifier.padding(vertical = 10.dp))
|
|
Text(text = stringResource(R.string.create_work_profile), style = typography.headlineLarge)
|
|
Spacer(Modifier.padding(vertical = 5.dp))
|
|
var skipEncrypt by remember { mutableStateOf(false) }
|
|
if(VERSION.SDK_INT>=24) {
|
|
CheckBoxItem(stringResource(R.string.skip_encryption), skipEncrypt, { skipEncrypt = it })
|
|
}
|
|
Spacer(Modifier.padding(vertical = 5.dp))
|
|
Button(
|
|
onClick = {
|
|
try {
|
|
val intent = Intent(ACTION_PROVISION_MANAGED_PROFILE)
|
|
if(VERSION.SDK_INT>=23) {
|
|
intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,receiver)
|
|
}else{
|
|
intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, context.packageName)
|
|
}
|
|
if(VERSION.SDK_INT>=24) { intent.putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION,skipEncrypt) }
|
|
if(VERSION.SDK_INT>=33) { intent.putExtra(EXTRA_PROVISIONING_ALLOW_OFFLINE,true) }
|
|
createManagedProfile.launch(intent)
|
|
}catch(e:ActivityNotFoundException) {
|
|
Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show()
|
|
}
|
|
},
|
|
modifier = Modifier.fillMaxWidth()
|
|
) {
|
|
Text(stringResource(R.string.create))
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressLint("NewApi")
|
|
@Composable
|
|
private fun OrgOwnedProfile() {
|
|
val context = LocalContext.current
|
|
val dpm = context.getDPM()
|
|
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
|
|
Spacer(Modifier.padding(vertical = 10.dp))
|
|
Text(text = stringResource(R.string.org_owned_work_profile), style = typography.headlineLarge)
|
|
Spacer(Modifier.padding(vertical = 5.dp))
|
|
Text(text = stringResource(R.string.is_org_owned_profile,dpm.isOrganizationOwnedDeviceWithManagedProfile))
|
|
Spacer(Modifier.padding(vertical = 5.dp))
|
|
if(!dpm.isOrganizationOwnedDeviceWithManagedProfile) {
|
|
SelectionContainer {
|
|
Text(
|
|
text = stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000),
|
|
color = colorScheme.onTertiaryContainer
|
|
)
|
|
}
|
|
CopyTextButton(R.string.copy_command, stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000))
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressLint("NewApi")
|
|
@Composable
|
|
private fun SuspendPersonalApp() {
|
|
val context = LocalContext.current
|
|
val dpm = context.getDPM()
|
|
val receiver = context.getReceiver()
|
|
val focusMgr = LocalFocusManager.current
|
|
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
|
|
Spacer(Modifier.padding(vertical = 10.dp))
|
|
SwitchItem(
|
|
R.string.suspend_personal_app, "", null,
|
|
{ dpm.getPersonalAppsSuspendedReasons(receiver)!=PERSONAL_APPS_NOT_SUSPENDED },
|
|
{ dpm.setPersonalAppsSuspended(receiver,it) }
|
|
)
|
|
var time by remember { mutableStateOf("") }
|
|
time = dpm.getManagedProfileMaximumTimeOff(receiver).toString()
|
|
Spacer(Modifier.padding(vertical = 10.dp))
|
|
Text(text = stringResource(R.string.profile_max_time_off), style = typography.titleLarge)
|
|
Text(text = stringResource(R.string.profile_max_time_out_desc))
|
|
Text(
|
|
text = stringResource(
|
|
R.string.personal_app_suspended_because_timeout,
|
|
dpm.getPersonalAppsSuspendedReasons(receiver) == PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT
|
|
)
|
|
)
|
|
OutlinedTextField(
|
|
value = time, onValueChange = { time=it }, modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
|
|
label = { Text(stringResource(R.string.time_unit_ms)) },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
|
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() })
|
|
)
|
|
Text(text = stringResource(R.string.cannot_less_than_72_hours))
|
|
Button(
|
|
onClick = {
|
|
dpm.setManagedProfileMaximumTimeOff(receiver,time.toLong())
|
|
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
|
},
|
|
modifier = Modifier.fillMaxWidth()
|
|
) {
|
|
Text(stringResource(R.string.apply))
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun IntentFilter() {
|
|
val context = LocalContext.current
|
|
val dpm = context.getDPM()
|
|
val receiver = context.getReceiver()
|
|
val focusMgr = LocalFocusManager.current
|
|
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
|
|
var action by remember { mutableStateOf("") }
|
|
Spacer(Modifier.padding(vertical = 10.dp))
|
|
Text(text = stringResource(R.string.intent_filter), style = typography.headlineLarge)
|
|
Spacer(Modifier.padding(vertical = 5.dp))
|
|
OutlinedTextField(
|
|
value = action, onValueChange = { action = it },
|
|
label = { Text("Action") },
|
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
|
|
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() }),
|
|
modifier = Modifier.fillMaxWidth()
|
|
)
|
|
Spacer(Modifier.padding(vertical = 5.dp))
|
|
Button(
|
|
onClick = {
|
|
dpm.addCrossProfileIntentFilter(receiver, IntentFilter(action), FLAG_PARENT_CAN_ACCESS_MANAGED)
|
|
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
|
},
|
|
modifier = Modifier.fillMaxWidth()
|
|
) {
|
|
Text(stringResource(R.string.add_intent_filter_work_to_personal))
|
|
}
|
|
Button(
|
|
onClick = {
|
|
dpm.addCrossProfileIntentFilter(receiver, IntentFilter(action), FLAG_MANAGED_CAN_ACCESS_PARENT)
|
|
Toast.makeText(context, R.string.success,Toast.LENGTH_SHORT).show()
|
|
},
|
|
modifier = Modifier.fillMaxWidth()
|
|
) {
|
|
Text(stringResource(R.string.add_intent_filter_personal_to_work))
|
|
}
|
|
Spacer(Modifier.padding(vertical = 2.dp))
|
|
Button(
|
|
onClick = {
|
|
dpm.clearCrossProfileIntentFilters(receiver)
|
|
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
|
},
|
|
modifier = Modifier.fillMaxWidth()
|
|
) {
|
|
Text(stringResource(R.string.clear_cross_profile_filters))
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun DeleteWorkProfile() {
|
|
val context = LocalContext.current
|
|
val dpm = context.getDPM()
|
|
val focusMgr = LocalFocusManager.current
|
|
var warning by remember { mutableStateOf(false) }
|
|
var externalStorage by remember { mutableStateOf(false) }
|
|
var euicc by remember { mutableStateOf(false) }
|
|
var silent by remember { mutableStateOf(false) }
|
|
var reason by remember { mutableStateOf("") }
|
|
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
|
|
Spacer(Modifier.padding(vertical = 10.dp))
|
|
Text(
|
|
text = stringResource(R.string.delete_work_profile),
|
|
style = typography.headlineLarge,
|
|
modifier = Modifier.padding(6.dp),color = colorScheme.error
|
|
)
|
|
Spacer(Modifier.padding(vertical = 5.dp))
|
|
CheckBoxItem(stringResource(R.string.wipe_external_storage), externalStorage, { externalStorage = it })
|
|
if(VERSION.SDK_INT >= 28) { CheckBoxItem(stringResource(R.string.wipe_euicc), euicc, { euicc = it }) }
|
|
if(VERSION.SDK_INT >= 29) { CheckBoxItem(stringResource(R.string.wipe_silently), silent, { silent = it }) }
|
|
AnimatedVisibility(!silent && VERSION.SDK_INT >= 28) {
|
|
OutlinedTextField(
|
|
value = reason, onValueChange = { reason = it },
|
|
label = { Text(stringResource(R.string.reason)) },
|
|
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
|
|
)
|
|
}
|
|
Spacer(Modifier.padding(vertical = 5.dp))
|
|
Button(
|
|
onClick = {
|
|
focusMgr.clearFocus()
|
|
warning = true
|
|
},
|
|
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
|
|
modifier = Modifier.fillMaxWidth()
|
|
) {
|
|
Text(stringResource(R.string.delete))
|
|
}
|
|
Spacer(Modifier.padding(vertical = 30.dp))
|
|
}
|
|
if(warning) {
|
|
LaunchedEffect(Unit) { silent = reason == "" }
|
|
AlertDialog(
|
|
title = {
|
|
Text(text = stringResource(R.string.warning), color = colorScheme.error)
|
|
},
|
|
text = {
|
|
Text(text = stringResource(R.string.wipe_work_profile_warning), color = colorScheme.error)
|
|
},
|
|
onDismissRequest = { warning = false },
|
|
confirmButton = {
|
|
TextButton(
|
|
onClick = {
|
|
var flag = 0
|
|
if(externalStorage) { flag += WIPE_EXTERNAL_STORAGE }
|
|
if(euicc && VERSION.SDK_INT >= 28) { flag += WIPE_EUICC }
|
|
if(silent && VERSION.SDK_INT >= 29) { flag += WIPE_SILENTLY }
|
|
if(VERSION.SDK_INT >= 28 && !silent) {
|
|
dpm.wipeData(flag, reason)
|
|
} else {
|
|
dpm.wipeData(flag)
|
|
}
|
|
},
|
|
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
|
|
) {
|
|
Text(stringResource(R.string.confirm))
|
|
}
|
|
},
|
|
dismissButton = {
|
|
TextButton(onClick = { warning = false }) {
|
|
Text(stringResource(R.string.cancel))
|
|
}
|
|
}
|
|
)
|
|
}
|
|
}
|