diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5ca5af9..5385ad3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ + diff --git a/app/src/main/java/com/bintianqi/owndroid/InstallAppActivity.kt b/app/src/main/java/com/bintianqi/owndroid/InstallAppActivity.kt index 1c94198..86ecea4 100644 --- a/app/src/main/java/com/bintianqi/owndroid/InstallAppActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/InstallAppActivity.kt @@ -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" ) { diff --git a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt index bb9cb1f..c0d8e4c 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt @@ -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) + } } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/Settings.kt b/app/src/main/java/com/bintianqi/owndroid/Settings.kt index ce8af0b..dfc85de 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Settings.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Settings.kt @@ -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)) diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index 1d758f5..5aeb61c 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -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 -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.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 -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 { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt index a395680..0817898 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt @@ -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(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) }, diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt index 8640055..0e210a0 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt @@ -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 lateinit var addDeviceAdmin: ActivityResultLauncher 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, 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) diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt index 937c1ca..da69100 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt @@ -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 { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt index 7e6439b..a54d8ca 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -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)) } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt index 1941b00..1681d18 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt @@ -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() } + 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() ) { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt index bdb7df6..314c7ec 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -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) { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt index e622287..680f5f9 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -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() } + 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(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(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(), diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt index b50a866..80b0036 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt @@ -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") } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt index 995ad65..7ce62c0 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt @@ -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() } + 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(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)) diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt index d83e433..f8175ca 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -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 ) } }