Optimize code

This commit is contained in:
BinTianqi
2024-12-29 14:03:28 +08:00
parent 4250d47683
commit 937afe9417
15 changed files with 513 additions and 750 deletions

View File

@@ -1,6 +1,5 @@
package com.bintianqi.owndroid
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
@@ -47,7 +46,6 @@ class InstallAppActivity: FragmentActivity() {
enableEdgeToEdge()
WindowCompat.setDecorFitsSystemWindows(window, false)
val context = applicationContext
val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE)
window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val uri = this.intent.data!!
var apkInfoText by mutableStateOf(context.getString(R.string.parsing_apk_info))
@@ -97,7 +95,9 @@ class InstallAppActivity: FragmentActivity() {
TextButton(
onClick = {
status = "installing"
uriToStream(applicationContext, this.intent.data) { stream -> installPackage(applicationContext, stream) }
intent.data?.let {
uriToStream(applicationContext, it) { stream -> installPackage(applicationContext, stream) }
}
},
enabled = status != "installing"
) {

View File

@@ -24,10 +24,10 @@ import android.util.Log
import android.widget.Toast
import androidx.core.app.NotificationCompat
import com.bintianqi.owndroid.dpm.handleNetworkLogs
import com.bintianqi.owndroid.dpm.handleSecurityLogs
import com.bintianqi.owndroid.dpm.isDeviceAdmin
import com.bintianqi.owndroid.dpm.isDeviceOwner
import com.bintianqi.owndroid.dpm.isProfileOwner
import com.bintianqi.owndroid.dpm.processSecurityLogs
import com.bintianqi.owndroid.dpm.toggleInstallAppActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -78,7 +78,13 @@ class Receiver : DeviceAdminReceiver() {
super.onSecurityLogsAvailable(context, intent)
if(VERSION.SDK_INT >= 24) {
CoroutineScope(Dispatchers.IO).launch {
handleSecurityLogs(context)
val events = getManager(context).retrieveSecurityLogs(ComponentName(context, this@Receiver::class.java)) ?: return@launch
val file = context.filesDir.resolve("SecurityLogs.json")
val fileExists = file.exists()
file.outputStream().use {
if(fileExists) it.write(",".encodeToByteArray())
processSecurityLogs(events, it)
}
}
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION
import android.widget.Toast
import androidx.biometric.BiometricManager
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.isSystemInDarkTheme
@@ -44,11 +43,11 @@ import java.security.SecureRandom
@Composable
fun Settings(navCtrl: NavHostController) {
MyScaffold(R.string.settings, 0.dp, navCtrl) {
FunctionItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("Options") }
FunctionItem(R.string.appearance, "", R.drawable.format_paint_fill0) { navCtrl.navigate("Appearance") }
FunctionItem(R.string.security, "", R.drawable.lock_fill0) { navCtrl.navigate("AuthSettings") }
FunctionItem(R.string.api, "", R.drawable.apps_fill0) { navCtrl.navigate("ApiSettings") }
FunctionItem(R.string.about, "", R.drawable.info_fill0) { navCtrl.navigate("About") }
FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { navCtrl.navigate("Options") }
FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { navCtrl.navigate("Appearance") }
FunctionItem(title = R.string.security, icon = R.drawable.lock_fill0) { navCtrl.navigate("AuthSettings") }
FunctionItem(title = R.string.api, icon = R.drawable.apps_fill0) { navCtrl.navigate("ApiSettings") }
FunctionItem(title = R.string.about, icon = R.drawable.info_fill0) { navCtrl.navigate("About") }
}
}
@@ -57,9 +56,9 @@ fun SettingsOptions(navCtrl: NavHostController) {
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
MyScaffold(R.string.options, 0.dp, navCtrl) {
SwitchItem(
R.string.show_dangerous_features, "", R.drawable.warning_fill0,
{ sharedPref.getBoolean("dangerous_features", false) },
{ sharedPref.edit().putBoolean("dangerous_features", it).apply() }
R.string.show_dangerous_features, icon = R.drawable.warning_fill0,
getState = { sharedPref.getBoolean("dangerous_features", false) },
onCheckedChange = { sharedPref.edit().putBoolean("dangerous_features", it).apply() }
)
}
}
@@ -75,11 +74,7 @@ fun Appearance(navCtrl: NavHostController, vm: MyViewModel) {
}
MyScaffold(R.string.appearance, 0.dp, navCtrl) {
if(VERSION.SDK_INT >= 31) {
SwitchItem(
R.string.material_you_color, "", null,
theme.materialYou,
{ vm.theme.value = theme.copy(materialYou = it) }
)
SwitchItem(R.string.material_you_color, state = theme.materialYou, onCheckedChange = { vm.theme.value = theme.copy(materialYou = it) })
}
Box {
FunctionItem(R.string.dark_theme, stringResource(darkThemeTextID)) { darkThemeMenu = true }
@@ -111,11 +106,7 @@ fun Appearance(navCtrl: NavHostController, vm: MyViewModel) {
}
}
AnimatedVisibility(theme.darkTheme == true || (theme.darkTheme == null && isSystemInDarkTheme())) {
SwitchItem(
R.string.black_theme, "", null,
theme.blackTheme,
{ vm.theme.value = theme.copy(blackTheme = it) }
)
SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { vm.theme.value = theme.copy(blackTheme = it) })
}
}
}
@@ -127,8 +118,8 @@ fun AuthSettings(navCtrl: NavHostController) {
var auth by remember{ mutableStateOf(sharedPref.getBoolean("auth",false)) }
MyScaffold(R.string.security, 0.dp, navCtrl) {
SwitchItem(
R.string.lock_owndroid, "", null, auth,
{
R.string.lock_owndroid, state = auth,
onCheckedChange = {
sharedPref.edit().putBoolean("auth", it).apply()
auth = sharedPref.getBoolean("auth", false)
}
@@ -143,13 +134,13 @@ fun AuthSettings(navCtrl: NavHostController) {
}
}
SwitchItem(
R.string.enable_bio_auth, "", null, bioAuth != 0,
{ bioAuth = if(it) 1 else 0; sharedPref.edit().putInt("biometrics_auth", bioAuth).apply() }, bioAuth != 2
R.string.enable_bio_auth, state = bioAuth != 0,
onCheckedChange = { bioAuth = if(it) 1 else 0; sharedPref.edit().putInt("biometrics_auth", bioAuth).apply() }, enabled = bioAuth != 2
)
SwitchItem(
R.string.lock_in_background, "", null,
{ sharedPref.getBoolean("lock_in_background", false) },
{ sharedPref.edit().putBoolean("lock_in_background", it).apply() }
R.string.lock_in_background,
getState = { sharedPref.getBoolean("lock_in_background", false) },
onCheckedChange = { sharedPref.edit().putBoolean("lock_in_background", it).apply() }
)
}
}
@@ -167,7 +158,7 @@ fun ApiSettings(navCtrl: NavHostController) {
if(!enabled) remove("api_key")
}
}
SwitchItem(R.string.enable, "", null, enabled, { enabled = it }, padding = false)
SwitchItem(R.string.enable, state = enabled, onCheckedChange = { enabled = it }, padding = false)
if(enabled) {
var key by remember { mutableStateOf("") }
OutlinedTextField(
@@ -189,7 +180,7 @@ fun ApiSettings(navCtrl: NavHostController) {
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
onClick = {
sharedPref.edit().putString("api_key", key).apply()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
}
) {
Text(stringResource(R.string.apply))

View File

@@ -5,18 +5,13 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import com.bintianqi.owndroid.dpm.addDeviceAdmin
import com.bintianqi.owndroid.dpm.createManagedProfile
import kotlinx.coroutines.flow.MutableStateFlow
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
@@ -27,29 +22,20 @@ import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
lateinit var getFile: ActivityResultLauncher<Intent>
val fileUriFlow = MutableStateFlow(Uri.parse(""))
var zhCN = true
fun uriToStream(
context: Context,
uri: Uri?,
uri: Uri,
operation: (stream: InputStream)->Unit
){
if(uri!=null){
try {
val stream = context.contentResolver.openInputStream(uri)
if(stream != null) { operation(stream) }
stream?.close()
}
catch(_: FileNotFoundException) { Toast.makeText(context, R.string.file_not_exist, Toast.LENGTH_SHORT).show() }
catch(_: IOException) { Toast.makeText(context, R.string.io_exception, Toast.LENGTH_SHORT).show() }
try {
val stream = context.contentResolver.openInputStream(uri)
if(stream != null) { operation(stream) }
stream?.close()
}
}
fun MutableList<Int>.toggle(status: Boolean, element: Int) {
if(status) add(element) else remove(element)
catch(_: FileNotFoundException) { Toast.makeText(context, R.string.file_not_exist, Toast.LENGTH_SHORT).show() }
catch(_: IOException) { Toast.makeText(context, R.string.io_exception, Toast.LENGTH_SHORT).show() }
}
fun writeClipBoard(context: Context, string: String):Boolean{
@@ -62,40 +48,13 @@ fun writeClipBoard(context: Context, string: String):Boolean{
return true
}
lateinit var exportFile: ActivityResultLauncher<Intent>
var exportFilePath: String? = null
var isExportingSecurityOrNetworkLogs = false
fun registerActivityResult(context: ComponentActivity){
getFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
activityResult.data.let {
if(it != null) fileUriFlow.value = it.data
}
}
createManagedProfile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {}
fun registerActivityResult(context: ComponentActivity) {
addDeviceAdmin = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val dpm = context.applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
if(dpm.isAdminActive(ComponentName(context.applicationContext, Receiver::class.java))) {
backToHomeStateFlow.value = true
}
}
exportFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val intentData = result.data ?: return@registerForActivityResult
val uriData = intentData.data ?: return@registerForActivityResult
val path = exportFilePath ?: return@registerForActivityResult
context.contentResolver.openOutputStream(uriData).use { outStream ->
if(outStream != null) {
if(isExportingSecurityOrNetworkLogs) outStream.write("[".encodeToByteArray())
File(path).inputStream().use { inStream ->
inStream.copyTo(outStream)
}
if(isExportingSecurityOrNetworkLogs) outStream.write("]".encodeToByteArray())
Toast.makeText(context.applicationContext, R.string.success, Toast.LENGTH_SHORT).show()
}
}
isExportingSecurityOrNetworkLogs = false
exportFilePath = null
}
}
fun formatFileSize(bytes: Long): String {

View File

@@ -18,6 +18,8 @@ import android.os.Build.VERSION
import android.os.Looper
import android.provider.Settings
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
@@ -50,7 +52,6 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
@@ -82,8 +83,7 @@ import com.bintianqi.owndroid.InstallAppActivity
import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.PackageInstallerReceiver
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.fileUriFlow
import com.bintianqi.owndroid.getFile
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
@@ -200,14 +200,14 @@ private fun Home(navCtrl:NavHostController, pkgName: String) {
if(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver)) {
Text(text = stringResource(R.string.scope_is_work_profile), textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth())
}
FunctionItem(R.string.app_info,"", R.drawable.open_in_new) {
FunctionItem(title = R.string.app_info, icon = R.drawable.open_in_new) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.setData(Uri.parse("package:$pkgName"))
startActivity(context, intent, null)
}
if(VERSION.SDK_INT >= 24) {
SwitchItem(
title = R.string.suspend, desc = "", icon = R.drawable.block_fill0,
title = R.string.suspend, icon = R.drawable.block_fill0,
state = suspend,
onCheckedChange = { appControlAction = 1; appControl(it) },
onClickBlank = { appControlAction = 1; dialogStatus = 4 }
@@ -220,48 +220,47 @@ private fun Home(navCtrl:NavHostController, pkgName: String) {
onClickBlank = { appControlAction = 2; dialogStatus = 4 }
)
SwitchItem(
title = R.string.block_uninstall, desc = "", icon = R.drawable.delete_forever_fill0,
title = R.string.block_uninstall, icon = R.drawable.delete_forever_fill0,
state = blockUninstall,
onCheckedChange = { appControlAction = 3; appControl(it) },
onClickBlank = { appControlAction = 3; dialogStatus = 4 }
)
if((VERSION.SDK_INT >= 33 && profileOwner) || (VERSION.SDK_INT >= 30 && deviceOwner)) {
FunctionItem(R.string.ucd, "", R.drawable.do_not_touch_fill0) { navCtrl.navigate("UserControlDisabled") }
FunctionItem(title = R.string.ucd, icon = R.drawable.do_not_touch_fill0) { navCtrl.navigate("UserControlDisabled") }
}
if(VERSION.SDK_INT>=23) {
FunctionItem(R.string.permission_manage, "", R.drawable.key_fill0) { navCtrl.navigate("PermissionManage") }
FunctionItem(title = R.string.permission_manage, icon = R.drawable.key_fill0) { navCtrl.navigate("PermissionManage") }
}
if(VERSION.SDK_INT >= 30 && profileOwner && dpm.isManagedProfile(receiver)) {
FunctionItem(R.string.cross_profile_package, "", R.drawable.work_fill0) { navCtrl.navigate("CrossProfilePackage") }
FunctionItem(title = R.string.cross_profile_package, icon = R.drawable.work_fill0) { navCtrl.navigate("CrossProfilePackage") }
}
if(profileOwner) {
FunctionItem(R.string.cross_profile_widget, "", R.drawable.widgets_fill0) { navCtrl.navigate("CrossProfileWidget") }
FunctionItem(title = R.string.cross_profile_widget, icon = R.drawable.widgets_fill0) { navCtrl.navigate("CrossProfileWidget") }
}
if(VERSION.SDK_INT >= 34 && deviceOwner) {
FunctionItem(R.string.credential_manage_policy, "", R.drawable.license_fill0) { navCtrl.navigate("CredentialManagePolicy") }
FunctionItem(title = R.string.credential_manage_policy, icon = R.drawable.license_fill0) { navCtrl.navigate("CredentialManagePolicy") }
}
FunctionItem(R.string.permitted_accessibility_services, "", R.drawable.settings_accessibility_fill0) { navCtrl.navigate("Accessibility") }
FunctionItem(R.string.permitted_ime, "", R.drawable.keyboard_fill0) { navCtrl.navigate("IME") }
FunctionItem(R.string.enable_system_app, "", R.drawable.enable_fill0) {
FunctionItem(title = R.string.permitted_accessibility_services, icon = R.drawable.settings_accessibility_fill0) { navCtrl.navigate("Accessibility") }
FunctionItem(title = R.string.permitted_ime, icon = R.drawable.keyboard_fill0) { navCtrl.navigate("IME") }
FunctionItem(title = R.string.enable_system_app, icon = R.drawable.enable_fill0) {
if(pkgName != "") dialogStatus = 1
}
if(VERSION.SDK_INT >= 28 && deviceOwner) {
FunctionItem(R.string.keep_uninstalled_packages, "", R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") }
FunctionItem(title = R.string.keep_uninstalled_packages, icon = R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") }
}
if(VERSION.SDK_INT >= 28) {
FunctionItem(R.string.clear_app_storage, "", R.drawable.mop_fill0) {
FunctionItem(title = R.string.clear_app_storage, icon = R.drawable.mop_fill0) {
if(pkgName != "") dialogStatus = 2
}
}
FunctionItem(R.string.install_app, "", R.drawable.install_mobile_fill0) { navCtrl.navigate("InstallApp") }
FunctionItem(R.string.uninstall_app, "", R.drawable.delete_fill0) { navCtrl.navigate("UninstallApp") }
FunctionItem(title = R.string.install_app, icon = R.drawable.install_mobile_fill0) { navCtrl.navigate("InstallApp") }
FunctionItem(title = R.string.uninstall_app, icon = R.drawable.delete_fill0) { navCtrl.navigate("UninstallApp") }
if(VERSION.SDK_INT >= 34 && (deviceOwner || dpm.isOrgProfile(receiver))) {
FunctionItem(R.string.set_default_dialer, "", R.drawable.call_fill0) {
FunctionItem(title = R.string.set_default_dialer, icon = R.drawable.call_fill0) {
if(pkgName != "") dialogStatus = 3
}
}
Spacer(Modifier.padding(vertical = 30.dp))
LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") }
}
if(dialogStatus == 1) AlertDialog(
title = { Text(stringResource(R.string.enable_system_app)) },
@@ -279,7 +278,7 @@ private fun Home(navCtrl:NavHostController, pkgName: String) {
onClick = {
try {
dpm.enableSystemApp(receiver, pkgName)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
} catch(_: IllegalArgumentException) {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
@@ -343,7 +342,7 @@ private fun Home(navCtrl:NavHostController, pkgName: String) {
onClick = {
try{
dpm.setDefaultDialerApplication(pkgName)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
} catch(_: IllegalArgumentException) {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
@@ -656,25 +655,13 @@ private fun CredentialManagePolicy(pkgName: String) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.credential_manage_policy), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
RadioButtonItem(
R.string.none,
policyType == -1, { policyType = -1 }
)
RadioButtonItem(
R.string.blacklist,
policyType == PACKAGE_POLICY_BLOCKLIST,
{ policyType = PACKAGE_POLICY_BLOCKLIST }
)
RadioButtonItem(
R.string.whitelist,
policyType == PACKAGE_POLICY_ALLOWLIST,
{ policyType = PACKAGE_POLICY_ALLOWLIST }
)
RadioButtonItem(R.string.none, policyType == -1) { policyType = -1 }
RadioButtonItem(R.string.blacklist, policyType == PACKAGE_POLICY_BLOCKLIST) { policyType = PACKAGE_POLICY_BLOCKLIST }
RadioButtonItem(R.string.whitelist, policyType == PACKAGE_POLICY_ALLOWLIST){ policyType = PACKAGE_POLICY_ALLOWLIST }
RadioButtonItem(
R.string.whitelist_and_system_app,
policyType == PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM,
{ policyType = PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM }
)
policyType == PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM
) { policyType = PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM }
Spacer(Modifier.padding(vertical = 5.dp))
AnimatedVisibility(policyType != -1) {
Column {
@@ -699,7 +686,7 @@ private fun CredentialManagePolicy(pkgName: String) {
} else {
dpm.credentialManagerPolicy = null
}
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
} catch(_: IllegalArgumentException) {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
} finally {
@@ -798,8 +785,8 @@ private fun PermittedIME(pkgName: String) {
Text(text = stringResource(R.string.permitted_ime), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
SwitchItem(
R.string.allow_all, "", null, allowAll,
{
R.string.allow_all, state = allowAll,
onCheckedChange = {
dpm.setPermittedInputMethods(receiver, if(it) null else listOf())
refresh()
}, padding = false
@@ -918,8 +905,11 @@ private fun UninstallApp(pkgName: String) {
private fun InstallApp() {
val context = LocalContext.current
val focusMgr = LocalFocusManager.current
val selected = fileUriFlow.collectAsState().value != Uri.parse("")
val sharedPrefs = context.getSharedPreferences("data", Context.MODE_PRIVATE)
var apkFileUri by remember { mutableStateOf<Uri?>(null) }
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result.data.also { if(it != null) apkFileUri = it.data }
}
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.install_app), style = typography.headlineLarge)
@@ -930,19 +920,19 @@ private fun InstallApp() {
val installApkIntent = Intent(Intent.ACTION_GET_CONTENT)
installApkIntent.setType("application/vnd.android.package-archive")
installApkIntent.addCategory(Intent.CATEGORY_OPENABLE)
getFile.launch(installApkIntent)
getFileLauncher.launch(installApkIntent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.select_apk))
}
AnimatedVisibility(selected) {
AnimatedVisibility(apkFileUri != null) {
Spacer(Modifier.padding(vertical = 3.dp))
Column(modifier = Modifier.fillMaxWidth()) {
Button(
onClick = {
val intent = Intent(context, InstallAppActivity::class.java)
intent.data = fileUriFlow.value
intent.data = apkFileUri
context.startActivity(intent)
},
enabled = !sharedPrefs.getBoolean("dhizuku", false) && context.isDeviceOwner,
@@ -953,7 +943,7 @@ private fun InstallApp() {
Button(
onClick = {
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
intent.setData(fileUriFlow.value)
intent.setData(apkFileUri)
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(intent)
},

View File

@@ -42,8 +42,8 @@ import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
lateinit var createManagedProfile: ActivityResultLauncher<Intent>
lateinit var addDeviceAdmin: ActivityResultLauncher<Intent>
val Context.isDeviceOwner: Boolean
@@ -356,15 +356,10 @@ fun handleNetworkLogs(context: Context, batchToken: Long) {
}
@RequiresApi(24)
fun handleSecurityLogs(context: Context) {
val file = context.filesDir.resolve("SecurityLogs.json")
fun processSecurityLogs(securityEvents: List<SecurityLog.SecurityEvent>, outputStream: OutputStream) {
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
val securityEvents = context.getDPM().retrieveSecurityLogs(context.getReceiver())
securityEvents ?: return
val fileExist = file.exists()
val buffer = file.bufferedWriter()
val buffer = outputStream.bufferedWriter()
securityEvents.forEachIndexed { index, event ->
if(fileExist && index == 0) buffer.write(",")
val item = buildJsonObject {
put("time_nanos", event.timeNanos)
put("tag", event.tag)

View File

@@ -19,6 +19,8 @@ import android.content.*
import android.os.Binder
import android.os.Build.VERSION
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -38,6 +40,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -52,6 +55,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CardItem
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.CopyTextButton
@@ -69,19 +73,19 @@ fun WorkProfile(navCtrl: NavHostController) {
val profileOwner = context.isProfileOwner
MyScaffold(R.string.work_profile, 0.dp, navCtrl) {
if(VERSION.SDK_INT >= 30 && profileOwner && dpm.isManagedProfile(receiver)) {
FunctionItem(R.string.org_owned_work_profile, "", R.drawable.corporate_fare_fill0) { navCtrl.navigate("OrgOwnedWorkProfile") }
FunctionItem(R.string.org_owned_work_profile, icon = R.drawable.corporate_fare_fill0) { navCtrl.navigate("OrgOwnedWorkProfile") }
}
if(VERSION.SDK_INT<24 || (VERSION.SDK_INT>=24 && dpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))) {
FunctionItem(R.string.create_work_profile, "", R.drawable.work_fill0) { navCtrl.navigate("CreateWorkProfile") }
FunctionItem(R.string.create_work_profile, icon = R.drawable.work_fill0) { navCtrl.navigate("CreateWorkProfile") }
}
if(dpm.isOrgProfile(receiver)) {
FunctionItem(R.string.suspend_personal_app, "", R.drawable.block_fill0) { navCtrl.navigate("SuspendPersonalApp") }
FunctionItem(R.string.suspend_personal_app, icon = R.drawable.block_fill0) { navCtrl.navigate("SuspendPersonalApp") }
}
if(profileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) {
FunctionItem(R.string.intent_filter, "", R.drawable.filter_alt_fill0) { navCtrl.navigate("IntentFilter") }
FunctionItem(R.string.intent_filter, icon = R.drawable.filter_alt_fill0) { navCtrl.navigate("IntentFilter") }
}
if(profileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) {
FunctionItem(R.string.delete_work_profile, "", R.drawable.delete_forever_fill0) { navCtrl.navigate("DeleteWorkProfile") }
FunctionItem(R.string.delete_work_profile, icon = R.drawable.delete_forever_fill0) { navCtrl.navigate("DeleteWorkProfile") }
}
}
}
@@ -91,6 +95,7 @@ fun CreateWorkProfile(navCtrl: NavHostController) {
val context = LocalContext.current
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { }
MyScaffold(R.string.create_work_profile, 8.dp, navCtrl) {
var skipEncrypt by remember { mutableStateOf(false) }
var offlineProvisioning by remember { mutableStateOf(true) }
@@ -99,7 +104,7 @@ fun CreateWorkProfile(navCtrl: NavHostController) {
var migrateAccountType by remember { mutableStateOf("") }
var keepAccount by remember { mutableStateOf(true) }
if(VERSION.SDK_INT >= 22) {
CheckBoxItem(R.string.migrate_account, migrateAccount, { migrateAccount = it })
CheckBoxItem(R.string.migrate_account, migrateAccount) { migrateAccount = it }
AnimatedVisibility(migrateAccount) {
val fr = FocusRequester()
Column(modifier = Modifier.padding(start = 10.dp)) {
@@ -118,23 +123,19 @@ fun CreateWorkProfile(navCtrl: NavHostController) {
modifier = Modifier.fillMaxWidth().focusRequester(fr)
)
if(VERSION.SDK_INT >= 26) {
CheckBoxItem(R.string.keep_account, keepAccount, { keepAccount = it })
CheckBoxItem(R.string.keep_account, keepAccount) { keepAccount = it }
}
}
}
}
if(VERSION.SDK_INT >= 24) {
CheckBoxItem(R.string.skip_encryption, skipEncrypt, { skipEncrypt = it })
}
if(VERSION.SDK_INT >= 33) {
CheckBoxItem(R.string.offline_provisioning, offlineProvisioning, { offlineProvisioning = it })
}
if(VERSION.SDK_INT >= 24) CheckBoxItem(R.string.skip_encryption, skipEncrypt) { skipEncrypt = it }
if(VERSION.SDK_INT >= 33) CheckBoxItem(R.string.offline_provisioning, offlineProvisioning) { offlineProvisioning = it }
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
try {
val intent = Intent(ACTION_PROVISION_MANAGED_PROFILE)
if(VERSION.SDK_INT>=23) {
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)
@@ -147,8 +148,8 @@ fun CreateWorkProfile(navCtrl: NavHostController) {
}
if(VERSION.SDK_INT >= 24) { intent.putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION, skipEncrypt) }
if(VERSION.SDK_INT >= 33) { intent.putExtra(EXTRA_PROVISIONING_ALLOW_OFFLINE, offlineProvisioning) }
createManagedProfile.launch(intent)
} catch(_:ActivityNotFoundException) {
launcher.launch(intent)
} catch(_: ActivityNotFoundException) {
Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show()
}
},
@@ -188,10 +189,8 @@ fun SuspendPersonalApp(navCtrl: NavHostController) {
val focusMgr = LocalFocusManager.current
var suspend by remember { mutableStateOf(dpm.getPersonalAppsSuspendedReasons(receiver) != PERSONAL_APPS_NOT_SUSPENDED) }
MyScaffold(R.string.suspend_personal_app, 8.dp, navCtrl) {
SwitchItem(
R.string.suspend_personal_app, "", null,
suspend,
{
SwitchItem(R.string.suspend_personal_app, state = suspend,
onCheckedChange = {
dpm.setPersonalAppsSuspended(receiver,it)
suspend = dpm.getPersonalAppsSuspendedReasons(receiver) != PERSONAL_APPS_NOT_SUSPENDED
}, padding = false
@@ -217,7 +216,7 @@ fun SuspendPersonalApp(navCtrl: NavHostController) {
Button(
onClick = {
dpm.setManagedProfileMaximumTimeOff(receiver,time.toLong())
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -246,7 +245,7 @@ fun IntentFilter(navCtrl: NavHostController) {
Button(
onClick = {
dpm.addCrossProfileIntentFilter(receiver, IntentFilter(action), FLAG_PARENT_CAN_ACCESS_MANAGED)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -255,7 +254,7 @@ fun IntentFilter(navCtrl: NavHostController) {
Button(
onClick = {
dpm.addCrossProfileIntentFilter(receiver, IntentFilter(action), FLAG_MANAGED_CAN_ACCESS_PARENT)
Toast.makeText(context, R.string.success,Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -265,7 +264,7 @@ fun IntentFilter(navCtrl: NavHostController) {
Button(
onClick = {
dpm.clearCrossProfileIntentFilters(receiver)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -280,15 +279,14 @@ fun DeleteWorkProfile(navCtrl: NavHostController) {
val context = LocalContext.current
val dpm = context.getDPM()
val focusMgr = LocalFocusManager.current
var flag by remember { mutableIntStateOf(0) }
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("") }
MyScaffold(R.string.delete_work_profile, 8.dp, navCtrl) {
CheckBoxItem(R.string.wipe_external_storage, externalStorage, { externalStorage = it })
if(VERSION.SDK_INT >= 28) { CheckBoxItem(R.string.wipe_euicc, euicc, { euicc = it }) }
CheckBoxItem(R.string.wipe_silently, silent, { silent = it })
CheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { flag = flag xor WIPE_EXTERNAL_STORAGE }
if(VERSION.SDK_INT >= 28) CheckBoxItem(R.string.wipe_euicc, flag and WIPE_EUICC != 0) { flag = flag xor WIPE_EUICC }
CheckBoxItem(R.string.wipe_silently, silent) { silent = it }
AnimatedVisibility(!silent && VERSION.SDK_INT >= 28) {
OutlinedTextField(
value = reason, onValueChange = { reason = it },
@@ -322,9 +320,6 @@ fun DeleteWorkProfile(navCtrl: NavHostController) {
confirmButton = {
TextButton(
onClick = {
var flag = 0
if(externalStorage) { flag += WIPE_EXTERNAL_STORAGE }
if(euicc && VERSION.SDK_INT >= 28) { flag += WIPE_EUICC }
if(VERSION.SDK_INT >= 28 && !silent) {
dpm.wipeData(flag, reason)
} else {

View File

@@ -124,10 +124,7 @@ import androidx.core.os.bundleOf
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.exportFile
import com.bintianqi.owndroid.exportFilePath
import com.bintianqi.owndroid.formatFileSize
import com.bintianqi.owndroid.isExportingSecurityOrNetworkLogs
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.FunctionItem
@@ -157,30 +154,30 @@ fun Network(navCtrl:NavHostController) {
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
val dhizuku = sharedPref.getBoolean("dhizuku", false)
MyScaffold(R.string.network, 0.dp, navCtrl) {
if(!dhizuku) FunctionItem(R.string.wifi, "", R.drawable.wifi_fill0) { navCtrl.navigate("Wifi") }
if(!dhizuku) FunctionItem(R.string.wifi, icon = R.drawable.wifi_fill0) { navCtrl.navigate("Wifi") }
if(VERSION.SDK_INT >= 30) {
FunctionItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("NetworkOptions") }
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { navCtrl.navigate("NetworkOptions") }
}
if(VERSION.SDK_INT >= 29 && deviceOwner) {
FunctionItem(R.string.private_dns, "", R.drawable.dns_fill0) { navCtrl.navigate("PrivateDNS") }
FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { navCtrl.navigate("PrivateDNS") }
}
if(VERSION.SDK_INT >= 24) {
FunctionItem(R.string.always_on_vpn, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("AlwaysOnVpn") }
FunctionItem(R.string.always_on_vpn, icon = R.drawable.vpn_key_fill0) { navCtrl.navigate("AlwaysOnVpn") }
}
if(deviceOwner) {
FunctionItem(R.string.recommended_global_proxy, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("RecommendedGlobalProxy") }
FunctionItem(R.string.recommended_global_proxy, icon = R.drawable.vpn_key_fill0) { navCtrl.navigate("RecommendedGlobalProxy") }
}
if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) {
FunctionItem(R.string.network_logging, "", R.drawable.description_fill0) { navCtrl.navigate("NetworkLog") }
FunctionItem(R.string.network_logging, icon = R.drawable.description_fill0) { navCtrl.navigate("NetworkLog") }
}
if(VERSION.SDK_INT >= 31) {
FunctionItem(R.string.wifi_auth_keypair, "", R.drawable.key_fill0) { navCtrl.navigate("WifiAuthKeypair") }
FunctionItem(R.string.wifi_auth_keypair, icon = R.drawable.key_fill0) { navCtrl.navigate("WifiAuthKeypair") }
}
if(VERSION.SDK_INT >= 33) {
FunctionItem(R.string.preferential_network_service, "", R.drawable.globe_fill0) { navCtrl.navigate("PreferentialNetworkService") }
FunctionItem(R.string.preferential_network_service, icon = R.drawable.globe_fill0) { navCtrl.navigate("PreferentialNetworkService") }
}
if(VERSION.SDK_INT >= 28 && deviceOwner) {
FunctionItem(R.string.override_apn_settings, "", R.drawable.cell_tower_fill0) { navCtrl.navigate("OverrideAPN") }
FunctionItem(R.string.override_apn_settings, icon = R.drawable.cell_tower_fill0) { navCtrl.navigate("OverrideAPN") }
}
}
}
@@ -194,8 +191,8 @@ fun NetworkOptions(navCtrl: NavHostController) {
var dialog by remember { mutableIntStateOf(0) }
MyScaffold(R.string.options, 0.dp, navCtrl) {
if(VERSION.SDK_INT>=30 && (deviceOwner || dpm.isOrgProfile(receiver))) {
SwitchItem(R.string.lockdown_admin_configured_network, "", R.drawable.wifi_password_fill0,
{ dpm.hasLockdownAdminConfiguredNetworks(receiver) }, { dpm.setConfiguredNetworksLockdownState(receiver,it) },
SwitchItem(R.string.lockdown_admin_configured_network, icon = R.drawable.wifi_password_fill0,
getState = { dpm.hasLockdownAdminConfiguredNetworks(receiver) }, onCheckedChange = { dpm.setConfiguredNetworksLockdownState(receiver,it) },
onClickBlank = { dialog = 1 }
)
}
@@ -280,11 +277,11 @@ fun Wifi(navCtrl: NavHostController) {
}
}
if(VERSION.SDK_INT >= 24 && (deviceOwner || orgProfileOwner)) {
FunctionItem(R.string.wifi_mac_address, "", null) { wifiMacDialog = true }
FunctionItem(R.string.wifi_mac_address) { wifiMacDialog = true }
}
if(VERSION.SDK_INT >= 33 && (deviceOwner || orgProfileOwner)) {
FunctionItem(R.string.min_wifi_security_level, "", null) { navCtrl.navigate("MinWifiSecurityLevel") }
FunctionItem(R.string.wifi_ssid_policy, "", null) { navCtrl.navigate("WifiSsidPolicy") }
FunctionItem(R.string.min_wifi_security_level) { navCtrl.navigate("MinWifiSecurityLevel") }
FunctionItem(R.string.wifi_ssid_policy) { navCtrl.navigate("WifiSsidPolicy") }
}
}
} else if(page == 1) {
@@ -397,8 +394,7 @@ private fun SavedNetworks(navCtrl: NavHostController) {
) {
Button(
onClick = {
val success = wm.enableNetwork(network.networkId, false)
Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(wm.enableNetwork(network.networkId, false))
networkDetailsDialog = -1
refresh()
},
@@ -408,8 +404,7 @@ private fun SavedNetworks(navCtrl: NavHostController) {
}
Button(
onClick = {
val success = wm.disableNetwork(network.networkId)
Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(wm.disableNetwork(network.networkId))
networkDetailsDialog = -1
refresh()
},
@@ -431,8 +426,7 @@ private fun SavedNetworks(navCtrl: NavHostController) {
}
TextButton(
onClick = {
val success = wm.removeNetwork(network.networkId)
Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(wm.removeNetwork(network.networkId))
networkDetailsDialog = -1
refresh()
},
@@ -526,7 +520,7 @@ private fun AddNetwork(wifiConfig: WifiConfiguration? = null, navCtrl: NavHostCo
value = ssid, onValueChange = { ssid = it }, label = { Text("SSID") },
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
)
CheckBoxItem(R.string.hidden_ssid, hiddenSsid, { hiddenSsid = it })
CheckBoxItem(R.string.hidden_ssid, hiddenSsid) { hiddenSsid = it }
if(VERSION.SDK_INT >= 30) {
// TODO: more protocols
val securityTypeTextMap = mutableMapOf(
@@ -731,31 +725,15 @@ fun WifiSecurityLevel(navCtrl: NavHostController) {
var selectedWifiSecLevel by remember { mutableIntStateOf(0) }
LaunchedEffect(Unit) { selectedWifiSecLevel = dpm.minimumRequiredWifiSecurityLevel }
MyScaffold(R.string.min_wifi_security_level, 8.dp, navCtrl) {
RadioButtonItem(
R.string.wifi_security_open,
selectedWifiSecLevel == WIFI_SECURITY_OPEN,
{ selectedWifiSecLevel = WIFI_SECURITY_OPEN }
)
RadioButtonItem(
"WEP, WPA(2)-PSK",
selectedWifiSecLevel == WIFI_SECURITY_PERSONAL,
{ selectedWifiSecLevel = WIFI_SECURITY_PERSONAL }
)
RadioButtonItem(
"WPA-EAP",
selectedWifiSecLevel == WIFI_SECURITY_ENTERPRISE_EAP,
{ selectedWifiSecLevel = WIFI_SECURITY_ENTERPRISE_EAP }
)
RadioButtonItem(
"WPA3-192bit",
selectedWifiSecLevel == WIFI_SECURITY_ENTERPRISE_192,
{ selectedWifiSecLevel = WIFI_SECURITY_ENTERPRISE_192 }
)
RadioButtonItem(R.string.wifi_security_open, selectedWifiSecLevel == WIFI_SECURITY_OPEN) { selectedWifiSecLevel = WIFI_SECURITY_OPEN }
RadioButtonItem("WEP, WPA(2)-PSK", selectedWifiSecLevel == WIFI_SECURITY_PERSONAL) { selectedWifiSecLevel = WIFI_SECURITY_PERSONAL }
RadioButtonItem("WPA-EAP", selectedWifiSecLevel == WIFI_SECURITY_ENTERPRISE_EAP) { selectedWifiSecLevel = WIFI_SECURITY_ENTERPRISE_EAP }
RadioButtonItem("WPA3-192bit", selectedWifiSecLevel == WIFI_SECURITY_ENTERPRISE_192) { selectedWifiSecLevel = WIFI_SECURITY_ENTERPRISE_192 }
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
dpm.minimumRequiredWifiSecurityLevel = selectedWifiSecLevel
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -781,21 +759,13 @@ fun WifiSsidPolicy(navCtrl: NavHostController) {
ssidList.addAll(policy?.ssids ?: mutableSetOf())
}
LaunchedEffect(Unit) { refreshPolicy() }
RadioButtonItem(
R.string.none,
selectedPolicyType == -1,
{ selectedPolicyType = -1 }
)
RadioButtonItem(
R.string.whitelist,
selectedPolicyType == WIFI_SSID_POLICY_TYPE_ALLOWLIST,
{ selectedPolicyType = WIFI_SSID_POLICY_TYPE_ALLOWLIST }
)
RadioButtonItem(
R.string.blacklist,
selectedPolicyType == WIFI_SSID_POLICY_TYPE_DENYLIST,
{ selectedPolicyType = WIFI_SSID_POLICY_TYPE_DENYLIST }
)
RadioButtonItem(R.string.none, selectedPolicyType == -1) { selectedPolicyType = -1 }
RadioButtonItem(R.string.whitelist, selectedPolicyType == WIFI_SSID_POLICY_TYPE_ALLOWLIST) {
selectedPolicyType = WIFI_SSID_POLICY_TYPE_ALLOWLIST
}
RadioButtonItem(R.string.blacklist, selectedPolicyType == WIFI_SSID_POLICY_TYPE_DENYLIST) {
selectedPolicyType = WIFI_SSID_POLICY_TYPE_DENYLIST
}
AnimatedVisibility(selectedPolicyType != -1) {
var inputSsid by remember { mutableStateOf("") }
Column {
@@ -838,7 +808,7 @@ fun WifiSsidPolicy(navCtrl: NavHostController) {
WifiSsidPolicy(selectedPolicyType, ssidList.toSet())
}
refreshPolicy()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -940,7 +910,7 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean ->
try {
dpm.setAlwaysOnVpnPackage(receiver, vpnPkg, lockdownEnabled)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
true
} catch(e: UnsupportedOperationException) {
e.printStackTrace()
@@ -971,7 +941,7 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
},
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
SwitchItem(R.string.enable_lockdown, "", null, lockdown, { lockdown = it }, padding = false)
SwitchItem(R.string.enable_lockdown, state = lockdown, onCheckedChange = { lockdown = it }, padding = false)
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = { if(setAlwaysOnVpn(pkgName, lockdown)) refresh() },
@@ -1002,9 +972,9 @@ fun RecommendedGlobalProxy(navCtrl: NavHostController) {
var proxyPort by remember { mutableStateOf("") }
var exclList by remember { mutableStateOf("") }
MyScaffold(R.string.recommended_global_proxy, 8.dp, navCtrl) {
RadioButtonItem(R.string.proxy_type_off, proxyType == 0, { proxyType = 0 })
RadioButtonItem(R.string.proxy_type_pac, proxyType == 1, { proxyType = 1 })
RadioButtonItem(R.string.proxy_type_direct, proxyType == 2, { proxyType = 2 })
RadioButtonItem(R.string.proxy_type_off, proxyType == 0) { proxyType = 0 }
RadioButtonItem(R.string.proxy_type_pac, proxyType == 1) { proxyType = 1 }
RadioButtonItem(R.string.proxy_type_direct, proxyType == 2) { proxyType = 2 }
AnimatedVisibility(proxyType != 0) {
OutlinedTextField(
value = proxyUri,
@@ -1017,7 +987,7 @@ fun RecommendedGlobalProxy(navCtrl: NavHostController) {
}
AnimatedVisibility(proxyType == 1 && VERSION.SDK_INT >= 30) {
Box(modifier = Modifier.padding(top = 2.dp)) {
CheckBoxItem(R.string.specify_port, specifyPort, { specifyPort = it })
CheckBoxItem(R.string.specify_port, specifyPort) { specifyPort = it }
}
}
AnimatedVisibility((proxyType == 1 && specifyPort && VERSION.SDK_INT >= 30) || proxyType == 2) {
@@ -1045,7 +1015,7 @@ fun RecommendedGlobalProxy(navCtrl: NavHostController) {
onClick = {
if(proxyType == 0) {
dpm.setRecommendedGlobalProxy(receiver, null)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
return@Button
}
if(proxyUri == "") {
@@ -1076,7 +1046,7 @@ fun RecommendedGlobalProxy(navCtrl: NavHostController) {
return@Button
}
dpm.setRecommendedGlobalProxy(receiver, proxyInfo)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -1094,11 +1064,24 @@ fun NetworkLogging(navCtrl: NavHostController) {
val receiver = context.getReceiver()
val logFile = context.filesDir.resolve("NetworkLogs.json")
var fileSize by remember { mutableLongStateOf(0) }
LaunchedEffect(Unit) {
fileSize = logFile.length()
LaunchedEffect(Unit) { fileSize = logFile.length() }
val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result.data?.data?.let { uri ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".encodeToByteArray())
logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".encodeToByteArray())
context.showOperationResultToast(true)
}
}
}
MyScaffold(R.string.network_logging, 8.dp, navCtrl) {
SwitchItem(R.string.enable, "", null, { dpm.isNetworkLoggingEnabled(receiver) }, { dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false)
SwitchItem(
R.string.enable,
getState = { dpm.isNetworkLoggingEnabled(receiver) },
onCheckedChange = { dpm.setNetworkLoggingEnabled(receiver,it) },
padding = false
)
Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button(
@@ -1107,9 +1090,7 @@ fun NetworkLogging(navCtrl: NavHostController) {
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json")
exportFilePath = logFile.path
isExportingSecurityOrNetworkLogs = true
exportFile.launch(intent)
exportNetworkLogsLauncher.launch(intent)
},
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)
@@ -1158,19 +1139,13 @@ fun WifiAuthKeypair(navCtrl: NavHostController) {
Spacer(Modifier.padding(vertical = 5.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
val result = dpm.grantKeyPairToWifiAuth(keyPair)
Toast.makeText(context, if(result) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
},
onClick = { context.showOperationResultToast(dpm.grantKeyPairToWifiAuth(keyPair)) },
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.grant))
}
Button(
onClick = {
val result = dpm.revokeKeyPairFromWifiAuth(keyPair)
Toast.makeText(context, if(result) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
},
onClick = { context.showOperationResultToast(dpm.revokeKeyPairFromWifiAuth(keyPair)) },
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.revoke))
@@ -1221,10 +1196,7 @@ fun PreferentialNetworkService(navCtrl: NavHostController) {
}
LaunchedEffect(Unit) { initialize() }
MyScaffold(R.string.preferential_network_service, 8.dp, navCtrl) {
SwitchItem(
title = R.string.enabled, desc = "", icon = null,
state = masterEnabled, onCheckedChange = { masterEnabled = it }, padding = false
)
SwitchItem(R.string.enabled, state = masterEnabled, onCheckedChange = { masterEnabled = it }, padding = false)
Row(
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
@@ -1272,7 +1244,7 @@ fun PreferentialNetworkService(navCtrl: NavHostController) {
onClick = {
try {
saveCurrentConfig()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
} catch(e: Exception) {
e.printStackTrace()
Toast.makeText(context, R.string.failed_to_save_current_config, Toast.LENGTH_SHORT).show()
@@ -1292,10 +1264,7 @@ fun PreferentialNetworkService(navCtrl: NavHostController) {
Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(R.string.delete_current_config))
}
}
SwitchItem(
title = R.string.enabled, desc = "", icon = null,
state = enabled, onCheckedChange = { enabled = it }, padding = false
)
SwitchItem(title = R.string.enabled, state = enabled, onCheckedChange = { enabled = it }, padding = false)
OutlinedTextField(
value = networkId, onValueChange = { networkId = it },
label = { Text(stringResource(R.string.network_id)) },
@@ -1304,11 +1273,11 @@ fun PreferentialNetworkService(navCtrl: NavHostController) {
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp)
)
SwitchItem(
title = R.string.allow_fallback_to_default_connection, desc = "", icon = null,
title = R.string.allow_fallback_to_default_connection,
state = allowFallback, onCheckedChange = { allowFallback = it }, padding = false
)
if(VERSION.SDK_INT >= 34) SwitchItem(
title = R.string.block_non_matching_networks, desc = "", icon = null,
title = R.string.block_non_matching_networks,
state = blockNonMatching, onCheckedChange = { blockNonMatching = it }, padding = false
)
OutlinedTextField(
@@ -1328,7 +1297,7 @@ fun PreferentialNetworkService(navCtrl: NavHostController) {
dpm.isPreferentialNetworkServiceEnabled = masterEnabled
dpm.preferentialNetworkServiceConfigs = configs
initialize()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth().padding(top = 12.dp)
) {
@@ -1351,7 +1320,11 @@ fun OverrideAPN(navCtrl: NavHostController) {
MyScaffold(R.string.override_apn_settings, 8.dp, navCtrl) {
Text(text = stringResource(id = R.string.developing))
Spacer(Modifier.padding(vertical = 5.dp))
SwitchItem(R.string.enable, "", null, { dpm.isOverrideApnEnabled(receiver) }, { dpm.setOverrideApnsEnabled(receiver,it) }, padding = false)
SwitchItem(
R.string.enable,
getState = { dpm.isOverrideApnEnabled(receiver) }, onCheckedChange = { dpm.setOverrideApnsEnabled(receiver,it) },
padding = false
)
Text(text = stringResource(R.string.total_apn_amount, setting.size))
if(setting.isNotEmpty()) {
Text(text = stringResource(R.string.select_a_apn_or_create, setting.size))
@@ -1468,11 +1441,11 @@ fun OverrideAPN(navCtrl: NavHostController) {
}
Text(text = stringResource(R.string.auth_type), style = typography.titleLarge)
RadioButtonItem(R.string.none, selectedAuthType==AUTH_TYPE_NONE , { selectedAuthType=AUTH_TYPE_NONE })
RadioButtonItem("CHAP", selectedAuthType == AUTH_TYPE_CHAP , { selectedAuthType = AUTH_TYPE_CHAP })
RadioButtonItem("PAP", selectedAuthType == AUTH_TYPE_PAP, { selectedAuthType = AUTH_TYPE_PAP })
RadioButtonItem("PAP/CHAP", selectedAuthType == AUTH_TYPE_PAP_OR_CHAP, { selectedAuthType = AUTH_TYPE_PAP_OR_CHAP })
RadioButtonItem(R.string.none, selectedAuthType==AUTH_TYPE_NONE) { selectedAuthType = AUTH_TYPE_NONE }
RadioButtonItem("CHAP", selectedAuthType == AUTH_TYPE_CHAP) { selectedAuthType = AUTH_TYPE_CHAP }
RadioButtonItem("PAP", selectedAuthType == AUTH_TYPE_PAP) { selectedAuthType = AUTH_TYPE_PAP }
RadioButtonItem("PAP/CHAP", selectedAuthType == AUTH_TYPE_PAP_OR_CHAP) { selectedAuthType = AUTH_TYPE_PAP_OR_CHAP }
if(VERSION.SDK_INT>=29) {
val ts = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
carrierId = ts.simCarrierId.toString()
@@ -1578,11 +1551,11 @@ fun OverrideAPN(navCtrl: NavHostController) {
}
Text(text = "MVNO", style = typography.titleLarge)
RadioButtonItem("SPN", mvnoType == MVNO_TYPE_SPN, { mvnoType = MVNO_TYPE_SPN })
RadioButtonItem("IMSI", mvnoType == MVNO_TYPE_IMSI, { mvnoType = MVNO_TYPE_IMSI })
RadioButtonItem("GID", mvnoType == MVNO_TYPE_GID, { mvnoType = MVNO_TYPE_GID })
RadioButtonItem("ICCID", mvnoType == MVNO_TYPE_ICCID, { mvnoType = MVNO_TYPE_ICCID })
RadioButtonItem("SPN", mvnoType == MVNO_TYPE_SPN) { mvnoType = MVNO_TYPE_SPN }
RadioButtonItem("IMSI", mvnoType == MVNO_TYPE_IMSI) { mvnoType = MVNO_TYPE_IMSI }
RadioButtonItem("GID", mvnoType == MVNO_TYPE_GID) { mvnoType = MVNO_TYPE_GID }
RadioButtonItem("ICCID", mvnoType == MVNO_TYPE_ICCID) { mvnoType = MVNO_TYPE_ICCID }
Text(text = stringResource(R.string.network_type), style = typography.titleLarge)
TextField(
value = networkTypeBitmask,
@@ -1625,23 +1598,23 @@ fun OverrideAPN(navCtrl: NavHostController) {
}
Text(text = stringResource(R.string.protocol), style = typography.titleLarge)
RadioButtonItem("IPV4", protocol == PROTOCOL_IP, { protocol = PROTOCOL_IP })
RadioButtonItem("IPV6", protocol == PROTOCOL_IPV6, { protocol = PROTOCOL_IPV6 })
RadioButtonItem("IPV4/IPV6", protocol == PROTOCOL_IPV4V6, { protocol = PROTOCOL_IPV4V6 })
RadioButtonItem("PPP", protocol == PROTOCOL_PPP, { protocol = PROTOCOL_PPP })
RadioButtonItem("IPV4", protocol == PROTOCOL_IP) { protocol = PROTOCOL_IP }
RadioButtonItem("IPV6", protocol == PROTOCOL_IPV6) { protocol = PROTOCOL_IPV6 }
RadioButtonItem("IPV4/IPV6", protocol == PROTOCOL_IPV4V6) { protocol = PROTOCOL_IPV4V6 }
RadioButtonItem("PPP", protocol == PROTOCOL_PPP) { protocol = PROTOCOL_PPP }
if(VERSION.SDK_INT>=29) {
RadioButtonItem("non-IP", protocol == PROTOCOL_NON_IP, { protocol = PROTOCOL_NON_IP })
RadioButtonItem("Unstructured", protocol == PROTOCOL_UNSTRUCTURED, { protocol = PROTOCOL_UNSTRUCTURED })
RadioButtonItem("non-IP", protocol == PROTOCOL_NON_IP) { protocol = PROTOCOL_NON_IP }
RadioButtonItem("Unstructured", protocol == PROTOCOL_UNSTRUCTURED) { protocol = PROTOCOL_UNSTRUCTURED }
}
Text(text = stringResource(R.string.roaming_protocol), style = typography.titleLarge)
RadioButtonItem("IPV4", roamingProtocol == PROTOCOL_IP, { roamingProtocol = PROTOCOL_IP })
RadioButtonItem("IPV6", roamingProtocol == PROTOCOL_IPV6, { roamingProtocol = PROTOCOL_IPV6 })
RadioButtonItem("IPV4/IPV6", roamingProtocol == PROTOCOL_IPV4V6, { roamingProtocol = PROTOCOL_IPV4V6 })
RadioButtonItem("PPP", roamingProtocol == PROTOCOL_PPP, { roamingProtocol = PROTOCOL_PPP})
RadioButtonItem("IPV4", roamingProtocol == PROTOCOL_IP) { roamingProtocol = PROTOCOL_IP }
RadioButtonItem("IPV6", roamingProtocol == PROTOCOL_IPV6) { roamingProtocol = PROTOCOL_IPV6 }
RadioButtonItem("IPV4/IPV6", roamingProtocol == PROTOCOL_IPV4V6) { roamingProtocol = PROTOCOL_IPV4V6 }
RadioButtonItem("PPP", roamingProtocol == PROTOCOL_PPP) { roamingProtocol = PROTOCOL_PPP }
if(VERSION.SDK_INT>=29) {
RadioButtonItem("non-IP", roamingProtocol == PROTOCOL_NON_IP, { roamingProtocol = PROTOCOL_NON_IP })
RadioButtonItem("Unstructured", roamingProtocol == PROTOCOL_UNSTRUCTURED, { roamingProtocol = PROTOCOL_UNSTRUCTURED })
RadioButtonItem("non-IP", roamingProtocol == PROTOCOL_NON_IP) { roamingProtocol = PROTOCOL_NON_IP }
RadioButtonItem("Unstructured", roamingProtocol == PROTOCOL_UNSTRUCTURED) { roamingProtocol = PROTOCOL_UNSTRUCTURED }
}
var finalStep by remember { mutableStateOf(false) }
@@ -1688,20 +1661,14 @@ fun OverrideAPN(navCtrl: NavHostController) {
}else{
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
val success = dpm.updateOverrideApn(receiver,id,result)
Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
},
Modifier.fillMaxWidth(0.49F)
onClick = { context.showOperationResultToast(dpm.updateOverrideApn(receiver, id, result)) },
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.update))
}
Button(
onClick = {
val success = dpm.removeOverrideApn(receiver,id)
Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
},
Modifier.fillMaxWidth(0.96F)
onClick = { context.showOperationResultToast(dpm.removeOverrideApn(receiver,id)) },
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}

View File

@@ -9,7 +9,6 @@ import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_IRIS
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
@@ -55,7 +54,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -70,7 +68,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.toggle
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CardItem
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.FunctionItem
@@ -89,34 +87,34 @@ fun Password(navCtrl: NavHostController) {
val profileOwner = context.isProfileOwner
var dialog by remember { mutableIntStateOf(0) }
MyScaffold(R.string.password_and_keyguard, 0.dp, navCtrl) {
FunctionItem(R.string.password_info, "", R.drawable.info_fill0) { navCtrl.navigate("PasswordInfo") }
FunctionItem(R.string.password_info, icon = R.drawable.info_fill0) { navCtrl.navigate("PasswordInfo") }
if(sharedPrefs.getBoolean("dangerous_features", false)) {
if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.reset_password_token, "", R.drawable.key_vertical_fill0) { navCtrl.navigate("ResetPasswordToken") }
FunctionItem(R.string.reset_password_token, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("ResetPasswordToken") }
}
if(deviceAdmin || deviceOwner || profileOwner) {
FunctionItem(R.string.reset_password, "", R.drawable.lock_reset_fill0) { navCtrl.navigate("ResetPassword") }
FunctionItem(R.string.reset_password, icon = R.drawable.lock_reset_fill0) { navCtrl.navigate("ResetPassword") }
}
}
if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.required_password_complexity, "", R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordComplexity") }
FunctionItem(R.string.required_password_complexity, icon = R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordComplexity") }
}
if(deviceAdmin) {
FunctionItem(R.string.disable_keyguard_features, "", R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("DisableKeyguardFeatures") }
FunctionItem(R.string.disable_keyguard_features, icon = R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("DisableKeyguardFeatures") }
}
if(deviceOwner) {
FunctionItem(R.string.max_time_to_lock, "", R.drawable.schedule_fill0) { dialog = 1 }
FunctionItem(R.string.pwd_expiration_timeout, "", R.drawable.lock_clock_fill0) { dialog = 3 }
FunctionItem(R.string.max_pwd_fail, "", R.drawable.no_encryption_fill0) { dialog = 4 }
FunctionItem(R.string.max_time_to_lock, icon = R.drawable.schedule_fill0) { dialog = 1 }
FunctionItem(R.string.pwd_expiration_timeout, icon = R.drawable.lock_clock_fill0) { dialog = 3 }
FunctionItem(R.string.max_pwd_fail, icon = R.drawable.no_encryption_fill0) { dialog = 4 }
}
if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.required_strong_auth_timeout, "", R.drawable.fingerprint_off_fill0) { dialog = 2 }
FunctionItem(R.string.required_strong_auth_timeout, icon = R.drawable.fingerprint_off_fill0) { dialog = 2 }
}
if(deviceAdmin){
FunctionItem(R.string.pwd_history, "", R.drawable.history_fill0) { dialog = 5 }
FunctionItem(R.string.pwd_history, icon = R.drawable.history_fill0) { dialog = 5 }
}
if(VERSION.SDK_INT < 31 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.required_password_quality, "", R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordQuality") }
FunctionItem(R.string.required_password_quality, icon = R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordQuality") }
}
}
if(dialog != 0) {
@@ -259,12 +257,8 @@ fun ResetPasswordToken(navCtrl: NavHostController) {
Button(
onClick = {
try {
Toast.makeText(
context,
if(dpm.setResetPasswordToken(receiver, tokenByteArray)) R.string.success else R.string.failed,
Toast.LENGTH_SHORT
).show()
}catch(_:SecurityException) {
context.showOperationResultToast(dpm.setResetPasswordToken(receiver, tokenByteArray))
} catch(_:SecurityException) {
Toast.makeText(context, R.string.security_exception, Toast.LENGTH_SHORT).show()
}
},
@@ -289,13 +283,7 @@ fun ResetPasswordToken(navCtrl: NavHostController) {
Text(stringResource(R.string.activate))
}
Button(
onClick = {
Toast.makeText(
context,
if(dpm.clearResetPasswordToken(receiver)) R.string.success else R.string.failed,
Toast.LENGTH_SHORT
).show()
},
onClick = { context.showOperationResultToast(dpm.clearResetPasswordToken(receiver)) },
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.clear))
@@ -316,7 +304,7 @@ fun ResetPassword(navCtrl: NavHostController) {
var useToken by remember { mutableStateOf(false) }
var token by remember { mutableStateOf("") }
val tokenByteArray = token.toByteArray()
val flags = remember { mutableStateListOf<Int>() }
var flag by remember { mutableIntStateOf(0) }
var confirmDialog by remember { mutableStateOf(false) }
MyScaffold(R.string.reset_password, 8.dp, navCtrl) {
if(VERSION.SDK_INT >= 26) {
@@ -342,15 +330,13 @@ fun ResetPassword(navCtrl: NavHostController) {
if(VERSION.SDK_INT >= 23) {
CheckBoxItem(
R.string.do_not_ask_credentials_on_boot,
RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT in flags,
{ flags.toggle(it, RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) }
)
flag and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0
) { flag = flag xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT }
}
CheckBoxItem(
R.string.reset_password_require_entry,
RESET_PASSWORD_REQUIRE_ENTRY in flags,
{ flags.toggle(it, RESET_PASSWORD_REQUIRE_ENTRY) }
)
flag and RESET_PASSWORD_REQUIRE_ENTRY != 0
) { flag = flag xor RESET_PASSWORD_REQUIRE_ENTRY }
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 26) {
Button(
@@ -402,14 +388,12 @@ fun ResetPassword(navCtrl: NavHostController) {
confirmButton = {
TextButton(
onClick = {
var resetFlag = 0
flags.forEach { resetFlag += it }
val success = if(VERSION.SDK_INT >= 26 && useToken) {
dpm.resetPasswordWithToken(receiver, password, tokenByteArray, resetFlag)
dpm.resetPasswordWithToken(receiver, password, tokenByteArray, flag)
} else {
dpm.resetPassword(password, resetFlag)
dpm.resetPassword(password, flag)
}
Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(success)
password = ""
confirmDialog = false
},
@@ -443,13 +427,13 @@ fun PasswordComplexity(navCtrl: NavHostController) {
LaunchedEffect(Unit) { selectedItem = dpm.requiredPasswordComplexity }
MyScaffold(R.string.required_password_complexity, 8.dp, navCtrl) {
passwordComplexity.forEach {
RadioButtonItem(it.value, selectedItem == it.key, { selectedItem = it.key })
RadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key }
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
dpm.requiredPasswordComplexity = selectedItem
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -470,86 +454,52 @@ fun DisableKeyguardFeatures(navCtrl: NavHostController) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var state by remember { mutableIntStateOf(-1) }
var shortcuts by remember { mutableStateOf(false) }
var biometrics by remember { mutableStateOf(false) }
var iris by remember { mutableStateOf(false) }
var face by remember { mutableStateOf(false) }
var remote by remember { mutableStateOf(false) }
var fingerprint by remember { mutableStateOf(false) }
var agents by remember { mutableStateOf(false) }
var unredacted by remember { mutableStateOf(false) }
var notification by remember { mutableStateOf(false) }
var camera by remember { mutableStateOf(false) }
var widgets by remember { mutableStateOf(false) }
val calculateCustomFeature = {
var calculate = dpm.getKeyguardDisabledFeatures(receiver)
if(calculate==0) {state=0}
else{
if(calculate-KEYGUARD_DISABLE_SHORTCUTS_ALL >= 0 && VERSION.SDK_INT >= 34) { shortcuts=true; calculate-= KEYGUARD_DISABLE_SHORTCUTS_ALL }
if(calculate-KEYGUARD_DISABLE_BIOMETRICS >= 0 && VERSION.SDK_INT >= 28) { biometrics=true; calculate -= KEYGUARD_DISABLE_BIOMETRICS }
if(calculate-KEYGUARD_DISABLE_IRIS >= 0 && VERSION.SDK_INT >= 28) { iris=true; calculate -= KEYGUARD_DISABLE_IRIS }
if(calculate-KEYGUARD_DISABLE_FACE >= 0 && VERSION.SDK_INT >= 28) { face=true; calculate -= KEYGUARD_DISABLE_FACE }
if(calculate-KEYGUARD_DISABLE_REMOTE_INPUT >= 0 && VERSION.SDK_INT >= 24) { remote=true; calculate -= KEYGUARD_DISABLE_REMOTE_INPUT }
if(calculate-KEYGUARD_DISABLE_FINGERPRINT >= 0) { fingerprint=true; calculate -= KEYGUARD_DISABLE_FINGERPRINT }
if(calculate-KEYGUARD_DISABLE_TRUST_AGENTS >= 0) { agents=true; calculate -= KEYGUARD_DISABLE_TRUST_AGENTS }
if(calculate-KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS >= 0) { unredacted=true; calculate -= KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS }
if(calculate-KEYGUARD_DISABLE_SECURE_NOTIFICATIONS >= 0) { notification=true; calculate -= KEYGUARD_DISABLE_SECURE_NOTIFICATIONS }
if(calculate-KEYGUARD_DISABLE_SECURE_CAMERA >= 0) { camera=true; calculate -= KEYGUARD_DISABLE_SECURE_CAMERA }
if(calculate-KEYGUARD_DISABLE_WIDGETS_ALL >= 0) { widgets=true; calculate -= KEYGUARD_DISABLE_WIDGETS_ALL }
}
var flag by remember { mutableIntStateOf(0) }
var mode by remember { mutableIntStateOf(0) } // 0:Enable all, 1:Disable all, 2:Custom
val flagsLiat = mutableListOf(
R.string.disable_keyguard_features_widgets to KEYGUARD_DISABLE_WIDGETS_ALL,
R.string.disable_keyguard_features_camera to KEYGUARD_DISABLE_SECURE_CAMERA,
R.string.disable_keyguard_features_notification to KEYGUARD_DISABLE_SECURE_NOTIFICATIONS,
R.string.disable_keyguard_features_unredacted_notification to KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS,
R.string.disable_keyguard_features_trust_agents to KEYGUARD_DISABLE_TRUST_AGENTS,
R.string.disable_keyguard_features_fingerprint to KEYGUARD_DISABLE_FINGERPRINT
)
if(VERSION.SDK_INT >= 28) {
flagsLiat +=R.string.disable_keyguard_features_face to KEYGUARD_DISABLE_FACE
flagsLiat += R.string.disable_keyguard_features_iris to KEYGUARD_DISABLE_IRIS
flagsLiat += R.string.disable_keyguard_features_biometrics to KEYGUARD_DISABLE_BIOMETRICS
}
if(state==-1) {
state = when(dpm.getKeyguardDisabledFeatures(receiver)) {
if(VERSION.SDK_INT >= 34) flagsLiat += R.string.disable_keyguard_features_shortcuts to KEYGUARD_DISABLE_SHORTCUTS_ALL
fun refresh() {
flag = dpm.getKeyguardDisabledFeatures(receiver)
mode = when(flag) {
KEYGUARD_DISABLE_FEATURES_NONE -> 0
KEYGUARD_DISABLE_FEATURES_ALL -> 1
else -> 2
}
calculateCustomFeature()
}
LaunchedEffect(mode) { if(mode != 2) flag = dpm.getKeyguardDisabledFeatures(receiver) }
LaunchedEffect(Unit) { refresh() }
MyScaffold(R.string.disable_keyguard_features, 8.dp, navCtrl) {
RadioButtonItem(R.string.enable_all, state == 0, { state = 0 })
RadioButtonItem(R.string.disable_all, state == 1, { state = 1 })
RadioButtonItem(R.string.custom, state == 2 , { state = 2 })
AnimatedVisibility(state==2) {
RadioButtonItem(R.string.enable_all, mode == 0) { mode = 0 }
RadioButtonItem(R.string.disable_all, mode == 1) { mode = 1 }
RadioButtonItem(R.string.custom, mode == 2) { mode = 2 }
AnimatedVisibility(mode == 2) {
Column {
CheckBoxItem(R.string.disable_keyguard_features_widgets, widgets, { widgets = it })
CheckBoxItem(R.string.disable_keyguard_features_camera, camera, { camera = it })
CheckBoxItem(R.string.disable_keyguard_features_notification, notification, { notification = it })
CheckBoxItem(R.string.disable_keyguard_features_unredacted_notification, unredacted, { unredacted = it })
CheckBoxItem(R.string.disable_keyguard_features_trust_agents, agents, { agents = it })
CheckBoxItem(R.string.disable_keyguard_features_fingerprint, fingerprint, { fingerprint = it })
if(VERSION.SDK_INT >= 24) { CheckBoxItem(R.string.disable_keyguard_features_remote_input, remote , { remote = it }) }
if(VERSION.SDK_INT >= 28) {
CheckBoxItem(R.string.disable_keyguard_features_face, face, { face = it })
CheckBoxItem(R.string.disable_keyguard_features_iris, iris, { iris = it })
CheckBoxItem(R.string.disable_keyguard_features_biometrics, biometrics, { biometrics = it })
flagsLiat.forEach {
CheckBoxItem(it.first, flag and it.second == it.second) { checked ->
flag = if(checked) flag or it.second else flag and (flag xor it.second)
}
}
if(VERSION.SDK_INT >= 34) { CheckBoxItem(R.string.disable_keyguard_features_shortcuts, shortcuts, { shortcuts = it }) }
}
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
var result = 0
if(state==0) { result = 0 }
else if(state==1) { result = KEYGUARD_DISABLE_FEATURES_ALL }
else{
if(shortcuts && VERSION.SDK_INT >= 34) { result+=KEYGUARD_DISABLE_SHORTCUTS_ALL }
if(biometrics && VERSION.SDK_INT >= 28) { result+=KEYGUARD_DISABLE_BIOMETRICS }
if(iris && VERSION.SDK_INT >= 28) { result+=KEYGUARD_DISABLE_IRIS }
if(face && VERSION.SDK_INT >= 28) { result+=KEYGUARD_DISABLE_FACE }
if(remote && VERSION.SDK_INT >= 24) { result+=KEYGUARD_DISABLE_REMOTE_INPUT }
if(fingerprint) { result+=KEYGUARD_DISABLE_FINGERPRINT }
if(agents) { result+=KEYGUARD_DISABLE_TRUST_AGENTS }
if(unredacted) { result+=KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS }
if(notification) { result+=KEYGUARD_DISABLE_SECURE_NOTIFICATIONS }
if(camera) { result+=KEYGUARD_DISABLE_SECURE_CAMERA }
if(widgets) { result+=KEYGUARD_DISABLE_WIDGETS_ALL }
}
dpm.setKeyguardDisabledFeatures(receiver,result)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
calculateCustomFeature()
val disabledFeatures = if(mode == 0) KEYGUARD_DISABLE_FEATURES_NONE else if(mode == 1) KEYGUARD_DISABLE_FEATURES_ALL else flag
dpm.setKeyguardDisabledFeatures(receiver, disabledFeatures)
refresh()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -576,13 +526,13 @@ fun PasswordQuality(navCtrl: NavHostController) {
LaunchedEffect(Unit) { selectedItem=dpm.getPasswordQuality(receiver) }
MyScaffold(R.string.required_password_quality, 8.dp, navCtrl) {
passwordQuality.forEach {
RadioButtonItem(it.value, selectedItem == it.key, { selectedItem = it.key })
RadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key }
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
dpm.setPasswordQuality(receiver,selectedItem)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {

View File

@@ -31,6 +31,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.backToHomeStateFlow
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.writeClipBoard
import com.bintianqi.owndroid.yesOrNo
@@ -56,9 +57,9 @@ fun Permissions(navCtrl: NavHostController) {
MyScaffold(R.string.permissions, 0.dp, navCtrl) {
if(!dpm.isDeviceOwnerApp(context.packageName)) {
SwitchItem(
R.string.dhizuku, "", null,
{ sharedPref.getBoolean("dhizuku", false) },
{ toggleDhizukuMode(it, context) },
R.string.dhizuku,
getState = { sharedPref.getBoolean("dhizuku", false) },
onCheckedChange = { toggleDhizukuMode(it, context) },
onClickBlank = { dialog = 4 }
)
}
@@ -78,7 +79,7 @@ fun Permissions(navCtrl: NavHostController) {
operation = { navCtrl.navigate("DeviceOwner") }
)
}
FunctionItem(R.string.shizuku,"") {
FunctionItem(R.string.shizuku) {
try {
if(Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { navCtrl.navigate("Shizuku") }
else if(Shizuku.shouldShowRequestPermissionRationale()) {
@@ -102,24 +103,24 @@ fun Permissions(navCtrl: NavHostController) {
Toast.makeText(context, R.string.shizuku_not_started, Toast.LENGTH_SHORT).show()
}
}
FunctionItem(R.string.device_info, "", R.drawable.perm_device_information_fill0) { navCtrl.navigate("DeviceInfo") }
FunctionItem(R.string.device_info, icon = R.drawable.perm_device_information_fill0) { navCtrl.navigate("DeviceInfo") }
if((VERSION.SDK_INT >= 26 && deviceOwner) || (VERSION.SDK_INT>=24 && profileOwner)) {
FunctionItem(R.string.org_name, "", R.drawable.corporate_fare_fill0) { dialog = 2 }
FunctionItem(R.string.org_name, icon = R.drawable.corporate_fare_fill0) { dialog = 2 }
}
if(VERSION.SDK_INT >= 31 && (profileOwner || deviceOwner)) {
FunctionItem(R.string.org_id, "", R.drawable.corporate_fare_fill0) { dialog = 3 }
FunctionItem(R.string.org_id, icon = R.drawable.corporate_fare_fill0) { dialog = 3 }
}
if(enrollmentSpecificId != "") {
FunctionItem(R.string.enrollment_specific_id, "", R.drawable.id_card_fill0) { dialog = 1 }
FunctionItem(R.string.enrollment_specific_id, icon = R.drawable.id_card_fill0) { dialog = 1 }
}
if(VERSION.SDK_INT >= 24 && (deviceOwner || dpm.isOrgProfile(receiver))) {
FunctionItem(R.string.lock_screen_info, "", R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("LockScreenInfo") }
FunctionItem(R.string.lock_screen_info, icon = R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("LockScreenInfo") }
}
if(VERSION.SDK_INT >= 24 && deviceAdmin) {
FunctionItem(R.string.support_messages, "", R.drawable.chat_fill0) { navCtrl.navigate("SupportMessages") }
FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { navCtrl.navigate("SupportMessages") }
}
if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.transfer_ownership, "", R.drawable.admin_panel_settings_fill0) { navCtrl.navigate("TransferOwnership") }
FunctionItem(R.string.transfer_ownership, icon = R.drawable.admin_panel_settings_fill0) { navCtrl.navigate("TransferOwnership") }
}
}
if(dialog != 0) {
@@ -254,7 +255,7 @@ fun LockScreenInfo(navCtrl: NavHostController) {
onClick = {
focusMgr.clearFocus()
dpm.setDeviceOwnerLockScreenInfo(receiver,infoText)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -263,8 +264,8 @@ fun LockScreenInfo(navCtrl: NavHostController) {
Button(
onClick = {
focusMgr.clearFocus()
dpm.setDeviceOwnerLockScreenInfo(receiver,null)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
dpm.setDeviceOwnerLockScreenInfo(receiver, null)
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -521,7 +522,7 @@ fun SupportMessages(navCtrl: NavHostController) {
onClick = {
dpm.setShortSupportMessage(receiver, shortMsg)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
@@ -531,7 +532,7 @@ fun SupportMessages(navCtrl: NavHostController) {
onClick = {
dpm.setShortSupportMessage(receiver, null)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
@@ -552,7 +553,7 @@ fun SupportMessages(navCtrl: NavHostController) {
onClick = {
dpm.setLongSupportMessage(receiver, longMsg)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
@@ -562,7 +563,7 @@ fun SupportMessages(navCtrl: NavHostController) {
onClick = {
dpm.setLongSupportMessage(receiver, null)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
@@ -615,7 +616,7 @@ fun TransferOwnership(navCtrl: NavHostController) {
val receiver = context.getReceiver()
try {
dpm.transferOwnership(receiver, componentName!!, null)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
dialog = false
backToHomeStateFlow.value = true
} catch(e: Exception) {

View File

@@ -39,6 +39,8 @@ import android.net.Uri
import android.os.Build.VERSION
import android.os.UserManager
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
@@ -84,7 +86,6 @@ import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
@@ -109,14 +110,9 @@ import androidx.navigation.NavHostController
import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.NotificationUtils
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.exportFile
import com.bintianqi.owndroid.exportFilePath
import com.bintianqi.owndroid.fileUriFlow
import com.bintianqi.owndroid.formatFileSize
import com.bintianqi.owndroid.getFile
import com.bintianqi.owndroid.humanReadableDate
import com.bintianqi.owndroid.isExportingSecurityOrNetworkLogs
import com.bintianqi.owndroid.toggle
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
@@ -125,17 +121,13 @@ import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.RadioButtonItem
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.uriToStream
import com.bintianqi.owndroid.yesOrNo
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.encodeToStream
import kotlinx.serialization.json.put
import java.io.ByteArrayOutputStream
import java.util.Date
import java.util.TimeZone
import java.util.concurrent.Executors
import kotlin.math.pow
@SuppressLint("NewApi")
@Composable
@@ -151,53 +143,52 @@ fun SystemManage(navCtrl: NavHostController) {
var dialog by remember { mutableIntStateOf(0) }
MyScaffold(R.string.system, 0.dp, navCtrl) {
if(deviceOwner || profileOwner) {
FunctionItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("SystemOptions") }
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { navCtrl.navigate("SystemOptions") }
}
FunctionItem(R.string.keyguard, "", R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("Keyguard") }
FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("Keyguard") }
if(VERSION.SDK_INT >= 24 && deviceOwner) {
FunctionItem(R.string.reboot, "", R.drawable.restart_alt_fill0) { dialog = 1 }
FunctionItem(R.string.reboot, icon = R.drawable.restart_alt_fill0) { dialog = 1 }
}
if(deviceOwner && ((VERSION.SDK_INT >= 28 && dpm.isAffiliatedUser) || VERSION.SDK_INT >= 24)) {
FunctionItem(R.string.bug_report, "", R.drawable.bug_report_fill0) { dialog = 2 }
FunctionItem(R.string.bug_report, icon = R.drawable.bug_report_fill0) { dialog = 2 }
}
if(VERSION.SDK_INT >= 28 && (deviceOwner || dpm.isOrgProfile(receiver))) {
FunctionItem(R.string.change_time, "", R.drawable.schedule_fill0) { navCtrl.navigate("ChangeTime") }
FunctionItem(R.string.change_timezone, "", R.drawable.schedule_fill0) { navCtrl.navigate("ChangeTimeZone") }
FunctionItem(R.string.change_time, icon = R.drawable.schedule_fill0) { navCtrl.navigate("ChangeTime") }
FunctionItem(R.string.change_timezone, icon = R.drawable.schedule_fill0) { navCtrl.navigate("ChangeTimeZone") }
}
if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.permission_policy, "", R.drawable.key_fill0) { navCtrl.navigate("PermissionPolicy") }
FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { navCtrl.navigate("PermissionPolicy") }
}
if(VERSION.SDK_INT >= 34 && deviceOwner) {
FunctionItem(R.string.mte_policy, "", R.drawable.memory_fill0) { navCtrl.navigate("MTEPolicy") }
FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { navCtrl.navigate("MTEPolicy") }
}
if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.nearby_streaming_policy, "", R.drawable.share_fill0) { navCtrl.navigate("NearbyStreamingPolicy") }
FunctionItem(R.string.nearby_streaming_policy, icon = R.drawable.share_fill0) { navCtrl.navigate("NearbyStreamingPolicy") }
}
if(VERSION.SDK_INT >= 28 && deviceOwner) {
FunctionItem(R.string.lock_task_mode, "", R.drawable.lock_fill0) { navCtrl.navigate("LockTaskMode") }
FunctionItem(R.string.lock_task_mode, icon = R.drawable.lock_fill0) { navCtrl.navigate("LockTaskMode") }
}
if(deviceOwner || profileOwner) {
FunctionItem(R.string.ca_cert, "", R.drawable.license_fill0) { navCtrl.navigate("CACert") }
FunctionItem(R.string.ca_cert, icon = R.drawable.license_fill0) { navCtrl.navigate("CACert") }
}
if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || dpm.isOrgProfile(receiver))) {
FunctionItem(R.string.security_logging, "", R.drawable.description_fill0) { navCtrl.navigate("SecurityLogging") }
FunctionItem(R.string.security_logging, icon = R.drawable.description_fill0) { navCtrl.navigate("SecurityLogging") }
}
if(deviceOwner || profileOwner) {
FunctionItem(R.string.disable_account_management, "", R.drawable.account_circle_fill0) { navCtrl.navigate("DisableAccountManagement") }
FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { navCtrl.navigate("DisableAccountManagement") }
}
if(VERSION.SDK_INT >= 23 && (deviceOwner || dpm.isOrgProfile(receiver))) {
FunctionItem(R.string.system_update_policy, "", R.drawable.system_update_fill0) { navCtrl.navigate("SystemUpdatePolicy") }
FunctionItem(R.string.system_update_policy, icon = R.drawable.system_update_fill0) { navCtrl.navigate("SystemUpdatePolicy") }
}
if(VERSION.SDK_INT >= 29 && (deviceOwner || dpm.isOrgProfile(receiver))) {
FunctionItem(R.string.install_system_update, "", R.drawable.system_update_fill0) { navCtrl.navigate("InstallSystemUpdate") }
FunctionItem(R.string.install_system_update, icon = R.drawable.system_update_fill0) { navCtrl.navigate("InstallSystemUpdate") }
}
if(VERSION.SDK_INT >= 30 && (deviceOwner || dpm.isOrgProfile(receiver))) {
FunctionItem(R.string.frp_policy, "", R.drawable.device_reset_fill0) { navCtrl.navigate("FRPPolicy") }
FunctionItem(R.string.frp_policy, icon = R.drawable.device_reset_fill0) { navCtrl.navigate("FRPPolicy") }
}
if(dangerousFeatures && context.isDeviceAdmin && !(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver))) {
FunctionItem(R.string.wipe_data, "", R.drawable.device_reset_fill0) { navCtrl.navigate("WipeData") }
FunctionItem(R.string.wipe_data, icon = R.drawable.device_reset_fill0) { navCtrl.navigate("WipeData") }
}
LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") }
}
if(dialog != 0) AlertDialog(
onDismissRequest = { dialog = 0 },
@@ -214,8 +205,7 @@ fun SystemManage(navCtrl: NavHostController) {
if(dialog == 1) {
dpm.reboot(receiver)
} else {
val result = dpm.requestBugreport(receiver)
Toast.makeText(context, if(result) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(dpm.requestBugreport(receiver))
}
dialog = 0
}
@@ -238,58 +228,60 @@ fun SystemOptions(navCtrl: NavHostController) {
var dialog by remember { mutableIntStateOf(0) }
MyScaffold(R.string.options, 0.dp, navCtrl) {
if(deviceOwner || profileOwner) {
SwitchItem(R.string.disable_cam,"", R.drawable.photo_camera_fill0,
{ dpm.getCameraDisabled(null) }, { dpm.setCameraDisabled(receiver,it) }
SwitchItem(R.string.disable_cam, icon = R.drawable.photo_camera_fill0,
getState = { dpm.getCameraDisabled(null) }, onCheckedChange = { dpm.setCameraDisabled(receiver,it) }
)
}
if(deviceOwner || profileOwner) {
SwitchItem(R.string.disable_screen_capture, "", R.drawable.screenshot_fill0,
{ dpm.getScreenCaptureDisabled(null) }, { dpm.setScreenCaptureDisabled(receiver,it) }
SwitchItem(R.string.disable_screen_capture, icon = R.drawable.screenshot_fill0,
getState = { dpm.getScreenCaptureDisabled(null) }, onCheckedChange = { dpm.setScreenCaptureDisabled(receiver,it) }
)
}
if(VERSION.SDK_INT >= 34 && (deviceOwner || (profileOwner && dpm.isAffiliatedUser))) {
SwitchItem(R.string.disable_status_bar, "", R.drawable.notifications_fill0,
{ dpm.isStatusBarDisabled}, { dpm.setStatusBarDisabled(receiver,it) }
SwitchItem(R.string.disable_status_bar, icon = R.drawable.notifications_fill0,
getState = { dpm.isStatusBarDisabled}, onCheckedChange = { dpm.setStatusBarDisabled(receiver,it) }
)
}
if(deviceOwner || (VERSION.SDK_INT >= 23 && profileOwner && um.isSystemUser) || dpm.isOrgProfile(receiver)) {
if(VERSION.SDK_INT >= 30) {
SwitchItem(R.string.auto_time, "", R.drawable.schedule_fill0,
{ dpm.getAutoTimeEnabled(receiver) }, { dpm.setAutoTimeEnabled(receiver,it) }
SwitchItem(R.string.auto_time, icon = R.drawable.schedule_fill0,
getState = { dpm.getAutoTimeEnabled(receiver) }, onCheckedChange = { dpm.setAutoTimeEnabled(receiver,it) }
)
SwitchItem(R.string.auto_timezone, "", R.drawable.globe_fill0,
{ dpm.getAutoTimeZoneEnabled(receiver) }, { dpm.setAutoTimeZoneEnabled(receiver,it) }
SwitchItem(R.string.auto_timezone, icon = R.drawable.globe_fill0,
getState = { dpm.getAutoTimeZoneEnabled(receiver) }, onCheckedChange = { dpm.setAutoTimeZoneEnabled(receiver,it) }
)
}else{
SwitchItem(R.string.require_auto_time, "", R.drawable.schedule_fill0, { dpm.autoTimeRequired}, { dpm.setAutoTimeRequired(receiver,it) }, padding = false)
} else {
SwitchItem(R.string.require_auto_time, icon = R.drawable.schedule_fill0,
getState = { dpm.autoTimeRequired}, onCheckedChange = { dpm.setAutoTimeRequired(receiver,it) }, padding = false)
}
}
if(deviceOwner || (profileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && !dpm.isManagedProfile(receiver))))) {
SwitchItem(R.string.master_mute, "", R.drawable.volume_up_fill0,
{ dpm.isMasterVolumeMuted(receiver) }, { dpm.setMasterVolumeMuted(receiver,it) }
SwitchItem(R.string.master_mute, icon = R.drawable.volume_up_fill0,
getState = { dpm.isMasterVolumeMuted(receiver) }, onCheckedChange = { dpm.setMasterVolumeMuted(receiver,it) }
)
}
if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) {
SwitchItem(R.string.backup_service, "", R.drawable.backup_fill0,
{ dpm.isBackupServiceEnabled(receiver) }, { dpm.setBackupServiceEnabled(receiver,it) },
SwitchItem(R.string.backup_service, icon = R.drawable.backup_fill0,
getState = { dpm.isBackupServiceEnabled(receiver) }, onCheckedChange = { dpm.setBackupServiceEnabled(receiver,it) },
onClickBlank = { dialog = 1 }
)
}
if(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver)) {
SwitchItem(R.string.disable_bt_contact_share, "", R.drawable.account_circle_fill0,
{ dpm.getBluetoothContactSharingDisabled(receiver) }, { dpm.setBluetoothContactSharingDisabled(receiver,it) },
SwitchItem(R.string.disable_bt_contact_share, icon = R.drawable.account_circle_fill0,
getState = { dpm.getBluetoothContactSharingDisabled(receiver) },
onCheckedChange = { dpm.setBluetoothContactSharingDisabled(receiver,it) }
)
}
if(VERSION.SDK_INT >= 30 && deviceOwner) {
SwitchItem(R.string.common_criteria_mode , "",R.drawable.security_fill0,
{ dpm.isCommonCriteriaModeEnabled(receiver) }, { dpm.setCommonCriteriaModeEnabled(receiver,it) },
SwitchItem(R.string.common_criteria_mode , icon =R.drawable.security_fill0,
getState = { dpm.isCommonCriteriaModeEnabled(receiver) }, onCheckedChange = { dpm.setCommonCriteriaModeEnabled(receiver,it) },
onClickBlank = { dialog = 2 }
)
}
if(VERSION.SDK_INT >= 31 && (deviceOwner || dpm.isOrgProfile(receiver)) && dpm.canUsbDataSignalingBeDisabled()) {
SwitchItem(
R.string.disable_usb_signal, "", R.drawable.usb_fill0, { !dpm.isUsbDataSignalingEnabled },
{ dpm.isUsbDataSignalingEnabled = !it },
R.string.disable_usb_signal, icon = R.drawable.usb_fill0, getState = { !dpm.isUsbDataSignalingEnabled },
onCheckedChange = { dpm.isUsbDataSignalingEnabled = !it },
)
}
}
@@ -324,18 +316,14 @@ fun Keyguard(navCtrl: NavHostController) {
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = {
Toast.makeText(context, if(dpm.setKeyguardDisabled(receiver,true)) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
},
onClick = { context.showOperationResultToast(dpm.setKeyguardDisabled(receiver, true)) },
enabled = deviceOwner || (VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser),
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.disable))
}
Button(
onClick = {
Toast.makeText(context, if(dpm.setKeyguardDisabled(receiver,false)) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
},
onClick = { context.showOperationResultToast(dpm.setKeyguardDisabled(receiver, false)) },
enabled = deviceOwner || (VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser),
modifier = Modifier.fillMaxWidth(0.96F)
) {
@@ -351,9 +339,8 @@ fun Keyguard(navCtrl: NavHostController) {
if(VERSION.SDK_INT >= 26 && profileOwner && dpm.isManagedProfile(receiver)) {
CheckBoxItem(
R.string.evict_credential_encryptoon_key,
flag == FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY,
{ flag = if(flag==0) {1}else{0} }
)
flag and FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY != 0
) { flag = flag xor FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY }
Spacer(Modifier.padding(vertical = 2.dp))
}
Button(
@@ -453,8 +440,7 @@ fun ChangeTime(navCtrl: NavHostController) {
onClick = {
val timeMillis = if(manualInput) inputTime.toLong()
else datePickerState.selectedDateMillis!! + timePickerState.hour * 3600000 + timePickerState.minute * 60000
val result = dpm.setTime(receiver, timeMillis)
Toast.makeText(context, if(result) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(dpm.setTime(receiver, timeMillis))
},
modifier = Modifier.fillMaxWidth(),
enabled = isInputLegal
@@ -509,8 +495,7 @@ fun ChangeTimeZone(navCtrl: NavHostController) {
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
val result = dpm.setTimeZone(receiver, inputTimezone)
Toast.makeText(context, if(result) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(dpm.setTimeZone(receiver, inputTimezone))
},
modifier = Modifier.fillMaxWidth()
) {
@@ -555,14 +540,14 @@ fun PermissionPolicy(navCtrl: NavHostController) {
val receiver = context.getReceiver()
var selectedPolicy by remember { mutableIntStateOf(dpm.getPermissionPolicy(receiver)) }
MyScaffold(R.string.permission_policy, 8.dp, navCtrl) {
RadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT, { selectedPolicy = PERMISSION_POLICY_PROMPT })
RadioButtonItem(R.string.auto_grant, selectedPolicy == PERMISSION_POLICY_AUTO_GRANT, { selectedPolicy = PERMISSION_POLICY_AUTO_GRANT })
RadioButtonItem(R.string.auto_deny, selectedPolicy == PERMISSION_POLICY_AUTO_DENY, { selectedPolicy = PERMISSION_POLICY_AUTO_DENY })
RadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT) { selectedPolicy = PERMISSION_POLICY_PROMPT }
RadioButtonItem(R.string.auto_grant, selectedPolicy == PERMISSION_POLICY_AUTO_GRANT) { selectedPolicy = PERMISSION_POLICY_AUTO_GRANT }
RadioButtonItem(R.string.auto_deny, selectedPolicy == PERMISSION_POLICY_AUTO_DENY) { selectedPolicy = PERMISSION_POLICY_AUTO_DENY }
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
dpm.setPermissionPolicy(receiver,selectedPolicy)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -579,26 +564,14 @@ fun MTEPolicy(navCtrl: NavHostController) {
val dpm = context.getDPM()
var selectedMtePolicy by remember { mutableIntStateOf(dpm.mtePolicy) }
MyScaffold(R.string.mte_policy, 8.dp, navCtrl) {
RadioButtonItem(
R.string.decide_by_user,
selectedMtePolicy == MTE_NOT_CONTROLLED_BY_POLICY,
{ selectedMtePolicy = MTE_NOT_CONTROLLED_BY_POLICY }
)
RadioButtonItem(
R.string.enabled,
selectedMtePolicy == MTE_ENABLED,
{ selectedMtePolicy = MTE_ENABLED }
)
RadioButtonItem(
R.string.disabled,
selectedMtePolicy == MTE_DISABLED,
{ selectedMtePolicy = MTE_DISABLED }
)
RadioButtonItem(R.string.decide_by_user, selectedMtePolicy == MTE_NOT_CONTROLLED_BY_POLICY) { selectedMtePolicy = MTE_NOT_CONTROLLED_BY_POLICY }
RadioButtonItem(R.string.enabled, selectedMtePolicy == MTE_ENABLED) { selectedMtePolicy = MTE_ENABLED }
RadioButtonItem(R.string.disabled, selectedMtePolicy == MTE_DISABLED) { selectedMtePolicy = MTE_DISABLED }
Button(
onClick = {
try {
dpm.mtePolicy = selectedMtePolicy
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
} catch(_: java.lang.UnsupportedOperationException) {
Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show()
}
@@ -624,29 +597,19 @@ fun NearbyStreamingPolicy(navCtrl: NavHostController) {
Spacer(Modifier.padding(vertical = 3.dp))
RadioButtonItem(
R.string.decide_by_user,
appPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY,
{ appPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY }
)
RadioButtonItem(
R.string.enabled,
appPolicy == NEARBY_STREAMING_ENABLED,
{ appPolicy = NEARBY_STREAMING_ENABLED }
)
RadioButtonItem(
R.string.disabled,
appPolicy == NEARBY_STREAMING_DISABLED,
{ appPolicy = NEARBY_STREAMING_DISABLED }
)
appPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY
) { appPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY }
RadioButtonItem(R.string.enabled, appPolicy == NEARBY_STREAMING_ENABLED) { appPolicy = NEARBY_STREAMING_ENABLED }
RadioButtonItem(R.string.disabled, appPolicy == NEARBY_STREAMING_DISABLED) { appPolicy = NEARBY_STREAMING_DISABLED }
RadioButtonItem(
R.string.enable_if_secure_enough,
appPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY,
{ appPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY }
)
appPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
) { appPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY }
Spacer(Modifier.padding(vertical = 3.dp))
Button(
onClick = {
dpm.nearbyAppStreamingPolicy = appPolicy
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -659,29 +622,25 @@ fun NearbyStreamingPolicy(navCtrl: NavHostController) {
Spacer(Modifier.padding(vertical = 3.dp))
RadioButtonItem(
R.string.decide_by_user,
notificationPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY,
{ notificationPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY }
)
notificationPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY
) { notificationPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY }
RadioButtonItem(
R.string.enabled,
notificationPolicy == NEARBY_STREAMING_ENABLED,
{ notificationPolicy = NEARBY_STREAMING_ENABLED }
)
notificationPolicy == NEARBY_STREAMING_ENABLED
) { notificationPolicy = NEARBY_STREAMING_ENABLED }
RadioButtonItem(
R.string.disabled,
notificationPolicy == NEARBY_STREAMING_DISABLED,
{ notificationPolicy = NEARBY_STREAMING_DISABLED }
)
notificationPolicy == NEARBY_STREAMING_DISABLED
) { notificationPolicy = NEARBY_STREAMING_DISABLED }
RadioButtonItem(
R.string.enable_if_secure_enough,
notificationPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY,
{ notificationPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY }
)
notificationPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
) { notificationPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY }
Spacer(Modifier.padding(vertical = 3.dp))
Button(
onClick = {
dpm.nearbyNotificationStreamingPolicy = notificationPolicy
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -700,84 +659,58 @@ fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
val focusMgr = LocalFocusManager.current
var appSelectorRequest by rememberSaveable { mutableIntStateOf(0) }
MyScaffold(R.string.lock_task_mode, 8.dp, navCtrl, false) {
val lockTaskFeatures = remember { mutableStateListOf<Int>() }
var lockTaskFeatures by remember { mutableIntStateOf(0) }
var custom by rememberSaveable { mutableStateOf(false) }
val refreshFeature = {
var calculate = dpm.getLockTaskFeatures(receiver)
lockTaskFeatures.clear()
if(calculate != 0) {
var sq = 10
while(sq >= 1) {
val current = (2).toDouble().pow(sq.toDouble()).toInt()
if(calculate - current >= 0) {
lockTaskFeatures += current
calculate -= current
}
sq--
}
if(calculate - 1 >= 0) { lockTaskFeatures += 1 }
custom = true
} else {
custom = false
}
fun refreshFeature() {
lockTaskFeatures = dpm.getLockTaskFeatures(receiver)
custom = lockTaskFeatures != 0
}
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.lock_task_feature), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
LaunchedEffect(Unit) { refreshFeature() }
RadioButtonItem(R.string.disable_all, !custom, { custom = false })
RadioButtonItem(R.string.custom, custom, { custom = true })
RadioButtonItem(R.string.disable_all, !custom) { custom = false }
RadioButtonItem(R.string.custom, custom) { custom = true }
AnimatedVisibility(custom) {
Column {
CheckBoxItem(
R.string.ltf_sys_info,
LOCK_TASK_FEATURE_SYSTEM_INFO in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_SYSTEM_INFO) }
)
lockTaskFeatures and LOCK_TASK_FEATURE_SYSTEM_INFO != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_SYSTEM_INFO }
CheckBoxItem(
R.string.ltf_notifications,
LOCK_TASK_FEATURE_NOTIFICATIONS in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_NOTIFICATIONS) }
)
lockTaskFeatures and LOCK_TASK_FEATURE_NOTIFICATIONS != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_NOTIFICATIONS }
CheckBoxItem(
R.string.ltf_home,
LOCK_TASK_FEATURE_HOME in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_HOME) }
)
lockTaskFeatures and LOCK_TASK_FEATURE_HOME != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_HOME }
CheckBoxItem(
R.string.ltf_overview,
LOCK_TASK_FEATURE_OVERVIEW in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_OVERVIEW) }
)
lockTaskFeatures and LOCK_TASK_FEATURE_OVERVIEW != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_OVERVIEW }
CheckBoxItem(
R.string.ltf_global_actions,
LOCK_TASK_FEATURE_GLOBAL_ACTIONS in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_GLOBAL_ACTIONS) }
)
lockTaskFeatures and LOCK_TASK_FEATURE_GLOBAL_ACTIONS != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_GLOBAL_ACTIONS }
CheckBoxItem(
R.string.ltf_keyguard,
LOCK_TASK_FEATURE_KEYGUARD in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_KEYGUARD) }
)
lockTaskFeatures and LOCK_TASK_FEATURE_KEYGUARD != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_KEYGUARD }
if(VERSION.SDK_INT >= 30) {
CheckBoxItem(
R.string.ltf_block_activity_start_in_task,
LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK in lockTaskFeatures,
{ lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK) }
)
lockTaskFeatures and LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK }
}
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
var result = 0
if(custom) {
lockTaskFeatures.forEach { result += it }
}
try {
dpm.setLockTaskFeatures(receiver, result)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
dpm.setLockTaskFeatures(receiver, lockTaskFeatures)
context.showOperationResultToast(true)
} catch (e: IllegalArgumentException) {
AlertDialog.Builder(context)
.setTitle(R.string.error)
@@ -846,7 +779,7 @@ fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
modifier = Modifier.fillMaxWidth(),
onClick = {
dpm.setLockTaskPackages(receiver, lockTaskPackages.toTypedArray())
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
}
) {
Text(stringResource(R.string.apply))
@@ -884,7 +817,7 @@ fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
},
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
CheckBoxItem(R.string.specify_activity, specifyActivity, { specifyActivity = it })
CheckBoxItem(R.string.specify_activity, specifyActivity) { specifyActivity = it }
AnimatedVisibility(specifyActivity) {
OutlinedTextField(
value = startLockTaskActivity,
@@ -925,29 +858,25 @@ fun CACert(navCtrl: NavHostController) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val uri by fileUriFlow.collectAsState()
var exist by remember { mutableStateOf(false) }
val uriPath = uri.path ?: ""
var caCertByteArray by remember { mutableStateOf(byteArrayOf()) }
LaunchedEffect(uri) {
if(uri != Uri.parse("")) {
var fileUri by remember { mutableStateOf<Uri?>(null) }
var caCertByteArray by remember { mutableStateOf(ByteArray(100000)) }
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result.data?.data?.let { uri ->
uriToStream(context, uri) {
val array = it.readBytes()
caCertByteArray = if(array.size < 10000) {
array
}else{
} else {
byteArrayOf()
}
}
exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
}
}
MyScaffold(R.string.ca_cert, 8.dp, navCtrl) {
AnimatedVisibility(uriPath != "") {
Text(text = uriPath)
}
Text(
text = if(uriPath == "") { stringResource(R.string.please_select_ca_cert) } else { stringResource(R.string.cert_installed, exist) },
text = if(fileUri == null) { stringResource(R.string.please_select_ca_cert) }
else { stringResource(R.string.cert_installed, stringResource(exist.yesOrNo)) },
modifier = Modifier.animateContentSize()
)
Spacer(Modifier.padding(vertical = 5.dp))
@@ -956,18 +885,17 @@ fun CACert(navCtrl: NavHostController) {
val caCertIntent = Intent(Intent.ACTION_GET_CONTENT)
caCertIntent.setType("*/*")
caCertIntent.addCategory(Intent.CATEGORY_OPENABLE)
getFile.launch(caCertIntent)
getFileLauncher.launch(caCertIntent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.select_ca_cert))
}
AnimatedVisibility(uriPath != "") {
AnimatedVisibility(fileUri != null) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
val result = dpm.installCaCert(receiver, caCertByteArray)
Toast.makeText(context, if(result) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(dpm.installCaCert(receiver, caCertByteArray))
exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
},
modifier = Modifier.fillMaxWidth(0.49F)
@@ -976,12 +904,11 @@ fun CACert(navCtrl: NavHostController) {
}
Button(
onClick = {
if(exist) {
dpm.uninstallCaCert(receiver, caCertByteArray)
exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
Toast.makeText(context, if(exist) R.string.failed else R.string.success, Toast.LENGTH_SHORT).show()
} else { Toast.makeText(context, R.string.not_exist, Toast.LENGTH_SHORT).show() }
dpm.uninstallCaCert(receiver, caCertByteArray)
exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
context.showOperationResultToast(true)
},
enabled = exist,
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.uninstall))
@@ -991,7 +918,7 @@ fun CACert(navCtrl: NavHostController) {
Button(
onClick = {
dpm.uninstallAllUserCaCerts(receiver)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -1008,11 +935,31 @@ fun SecurityLogging(navCtrl: NavHostController) {
val receiver = context.getReceiver()
val logFile = context.filesDir.resolve("SecurityLogs.json")
var fileSize by remember { mutableLongStateOf(0) }
LaunchedEffect(Unit) {
fileSize = logFile.length()
LaunchedEffect(Unit) { fileSize = logFile.length() }
var preRebootSecurityLogs by remember { mutableStateOf(byteArrayOf()) }
val exportPreRebootSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result.data?.data?.let { uri ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
preRebootSecurityLogs.inputStream().copyTo(outStream)
}
}
}
val exportSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result.data?.data?.let { uri ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".toByteArray())
logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".toByteArray())
context.showOperationResultToast(true)
}
}
}
MyScaffold(R.string.security_logging, 8.dp, navCtrl) {
SwitchItem(R.string.enable, "", null, { dpm.isSecurityLoggingEnabled(receiver) }, { dpm.setSecurityLoggingEnabled(receiver, it) }, padding = false)
SwitchItem(
R.string.enable,
getState = { dpm.isSecurityLoggingEnabled(receiver) }, onCheckedChange = { dpm.setSecurityLoggingEnabled(receiver, it) },
padding = false
)
Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button(
@@ -1021,9 +968,7 @@ fun SecurityLogging(navCtrl: NavHostController) {
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "SecurityLogs.json")
exportFilePath = logFile.path
isExportingSecurityOrNetworkLogs = true
exportFile.launch(intent)
exportSecurityLogs.launch(intent)
},
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)
@@ -1050,29 +995,16 @@ fun SecurityLogging(navCtrl: NavHostController) {
Toast.makeText(context, R.string.no_logs, Toast.LENGTH_SHORT).show()
return@Button
} else {
val securityEvents = buildJsonArray {
logs.forEach { event ->
addJsonObject {
put("time_nanos", event.timeNanos)
put("tag", event.tag)
if(VERSION.SDK_INT >= 28) put("level", event.logLevel)
if(VERSION.SDK_INT >= 28) put("id", event.id)
parseSecurityEventData(event).let { if(it != null) put("data", it) }
}
}
}
val preRebootSecurityLogs = context.filesDir.resolve("PreRebootSecurityLogs")
preRebootSecurityLogs.outputStream().use {
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
json.encodeToStream(securityEvents, it)
}
val outputStream = ByteArrayOutputStream()
outputStream.write("[".encodeToByteArray())
processSecurityLogs(logs, outputStream)
outputStream.write("]".encodeToByteArray())
preRebootSecurityLogs = outputStream.toByteArray()
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "PreRebootSecurityLogs.json")
exportFilePath = preRebootSecurityLogs.path
isExportingSecurityOrNetworkLogs = true
exportFile.launch(intent)
exportPreRebootSecurityLogs.launch(intent)
}
},
modifier = Modifier.fillMaxWidth()
@@ -1169,7 +1101,7 @@ fun FRPPolicy(navCtrl: NavHostController) {
}
AnimatedVisibility(usePolicy) {
Column {
CheckBoxItem(R.string.enable_frp, enabled, { enabled = it })
CheckBoxItem(R.string.enable_frp, enabled) { enabled = it }
Text(stringResource(R.string.account_list_is))
Column(modifier = Modifier.animateContentSize()) {
if(accountList.isEmpty()) Text(stringResource(R.string.none))
@@ -1230,25 +1162,17 @@ fun WipeData(navCtrl: NavHostController) {
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
val dpm = context.getDPM()
val focusMgr = LocalFocusManager.current
var flag by remember { mutableIntStateOf(0) }
var warning by remember { mutableStateOf(false) }
var wipeDevice by remember { mutableStateOf(false) }
var externalStorage by remember { mutableStateOf(false) }
var protectionData by remember { mutableStateOf(false) }
var euicc by remember { mutableStateOf(false) }
var silent by remember { mutableStateOf(false) }
var reason by remember { mutableStateOf("") }
MyScaffold(R.string.wipe_data, 8.dp, navCtrl) {
CheckBoxItem(
R.string.wipe_external_storage,
externalStorage, { externalStorage = it }
)
if(VERSION.SDK_INT >= 22 && context.isDeviceOwner) {
CheckBoxItem(R.string.wipe_reset_protection_data,
protectionData, { protectionData = it }
)
}
if(VERSION.SDK_INT >= 28) { CheckBoxItem(R.string.wipe_euicc, euicc, { euicc = it }) }
if(VERSION.SDK_INT >= 29) { CheckBoxItem(R.string.wipe_silently, silent, { silent = it }) }
CheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { flag = flag xor WIPE_EXTERNAL_STORAGE }
if(VERSION.SDK_INT >= 22 && context.isDeviceOwner) CheckBoxItem(
R.string.wipe_reset_protection_data, flag and WIPE_RESET_PROTECTION_DATA != 0) { flag = flag xor WIPE_RESET_PROTECTION_DATA }
if(VERSION.SDK_INT >= 28) CheckBoxItem(R.string.wipe_euicc, flag and WIPE_EUICC != 0) { flag = flag xor WIPE_EUICC }
if(VERSION.SDK_INT >= 29) CheckBoxItem(R.string.wipe_silently, silent) { silent = it }
AnimatedVisibility(!silent && VERSION.SDK_INT >= 28) {
OutlinedTextField(
value = reason, onValueChange = { reason = it },
@@ -1308,11 +1232,7 @@ fun WipeData(navCtrl: NavHostController) {
val timerText = if(timer > 0) "(${timer}s)" else ""
TextButton(
onClick = {
var flag = 0
if(externalStorage) { flag += WIPE_EXTERNAL_STORAGE }
if(protectionData && VERSION.SDK_INT >= 22) { flag += WIPE_RESET_PROTECTION_DATA }
if(euicc && VERSION.SDK_INT >= 28) { flag += WIPE_EUICC }
if(silent && VERSION.SDK_INT >= 29) { flag += WIPE_SILENTLY }
if(silent && VERSION.SDK_INT >= 29) { flag = flag or WIPE_SILENTLY }
if(wipeDevice) {
dpm.wipeDevice(flag)
} else {
@@ -1351,17 +1271,17 @@ fun SystemUpdatePolicy(navCtrl: NavHostController) {
var selectedPolicy by remember { mutableStateOf(dpm.systemUpdatePolicy?.policyType) }
RadioButtonItem(
R.string.system_update_policy_automatic,
selectedPolicy == TYPE_INSTALL_AUTOMATIC, { selectedPolicy = TYPE_INSTALL_AUTOMATIC }
)
selectedPolicy == TYPE_INSTALL_AUTOMATIC
) { selectedPolicy = TYPE_INSTALL_AUTOMATIC }
RadioButtonItem(
R.string.system_update_policy_install_windowed,
selectedPolicy == TYPE_INSTALL_WINDOWED, { selectedPolicy = TYPE_INSTALL_WINDOWED }
)
selectedPolicy == TYPE_INSTALL_WINDOWED
) { selectedPolicy = TYPE_INSTALL_WINDOWED }
RadioButtonItem(
R.string.system_update_policy_postpone,
selectedPolicy == TYPE_POSTPONE, { selectedPolicy = TYPE_POSTPONE }
)
RadioButtonItem(R.string.none, selectedPolicy == null, { selectedPolicy = null })
selectedPolicy == TYPE_POSTPONE
) { selectedPolicy = TYPE_POSTPONE }
RadioButtonItem(R.string.none, selectedPolicy == null) { selectedPolicy = null }
var windowedPolicyStart by remember { mutableStateOf("") }
var windowedPolicyEnd by remember { mutableStateOf("") }
AnimatedVisibility(selectedPolicy == 2) {
@@ -1399,7 +1319,7 @@ fun SystemUpdatePolicy(navCtrl: NavHostController) {
else -> null
}
dpm.setSystemUpdatePolicy(receiver,policy)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
) {
@@ -1447,27 +1367,30 @@ fun InstallSystemUpdate(navCtrl: NavHostController) {
Toast.makeText(context, errMsg, Toast.LENGTH_SHORT).show()
}
}
val uri by fileUriFlow.collectAsState()
var uri by remember { mutableStateOf<Uri?>(null) }
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
uri = it.data?.data
}
MyScaffold(R.string.install_system_update, 8.dp, navCtrl) {
Button(
onClick = {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.setType("application/zip")
intent.addCategory(Intent.CATEGORY_OPENABLE)
getFile.launch(intent)
getFileLauncher.launch(intent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.select_ota_package))
}
AnimatedVisibility(uri != Uri.parse("")) {
AnimatedVisibility(uri != null) {
Button(
onClick = {
val executor = Executors.newCachedThreadPool()
try {
dpm.installSystemUpdate(receiver, uri, executor, callback)
dpm.installSystemUpdate(receiver, uri!!, executor, callback)
Toast.makeText(context, R.string.start_install_system_update, Toast.LENGTH_SHORT).show()
}catch(e: Exception) {
} catch(e: Exception) {
Toast.makeText(
context,
context.getString(R.string.install_system_update_failed) + e.cause.toString(),

View File

@@ -39,12 +39,12 @@ fun UserRestriction(navCtrl:NavHostController) {
Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 16.dp))
}
Spacer(Modifier.padding(vertical = 2.dp))
FunctionItem(R.string.network_and_internet, "", R.drawable.wifi_fill0) { navCtrl.navigate("UR-Internet") }
FunctionItem(R.string.connectivity, "", R.drawable.devices_other_fill0) { navCtrl.navigate("UR-Connectivity") }
FunctionItem(R.string.applications, "", R.drawable.apps_fill0) { navCtrl.navigate("UR-Applications") }
FunctionItem(R.string.users, "", R.drawable.account_circle_fill0) { navCtrl.navigate("UR-Users") }
FunctionItem(R.string.media, "", R.drawable.volume_up_fill0) { navCtrl.navigate("UR-Media") }
FunctionItem(R.string.other, "", R.drawable.more_horiz_fill0) { navCtrl.navigate("UR-Other") }
FunctionItem(R.string.network_and_internet, icon = R.drawable.wifi_fill0) { navCtrl.navigate("UR-Internet") }
FunctionItem(R.string.connectivity, icon = R.drawable.devices_other_fill0) { navCtrl.navigate("UR-Connectivity") }
FunctionItem(R.string.applications, icon = R.drawable.apps_fill0) { navCtrl.navigate("UR-Applications") }
FunctionItem(R.string.users, icon = R.drawable.account_circle_fill0) { navCtrl.navigate("UR-Users") }
FunctionItem(R.string.media, icon = R.drawable.volume_up_fill0) { navCtrl.navigate("UR-Media") }
FunctionItem(R.string.other, icon = R.drawable.more_horiz_fill0) { navCtrl.navigate("UR-Other") }
}
}

View File

@@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Binder
import android.os.Build.VERSION
import android.os.Process
@@ -14,6 +13,8 @@ import android.os.UserHandle
import android.os.UserManager
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
@@ -40,7 +41,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
@@ -59,10 +59,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.fileUriFlow
import com.bintianqi.owndroid.getFile
import com.bintianqi.owndroid.parseTimestamp
import com.bintianqi.owndroid.toggle
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CardItem
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.FunctionItem
@@ -82,33 +80,32 @@ fun Users(navCtrl: NavHostController) {
val profileOwner = context.isProfileOwner
var dialog by remember { mutableIntStateOf(0) }
MyScaffold(R.string.users, 0.dp, navCtrl) {
FunctionItem(R.string.user_info, "", R.drawable.person_fill0) { navCtrl.navigate("UserInfo") }
FunctionItem(R.string.user_info, icon = R.drawable.person_fill0) { navCtrl.navigate("UserInfo") }
if(deviceOwner && VERSION.SDK_INT >= 28) {
FunctionItem(R.string.secondary_users, "", R.drawable.list_fill0) { dialog = 1 }
FunctionItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("UserOptions") }
FunctionItem(R.string.secondary_users, icon = R.drawable.list_fill0) { dialog = 1 }
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { navCtrl.navigate("UserOptions") }
}
if(deviceOwner) {
FunctionItem(R.string.user_operation, "", R.drawable.sync_alt_fill0) { navCtrl.navigate("UserOperation") }
FunctionItem(R.string.user_operation, icon = R.drawable.sync_alt_fill0) { navCtrl.navigate("UserOperation") }
}
if(VERSION.SDK_INT >= 24 && deviceOwner) {
FunctionItem(R.string.create_user, "", R.drawable.person_add_fill0) { navCtrl.navigate("CreateUser") }
FunctionItem(R.string.create_user, icon = R.drawable.person_add_fill0) { navCtrl.navigate("CreateUser") }
}
if(VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser) {
FunctionItem(R.string.logout_current_user, "", R.drawable.logout_fill0) { dialog = 2 }
FunctionItem(R.string.logout_current_user, icon = R.drawable.logout_fill0) { dialog = 2 }
}
if(deviceOwner || profileOwner) {
FunctionItem(R.string.change_username, "", R.drawable.edit_fill0) { navCtrl.navigate("ChangeUsername") }
FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { navCtrl.navigate("ChangeUsername") }
}
if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.change_user_icon, "", R.drawable.account_circle_fill0) { navCtrl.navigate("ChangeUserIcon") }
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { navCtrl.navigate("ChangeUserIcon") }
}
if(VERSION.SDK_INT >= 28 && deviceOwner) {
FunctionItem(R.string.user_session_msg, "", R.drawable.notifications_fill0) { navCtrl.navigate("UserSessionMessage") }
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { navCtrl.navigate("UserSessionMessage") }
}
if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.affiliation_id, "", R.drawable.id_card_fill0) { navCtrl.navigate("AffiliationID") }
FunctionItem(R.string.affiliation_id, icon = R.drawable.id_card_fill0) { navCtrl.navigate("AffiliationID") }
}
LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") }
}
if(dialog != 0 && VERSION.SDK_INT >= 28) AlertDialog(
title = { Text(stringResource(if(dialog == 1) R.string.secondary_users else R.string.logout_current_user)) },
@@ -157,7 +154,7 @@ fun UserOptions(navCtrl: NavHostController) {
val receiver = context.getReceiver()
MyScaffold(R.string.options, 0.dp, navCtrl) {
if(VERSION.SDK_INT >= 28) {
SwitchItem(R.string.enable_logout, "", null, { dpm.isLogoutEnabled }, { dpm.setLogoutEnabled(receiver, it) })
SwitchItem(R.string.enable_logout, getState = { dpm.isLogoutEnabled }, onCheckedChange = { dpm.setLogoutEnabled(receiver, it) })
}
}
}
@@ -259,9 +256,7 @@ fun UserOperation(navCtrl: NavHostController) {
Button(
onClick = {
focusMgr.clearFocus()
withUserHandle {
Toast.makeText(context, if(dpm.switchUser(receiver, it)) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
}
withUserHandle { context.showOperationResultToast(dpm.switchUser(receiver, it)) }
},
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
@@ -288,7 +283,7 @@ fun UserOperation(navCtrl: NavHostController) {
focusMgr.clearFocus()
withUserHandle {
if(dpm.removeUser(receiver, it)) {
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
idInput = ""
} else {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
@@ -313,7 +308,7 @@ fun CreateUser(navCtrl: NavHostController) {
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
var userName by remember { mutableStateOf("") }
val flags = remember { mutableStateListOf<Int>() }
var flag by remember { mutableIntStateOf(0) }
MyScaffold(R.string.create_user, 8.dp, navCtrl) {
OutlinedTextField(
value = userName,
@@ -326,28 +321,25 @@ fun CreateUser(navCtrl: NavHostController) {
Spacer(Modifier.padding(vertical = 5.dp))
CheckBoxItem(
R.string.create_user_skip_wizard,
DevicePolicyManager.SKIP_SETUP_WIZARD in flags,
{ flags.toggle(it, DevicePolicyManager.SKIP_SETUP_WIZARD) }
)
flag and DevicePolicyManager.SKIP_SETUP_WIZARD != 0
) { flag = flag xor DevicePolicyManager.SKIP_SETUP_WIZARD }
if(VERSION.SDK_INT >= 28) {
CheckBoxItem(
R.string.create_user_ephemeral_user,
DevicePolicyManager.MAKE_USER_EPHEMERAL in flags,
{ flags.toggle(it, DevicePolicyManager.MAKE_USER_EPHEMERAL) }
)
flag and DevicePolicyManager.MAKE_USER_EPHEMERAL != 0
) { flag = flag xor DevicePolicyManager.MAKE_USER_EPHEMERAL }
CheckBoxItem(
R.string.create_user_enable_all_system_app,
DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED in flags,
{ flags.toggle(it, DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED) }
)
flag and DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED != 0
) { flag = flag xor DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED }
}
var newUserHandle: UserHandle? by remember { mutableStateOf(null) }
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
focusMgr.clearFocus()
newUserHandle = dpm.createAndManageUser(receiver, userName, receiver, null, flags.sum())
Toast.makeText(context, if(newUserHandle!=null) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
newUserHandle = dpm.createAndManageUser(receiver, userName, receiver, null, flag)
context.showOperationResultToast(newUserHandle != null)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -403,7 +395,7 @@ fun AffiliationID(navCtrl: NavHostController) {
onClick = {
list.removeAll(listOf(""))
dpm.setAffiliationIds(receiver, list.toSet())
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
refreshIds()
},
modifier = Modifier.fillMaxWidth()
@@ -434,7 +426,7 @@ fun ChangeUsername(navCtrl: NavHostController) {
Button(
onClick = {
dpm.setProfileName(receiver, inputUsername)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -477,7 +469,6 @@ fun UserSessionMessage(navCtrl: NavHostController) {
onClick = {
dpm.setStartUserSessionMessage(receiver,start)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
@@ -487,7 +478,7 @@ fun UserSessionMessage(navCtrl: NavHostController) {
onClick = {
dpm.setStartUserSessionMessage(receiver,null)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
@@ -508,7 +499,7 @@ fun UserSessionMessage(navCtrl: NavHostController) {
onClick = {
dpm.setEndUserSessionMessage(receiver,end)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
@@ -518,7 +509,7 @@ fun UserSessionMessage(navCtrl: NavHostController) {
onClick = {
dpm.setEndUserSessionMessage(receiver,null)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
@@ -536,22 +527,22 @@ fun ChangeUserIcon(navCtrl: NavHostController) {
val receiver = context.getReceiver()
var getContent by remember { mutableStateOf(false) }
var bitmap by remember { mutableStateOf<Bitmap?>(null) }
val uriState by fileUriFlow.collectAsState()
LaunchedEffect(uriState) {
if(uriState == Uri.parse("")) return@LaunchedEffect
uriToStream(context, fileUriFlow.value) { stream ->
bitmap = BitmapFactory.decodeStream(stream)
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
it.data?.data?.let {
uriToStream(context, it) { stream ->
bitmap = BitmapFactory.decodeStream(stream)
}
}
}
MyScaffold(R.string.change_user_icon, 8.dp, navCtrl) {
CheckBoxItem(R.string.file_picker_instead_gallery, getContent, { getContent = it })
CheckBoxItem(R.string.file_picker_instead_gallery, getContent) { getContent = it }
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
val intent = Intent(if(getContent) Intent.ACTION_GET_CONTENT else Intent.ACTION_PICK)
if(getContent) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
getFile.launch(intent)
getFileLauncher.launch(intent)
},
modifier = Modifier.fillMaxWidth()
) {
@@ -567,7 +558,7 @@ fun ChangeUserIcon(navCtrl: NavHostController) {
Button(
onClick = {
dpm.setUserIcon(receiver, bitmap)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
context.showOperationResultToast(true)
}
) {
Text(stringResource(R.string.apply))

View File

@@ -25,7 +25,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -41,7 +40,7 @@ import kotlinx.coroutines.launch
@Composable
fun FunctionItem(
@StringRes title: Int,
desc: String,
desc: String? = null,
@DrawableRes icon: Int? = null,
operation: () -> Unit
) {
@@ -63,7 +62,7 @@ fun FunctionItem(
style = typography.titleLarge,
modifier = Modifier.padding(bottom = if(zhCN) 2.dp else 0.dp)
)
if(desc != "") { Text(text = desc, color = colorScheme.onBackground.copy(alpha = 0.8F)) }
if(desc != null) { Text(text = desc, color = colorScheme.onBackground.copy(alpha = 0.8F)) }
}
}
}
@@ -85,18 +84,16 @@ fun NavIcon(operation: () -> Unit) {
fun RadioButtonItem(
@StringRes text: Int,
selected: Boolean,
operation: () -> Unit,
textColor: Color = colorScheme.onBackground
operation: () -> Unit
) {
RadioButtonItem(stringResource(text), selected, operation, textColor)
RadioButtonItem(stringResource(text), selected, operation)
}
@Composable
fun RadioButtonItem(
text: String,
selected: Boolean,
operation: () -> Unit,
textColor: Color = colorScheme.onBackground
operation: () -> Unit
) {
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier
.fillMaxWidth()
@@ -104,7 +101,7 @@ fun RadioButtonItem(
.clickable(onClick = operation)
) {
RadioButton(selected = selected, onClick = operation)
Text(text = text, color = textColor, modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp))
Text(text = text, modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp))
}
}
@@ -112,8 +109,7 @@ fun RadioButtonItem(
fun CheckBoxItem(
@StringRes text: Int,
checked: Boolean,
operation: (Boolean) -> Unit,
textColor: Color = colorScheme.onBackground
operation: (Boolean) -> Unit
) {
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier
.fillMaxWidth()
@@ -124,7 +120,7 @@ fun CheckBoxItem(
checked = checked,
onCheckedChange = operation
)
Text(text = stringResource(text), color = textColor, modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp))
Text(text = stringResource(text), modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp))
}
}
@@ -132,26 +128,26 @@ fun CheckBoxItem(
@Composable
fun SwitchItem(
@StringRes title: Int,
desc: String,
@DrawableRes icon: Int?,
getState: ()->Boolean,
desc: String? = null,
@DrawableRes icon: Int? = null,
getState: () -> Boolean,
onCheckedChange: (Boolean)->Unit,
enable: Boolean = true,
enabled: Boolean = true,
onClickBlank: (() -> Unit)? = null,
padding: Boolean = true
) {
var state by remember { mutableStateOf(getState()) }
SwitchItem(title, desc, icon, state, { onCheckedChange(it); state = getState() }, enable, onClickBlank, padding)
SwitchItem(title, desc, icon, state, { onCheckedChange(it); state = getState() }, enabled, onClickBlank, padding)
}
@Composable
fun SwitchItem(
@StringRes title: Int,
desc: String,
@DrawableRes icon: Int?,
desc: String? = null,
@DrawableRes icon: Int? = null,
state: Boolean,
onCheckedChange: (Boolean)->Unit,
enable: Boolean = true,
onCheckedChange: (Boolean) -> Unit,
enabled: Boolean = true,
onClickBlank: (() -> Unit)? = null,
padding: Boolean = true
) {
@@ -172,14 +168,12 @@ fun SwitchItem(
)
Column(modifier = Modifier.padding(end = 60.dp, bottom = if(zhCN) 2.dp else 0.dp)) {
Text(text = stringResource(title), style = typography.titleLarge)
if(desc != "") {
Text(text = desc, color = colorScheme.onBackground.copy(alpha = 0.8F))
}
if(desc != null) Text(text = desc, color = colorScheme.onBackground.copy(alpha = 0.8F))
}
}
Switch(
checked = state, onCheckedChange = { onCheckedChange(it) },
modifier = Modifier.align(Alignment.CenterEnd), enabled = enable
modifier = Modifier.align(Alignment.CenterEnd), enabled = enabled
)
}
}