diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0ca96a2..3d7b8ee 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -91,10 +91,6 @@ android:description="@string/app_name" android:permission="android.permission.BIND_DEVICE_ADMIN"> - - diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index 576fecb..60fe69b 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -37,7 +37,6 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState 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 @@ -188,7 +187,6 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { val receiver = context.getReceiver() val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) val focusMgr = LocalFocusManager.current - val dialogStatus = remember { mutableIntStateOf(0) } val backToHome by backToHomeStateFlow.collectAsState() val lifecycleOwner = LocalLifecycleOwner.current LaunchedEffect(backToHome) { @@ -227,7 +225,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable(route = "PermissionPolicy") { PermissionPolicy(navCtrl) } composable(route = "MTEPolicy") { MTEPolicy(navCtrl) } composable(route = "NearbyStreamingPolicy") { NearbyStreamingPolicy(navCtrl) } - composable(route = "LockTaskMode") { LockTaskMode(navCtrl) } + composable(route = "LockTaskMode") { LockTaskMode(navCtrl, vm) } composable(route = "CACert") { CACert(navCtrl) } composable(route = "SecurityLogging") { SecurityLogging(navCtrl) } composable(route = "DisableAccountManagement") { DisableAccountManagement(navCtrl) } @@ -241,7 +239,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable(route = "MinWifiSecurityLevel") { WifiSecurityLevel(navCtrl) } composable(route = "WifiSsidPolicy") { WifiSsidPolicy(navCtrl) } composable(route = "PrivateDNS") { PrivateDNS(navCtrl) } - composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(navCtrl) } + composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(navCtrl, vm) } composable(route = "RecommendedGlobalProxy") { RecommendedGlobalProxy(navCtrl) } composable(route = "NetworkLog") { NetworkLogging(navCtrl) } composable(route = "WifiAuthKeypair") { WifiAuthKeypair(navCtrl) } @@ -255,7 +253,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable(route = "IntentFilter") { IntentFilter(navCtrl) } composable(route = "DeleteWorkProfile") { DeleteWorkProfile(navCtrl) } - composable(route = "Applications") { ApplicationManage(navCtrl, dialogStatus) } + composable(route = "Applications") { ApplicationManage(navCtrl, vm) } composable(route = "UserRestriction") { UserRestriction(navCtrl) } composable(route = "UR-Internet") { @@ -302,7 +300,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable(route = "ApiSettings") { ApiSettings(navCtrl) } composable(route = "About") { About(navCtrl) } - composable(route = "PackageSelector") { PackageSelector(navCtrl) } + composable(route = "PackageSelector") { PackageSelector(navCtrl, vm) } composable( route = "Authenticate", diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt index b4af388..932dc54 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt @@ -10,6 +10,8 @@ import kotlinx.coroutines.launch class MyViewModel: ViewModel() { val theme = MutableStateFlow(ThemeSettings()) + val installedPackages = mutableListOf() + val selectedPackage = MutableStateFlow("") val shizukuBinder = MutableStateFlow(null) var initialized = false diff --git a/app/src/main/java/com/bintianqi/owndroid/NotificationUtils.kt b/app/src/main/java/com/bintianqi/owndroid/NotificationUtils.kt new file mode 100644 index 0000000..13a2033 --- /dev/null +++ b/app/src/main/java/com/bintianqi/owndroid/NotificationUtils.kt @@ -0,0 +1,29 @@ +package com.bintianqi.owndroid + +import android.Manifest +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build + +/** + * ### Notification channels + * - LockTaskMode + * + * ### Notification IDs + * - 1: Stop lock task mode + */ +object NotificationUtils { + fun checkPermission(context: Context): Boolean { + return if(Build.VERSION.SDK_INT >= 33) + context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED + else false + } + fun registerChannels(context: Context) { + if(Build.VERSION.SDK_INT < 26) return + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channel = NotificationChannel("LockTaskMode", context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH) + nm.createNotificationChannel(channel) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bintianqi/owndroid/PkgSelector.kt b/app/src/main/java/com/bintianqi/owndroid/PackageSelector.kt similarity index 94% rename from app/src/main/java/com/bintianqi/owndroid/PkgSelector.kt rename to app/src/main/java/com/bintianqi/owndroid/PackageSelector.kt index 2f8e542..fef2530 100644 --- a/app/src/main/java/com/bintianqi/owndroid/PkgSelector.kt +++ b/app/src/main/java/com/bintianqi/owndroid/PackageSelector.kt @@ -54,24 +54,19 @@ import com.bintianqi.owndroid.ui.NavIcon import com.google.accompanist.drawablepainter.rememberDrawablePainter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -private data class PkgInfo( +data class PackageInfo( val pkgName: String, val label: String, val icon: Drawable, val system: Boolean ) -private val pkgs = mutableListOf() - -val selectedPackage = MutableStateFlow("") - @OptIn(ExperimentalMaterial3Api::class) @Composable -fun PackageSelector(navCtrl: NavHostController) { +fun PackageSelector(navCtrl: NavHostController, vm: MyViewModel) { val context = LocalContext.current val pm = context.packageManager val apps = pm.getInstalledApplications(0) @@ -88,9 +83,9 @@ fun PackageSelector(navCtrl: NavHostController) { show = false progress = 0 hideProgress = false - pkgs.clear() + vm.installedPackages.clear() for(pkg in apps) { - pkgs += PkgInfo( + vm.installedPackages += PackageInfo( pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm), (pkg.flags and ApplicationInfo.FLAG_SYSTEM) != 0 ) @@ -181,14 +176,14 @@ fun PackageSelector(navCtrl: NavHostController) { } } if(show) { - items(pkgs) { + items(vm.installedPackages) { if(system == it.system) { if(search != "") { if(it.pkgName.contains(search, ignoreCase = true) || it.label.contains(search, ignoreCase = true)) { - PackageItem(it, navCtrl) + PackageItem(it, navCtrl, vm) } } else { - PackageItem(it, navCtrl) + PackageItem(it, navCtrl, vm) } } } @@ -201,13 +196,13 @@ fun PackageSelector(navCtrl: NavHostController) { } } LaunchedEffect(Unit) { - if(pkgs.size == 0) { getPkgList() } + if(vm.installedPackages.isEmpty()) { getPkgList() } } } } @Composable -private fun PackageItem(pkg: PkgInfo, navCtrl: NavHostController) { +private fun PackageItem(pkg: PackageInfo, navCtrl: NavHostController, vm: MyViewModel) { val focusMgr = LocalFocusManager.current Row( verticalAlignment = Alignment.CenterVertically, @@ -215,7 +210,7 @@ private fun PackageItem(pkg: PkgInfo, navCtrl: NavHostController) { .fillMaxWidth() .clickable{ focusMgr.clearFocus() - selectedPackage.value = pkg.pkgName + vm.selectedPackage.value = pkg.pkgName navCtrl.navigateUp() } .padding(horizontal = 8.dp, vertical = 10.dp) diff --git a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt index 5823f21..bb9cb1f 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt @@ -1,9 +1,10 @@ package com.bintianqi.owndroid -import android.annotation.SuppressLint import android.app.NotificationManager +import android.app.PendingIntent import android.app.admin.DeviceAdminReceiver import android.content.BroadcastReceiver +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller.EXTRA_STATUS @@ -21,8 +22,7 @@ import android.os.Build.VERSION import android.os.PersistableBundle import android.util.Log import android.widget.Toast -import com.bintianqi.owndroid.dpm.getDPM -import com.bintianqi.owndroid.dpm.getReceiver +import androidx.core.app.NotificationCompat import com.bintianqi.owndroid.dpm.handleNetworkLogs import com.bintianqi.owndroid.dpm.handleSecurityLogs import com.bintianqi.owndroid.dpm.isDeviceAdmin @@ -35,6 +35,17 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch class Receiver : DeviceAdminReceiver() { + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + if(VERSION.SDK_INT >= 26 && intent.action == "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE") { + val dpm = getManager(context) + val receiver = ComponentName(context, this::class.java) + val packages = dpm.getLockTaskPackages(receiver) + dpm.setLockTaskPackages(receiver, arrayOf()) + dpm.setLockTaskPackages(receiver, packages) + } + } + override fun onEnabled(context: Context, intent: Intent) { super.onEnabled(context, intent) context.toggleInstallAppActivity() @@ -78,6 +89,26 @@ class Receiver : DeviceAdminReceiver() { sp.edit().putBoolean("dhizuku", false).apply() context.toggleInstallAppActivity() } + + override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) { + super.onLockTaskModeEntering(context, intent, pkg) + NotificationUtils.registerChannels(context) + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val intent = Intent(context, this::class.java).apply { action = "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE" } + val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + val builder = NotificationCompat.Builder(context, "LockTaskMode") + .setContentTitle(context.getText(R.string.lock_task_mode)) + .setSmallIcon(R.drawable.lock_fill0) + .addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build()) + .setPriority(NotificationCompat.PRIORITY_HIGH) + nm.notify(1, builder.build()) + } + + override fun onLockTaskModeExiting(context: Context, intent: Intent) { + super.onLockTaskModeExiting(context, intent) + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + nm.cancel(1) + } } val installAppDone = MutableStateFlow(false) @@ -105,16 +136,3 @@ class PackageInstallerReceiver: BroadcastReceiver() { } } } - -class StopLockTaskModeReceiver: BroadcastReceiver() { - @SuppressLint("NewApi") - override fun onReceive(context: Context, intent: Intent) { - val dpm = context.getDPM() - val receiver = context.getReceiver() - val packages = dpm.getLockTaskPackages(receiver) - dpm.setLockTaskPackages(receiver, arrayOf()) - dpm.setLockTaskPackages(receiver, packages) - val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - nm.cancel(1) - } -} diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index 879df73..efdd902 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -1,15 +1,12 @@ package com.bintianqi.owndroid -import android.Manifest import android.app.admin.DevicePolicyManager import android.content.ClipData import android.content.ClipboardManager import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.net.Uri -import android.os.Build.VERSION import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher @@ -65,7 +62,6 @@ fun writeClipBoard(context: Context, string: String):Boolean{ return true } -lateinit var requestPermission: ActivityResultLauncher lateinit var exportFile: ActivityResultLauncher var exportFilePath: String? = null var isExportingSecurityOrNetworkLogs = false @@ -83,7 +79,6 @@ fun registerActivityResult(context: ComponentActivity){ backToHomeStateFlow.value = true } } - requestPermission = context.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permissionGranted.value = it } exportFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> val intentData = result.data ?: return@registerForActivityResult val uriData = intentData.data ?: return@registerForActivityResult @@ -103,21 +98,6 @@ fun registerActivityResult(context: ComponentActivity){ } } -val permissionGranted = MutableStateFlow(null) - -suspend fun prepareForNotification(context: Context, action: ()->Unit) { - if(VERSION.SDK_INT >= 33) { - if(context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { - action() - } else { - requestPermission.launch(Manifest.permission.POST_NOTIFICATIONS) - permissionGranted.collect { if(it == true) action() } - } - } else { - action() - } -} - fun formatFileSize(bytes: Long): String { val kb = 1024 val mb = kb * 1024 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 3ebb4c7..a395680 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt @@ -50,7 +50,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.MutableIntState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -74,37 +73,37 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController 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.selectedPackage import com.bintianqi.owndroid.ui.Animations +import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.InfoCard -import com.bintianqi.owndroid.ui.Information import com.bintianqi.owndroid.ui.ListItem import com.bintianqi.owndroid.ui.NavIcon import com.bintianqi.owndroid.ui.RadioButtonItem -import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.SwitchItem import java.util.concurrent.Executors @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ApplicationManage(navCtrl:NavHostController, dialogStatus: MutableIntState) { +fun ApplicationManage(navCtrl:NavHostController, vm: MyViewModel) { val focusMgr = LocalFocusManager.current val localNavCtrl = rememberNavController() var pkgName by rememberSaveable { mutableStateOf("") } - val updatePackage by selectedPackage.collectAsState() + val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle() LaunchedEffect(updatePackage) { if(updatePackage != "") { pkgName = updatePackage - selectedPackage.value = "" + vm.selectedPackage.value = "" } } Scaffold( @@ -145,9 +144,7 @@ fun ApplicationManage(navCtrl:NavHostController, dialogStatus: MutableIntState) popEnterTransition = Animations.navHostPopEnterTransition, popExitTransition = Animations.navHostPopExitTransition ) { - composable(route = "Home") { - Home(localNavCtrl, pkgName, dialogStatus) - } + composable(route = "Home") { Home(localNavCtrl, pkgName) } composable(route = "UserControlDisabled") { UserCtrlDisabledPkg(pkgName) } composable(route = "PermissionManage") { PermissionManage(pkgName) } composable(route = "CrossProfilePackage") { CrossProfilePkg(pkgName) } @@ -160,23 +157,11 @@ fun ApplicationManage(navCtrl:NavHostController, dialogStatus: MutableIntState) composable(route = "UninstallApp") { UninstallApp(pkgName) } } } - when(dialogStatus.intValue) { - 0 -> {} - 1 -> EnableSystemAppDialog(dialogStatus, pkgName) - 2 -> ClearAppDataDialog(dialogStatus, pkgName) - 3 -> DefaultDialerAppDialog(dialogStatus, pkgName) - } - LaunchedEffect(dialogStatus.intValue) { - focusMgr.clearFocus() - } } @Composable -private fun Home( - navCtrl:NavHostController, - pkgName: String, - dialogStatus: MutableIntState -) { +private fun Home(navCtrl:NavHostController, pkgName: String) { + var dialogStatus by remember { mutableIntStateOf(0) } val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() @@ -190,7 +175,6 @@ private fun Home( hide = dpm.isApplicationHidden(receiver, pkgName) var blockUninstall by remember { mutableStateOf(false) } blockUninstall = dpm.isUninstallBlocked(receiver,pkgName) - var appControlDialog by remember { mutableStateOf(false) } var appControlAction by remember { mutableIntStateOf(0) } val focusMgr = LocalFocusManager.current val appControl: (Boolean) -> Unit = { @@ -226,20 +210,20 @@ private fun Home( title = R.string.suspend, desc = "", icon = R.drawable.block_fill0, state = suspend, onCheckedChange = { appControlAction = 1; appControl(it) }, - onClickBlank = { appControlAction = 1; appControlDialog = true } + onClickBlank = { appControlAction = 1; dialogStatus = 4 } ) } SwitchItem( title = R.string.hide, desc = stringResource(R.string.isapphidden_desc), icon = R.drawable.visibility_off_fill0, state = hide, onCheckedChange = { appControlAction = 2; appControl(it) }, - onClickBlank = { appControlAction = 2; appControlDialog = true } + onClickBlank = { appControlAction = 2; dialogStatus = 4 } ) SwitchItem( title = R.string.block_uninstall, desc = "", icon = R.drawable.delete_forever_fill0, state = blockUninstall, onCheckedChange = { appControlAction = 3; appControl(it) }, - onClickBlank = { appControlAction = 3; appControlDialog = true } + 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") } @@ -259,32 +243,124 @@ private fun Home( 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) { - if(pkgName != "") dialogStatus.intValue = 1 + if(pkgName != "") dialogStatus = 1 } if(VERSION.SDK_INT >= 28 && deviceOwner) { FunctionItem(R.string.keep_uninstalled_packages, "", R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") } } if(VERSION.SDK_INT >= 28) { FunctionItem(R.string.clear_app_storage, "", R.drawable.mop_fill0) { - if(pkgName != "") dialogStatus.intValue = 2 + 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") } if(VERSION.SDK_INT >= 34 && (deviceOwner || dpm.isOrgProfile(receiver))) { FunctionItem(R.string.set_default_dialer, "", R.drawable.call_fill0) { - if(pkgName != "") dialogStatus.intValue = 3 + if(pkgName != "") dialogStatus = 3 } } Spacer(Modifier.padding(vertical = 30.dp)) LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") } } - if(appControlDialog) { + if(dialogStatus == 1) AlertDialog( + title = { Text(stringResource(R.string.enable_system_app)) }, + text = { + Text(stringResource(R.string.enable_system_app_desc) + "\n" + pkgName) + }, + onDismissRequest = { dialogStatus = 0 }, + dismissButton = { + TextButton(onClick = { dialogStatus = 0 }) { + Text(stringResource(R.string.cancel)) + } + }, + confirmButton = { + TextButton( + onClick = { + try { + dpm.enableSystemApp(receiver, pkgName) + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + } catch(_: IllegalArgumentException) { + Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() + } + dialogStatus = 0 + } + ) { + Text(stringResource(R.string.confirm)) + } + }, + modifier = Modifier.fillMaxWidth() + ) + if(dialogStatus == 2 && VERSION.SDK_INT >= 28) AlertDialog( + title = { Text(text = stringResource(R.string.clear_app_storage)) }, + text = { + Text(stringResource(R.string.app_storage_will_be_cleared) + "\n" + pkgName) + }, + confirmButton = { + TextButton( + onClick = { + val executor = Executors.newCachedThreadPool() + val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean -> + Looper.prepare() + val toastText = + if(pkg!="") { "$pkg\n" }else{ "" } + + context.getString(R.string.clear_data) + + context.getString(if(succeed) R.string.success else R.string.failed ) + Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show() + Looper.loop() + } + dpm.clearApplicationUserData(receiver, pkgName, executor, onClear) + dialogStatus = 0 + }, + colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error) + ) { + Text(text = stringResource(R.string.clear)) + } + }, + dismissButton = { + TextButton( + onClick = { dialogStatus = 0 } + ) { + Text(text = stringResource(R.string.cancel)) + } + }, + onDismissRequest = { dialogStatus = 0 }, + modifier = Modifier.fillMaxWidth() + ) + if(dialogStatus == 3 && VERSION.SDK_INT >= 34) AlertDialog( + title = { Text(stringResource(R.string.set_default_dialer)) }, + text = { + Text(stringResource(R.string.app_will_be_default_dialer) + "\n" + pkgName) + }, + onDismissRequest = { dialogStatus = 0 }, + dismissButton = { + TextButton(onClick = { dialogStatus = 0 }) { + Text(stringResource(R.string.cancel)) + } + }, + confirmButton = { + TextButton( + onClick = { + try{ + dpm.setDefaultDialerApplication(pkgName) + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + } catch(_: IllegalArgumentException) { + Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() + } + dialogStatus = 0 + } + ) { + Text(stringResource(R.string.confirm)) + } + }, + modifier = Modifier.fillMaxWidth() + ) + if(dialogStatus == 4) { LaunchedEffect(Unit) { focusMgr.clearFocus() } AlertDialog( - onDismissRequest = { appControlDialog = false }, + onDismissRequest = { dialogStatus = 0 }, title = { Text( text = stringResource( @@ -316,7 +392,7 @@ private fun Home( TextButton( onClick = { appControl(true) - appControlDialog = false + dialogStatus = 0 } ) { Text(text = stringResource(R.string.enable)) @@ -326,7 +402,7 @@ private fun Home( TextButton( onClick = { appControl(false) - appControlDialog = false + dialogStatus = 0 } ) { Text(text = stringResource(R.string.disable)) @@ -334,6 +410,7 @@ private fun Home( } ) } + LaunchedEffect(dialogStatus) { focusMgr.clearFocus() } } @@ -697,9 +774,7 @@ private fun PermittedAccessibility(pkgName: String) { } } } - Information { - Text(stringResource(R.string.system_accessibility_always_allowed)) - } + InfoCard(R.string.system_accessibility_always_allowed) Spacer(Modifier.padding(vertical = 30.dp)) } } @@ -755,9 +830,7 @@ private fun PermittedIME(pkgName: String) { } } } - Information { - Text(stringResource(R.string.system_ime_always_allowed)) - } + InfoCard(R.string.system_ime_always_allowed) Spacer(Modifier.padding(vertical = 30.dp)) } } @@ -892,117 +965,3 @@ private fun InstallApp() { } } } - -@SuppressLint("NewApi") -@Composable -private fun ClearAppDataDialog(status: MutableIntState, pkgName: String) { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - AlertDialog( - title = { Text(text = stringResource(R.string.clear_app_storage)) }, - text = { - Text(stringResource(R.string.app_storage_will_be_cleared) + "\n" + pkgName) - }, - confirmButton = { - TextButton( - onClick = { - val executor = Executors.newCachedThreadPool() - val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean -> - Looper.prepare() - val toastText = - if(pkg!="") { "$pkg\n" }else{ "" } + - context.getString(R.string.clear_data) + - context.getString(if(succeed) R.string.success else R.string.failed ) - Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show() - Looper.loop() - } - dpm.clearApplicationUserData(receiver, pkgName, executor, onClear) - status.intValue = 0 - }, - colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error) - ) { - Text(text = stringResource(R.string.clear)) - } - }, - dismissButton = { - TextButton( - onClick = { status.intValue = 0 } - ) { - Text(text = stringResource(R.string.cancel)) - } - }, - onDismissRequest = { status.intValue = 0 }, - modifier = Modifier.fillMaxWidth() - ) -} - -@SuppressLint("NewApi") -@Composable -private fun DefaultDialerAppDialog(status: MutableIntState, pkgName: String) { - val context = LocalContext.current - val dpm = context.getDPM() - AlertDialog( - title = { Text(stringResource(R.string.set_default_dialer)) }, - text = { - Text(stringResource(R.string.app_will_be_default_dialer) + "\n" + pkgName) - }, - onDismissRequest = { status.intValue = 0 }, - dismissButton = { - TextButton(onClick = { status.intValue = 0 }) { - Text(stringResource(R.string.cancel)) - } - }, - confirmButton = { - TextButton( - onClick = { - try{ - dpm.setDefaultDialerApplication(pkgName) - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - }catch(_: IllegalArgumentException) { - Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() - } - status.intValue = 0 - } - ) { - Text(stringResource(R.string.confirm)) - } - }, - modifier = Modifier.fillMaxWidth() - ) -} - -@Composable -private fun EnableSystemAppDialog(status: MutableIntState, pkgName: String) { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - AlertDialog( - title = { Text(stringResource(R.string.enable_system_app)) }, - text = { - Text(stringResource(R.string.enable_system_app_desc) + "\n" + pkgName) - }, - onDismissRequest = { status.intValue = 0 }, - dismissButton = { - TextButton(onClick = { status.intValue = 0 }) { - Text(stringResource(R.string.cancel)) - } - }, - confirmButton = { - TextButton( - onClick = { - try { - dpm.enableSystemApp(receiver, pkgName) - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - } catch(_: IllegalArgumentException) { - Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() - } - status.intValue = 0 - } - ) { - Text(stringResource(R.string.confirm)) - } - }, - modifier = Modifier.fillMaxWidth() - ) -} 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 b7c94f0..e87d024 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -92,12 +92,12 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.core.net.toUri 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.selectedPackage import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.InfoCard @@ -400,7 +400,7 @@ fun PrivateDNS(navCtrl: NavHostController) { @SuppressLint("NewApi") @Composable -fun AlwaysOnVPNPackage(navCtrl: NavHostController) { +fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() @@ -409,11 +409,11 @@ fun AlwaysOnVPNPackage(navCtrl: NavHostController) { val focusMgr = LocalFocusManager.current val refresh = { pkgName = dpm.getAlwaysOnVpnPackage(receiver) ?: "" } LaunchedEffect(Unit) { refresh() } - val updatePackage by selectedPackage.collectAsState() + val updatePackage by vm.selectedPackage.collectAsState() LaunchedEffect(updatePackage) { - if(selectedPackage.value != "") { - pkgName = selectedPackage.value - selectedPackage.value = "" + if(updatePackage != "") { + pkgName = updatePackage + vm.selectedPackage.value = "" } } val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean -> 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 4e486ec..1941b00 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt @@ -75,7 +75,6 @@ import com.bintianqi.owndroid.ui.CardItem import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.InfoCard -import com.bintianqi.owndroid.ui.Information import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.RadioButtonItem import com.bintianqi.owndroid.yesOrNo @@ -303,7 +302,7 @@ fun ResetPasswordToken(navCtrl: NavHostController) { } } Spacer(Modifier.padding(vertical = 5.dp)) - Information{ Text(stringResource(R.string.activate_token_not_required_when_no_password)) } + InfoCard(R.string.activate_token_not_required_when_no_password) } } 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 ab9dbf0..e622287 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -3,9 +3,6 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint import android.app.ActivityOptions import android.app.AlertDialog -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.app.admin.DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK @@ -107,10 +104,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp -import androidx.core.app.NotificationCompat +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController +import com.bintianqi.owndroid.MyViewModel +import com.bintianqi.owndroid.NotificationUtils import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.StopLockTaskModeReceiver import com.bintianqi.owndroid.exportFile import com.bintianqi.owndroid.exportFilePath import com.bintianqi.owndroid.fileUriFlow @@ -118,13 +116,10 @@ import com.bintianqi.owndroid.formatFileSize import com.bintianqi.owndroid.getFile import com.bintianqi.owndroid.humanReadableDate import com.bintianqi.owndroid.isExportingSecurityOrNetworkLogs -import com.bintianqi.owndroid.prepareForNotification -import com.bintianqi.owndroid.selectedPackage import com.bintianqi.owndroid.toggle import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.InfoCard -import com.bintianqi.owndroid.ui.Information import com.bintianqi.owndroid.ui.ListItem import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.RadioButtonItem @@ -522,9 +517,7 @@ fun ChangeTimeZone(navCtrl: NavHostController) { Text(stringResource(R.string.apply)) } Spacer(Modifier.padding(vertical = 10.dp)) - Information { - Text(stringResource(R.string.disable_auto_time_zone_before_set)) - } + InfoCard(R.string.disable_auto_time_zone_before_set) } if(dialog) AlertDialog( text = { @@ -700,12 +693,11 @@ fun NearbyStreamingPolicy(navCtrl: NavHostController) { @SuppressLint("NewApi") @Composable -fun LockTaskMode(navCtrl: NavHostController) { +fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current - val coroutine = rememberCoroutineScope() var appSelectorRequest by rememberSaveable { mutableIntStateOf(0) } MyScaffold(R.string.lock_task_mode, 8.dp, navCtrl, false) { val lockTaskFeatures = remember { mutableStateListOf() } @@ -784,11 +776,11 @@ fun LockTaskMode(navCtrl: NavHostController) { lockTaskFeatures.forEach { result += it } } try { - dpm.setLockTaskFeatures(receiver,result) + dpm.setLockTaskFeatures(receiver, result) Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() } catch (e: IllegalArgumentException) { AlertDialog.Builder(context) - .setTitle("Error") + .setTitle(R.string.error) .setMessage(e.message) .setPositiveButton(R.string.confirm) { dialog, _ -> dialog.dismiss() } .show() @@ -863,11 +855,11 @@ fun LockTaskMode(navCtrl: NavHostController) { var startLockTaskApp by rememberSaveable { mutableStateOf("") } var startLockTaskActivity by rememberSaveable { mutableStateOf("") } var specifyActivity by rememberSaveable { mutableStateOf(false) } - val updatePackage by selectedPackage.collectAsState() + val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle() LaunchedEffect(updatePackage) { if(updatePackage != "") { if(appSelectorRequest == 1) inputLockTaskPkg = updatePackage else startLockTaskApp = updatePackage - selectedPackage.value = "" + vm.selectedPackage.value = "" } } Spacer(Modifier.padding(vertical = 10.dp)) @@ -906,7 +898,8 @@ fun LockTaskMode(navCtrl: NavHostController) { Button( modifier = Modifier.fillMaxWidth(), onClick = { - if(!dpm.getLockTaskPackages(receiver).contains(startLockTaskApp)) { + if(!NotificationUtils.checkPermission(context)) return@Button + if(!dpm.isLockTaskPermitted(startLockTaskApp)) { Toast.makeText(context, R.string.app_not_allowed, Toast.LENGTH_SHORT).show() return@Button } @@ -915,13 +908,7 @@ fun LockTaskMode(navCtrl: NavHostController) { val launchIntent = if(specifyActivity) Intent().setComponent(ComponentName(startLockTaskApp, startLockTaskActivity)) else packageManager.getLaunchIntentForPackage(startLockTaskApp) if (launchIntent != null) { - coroutine.launch { - prepareForNotification(context) { - sendStopLockTaskNotification(context) - context.startActivity(launchIntent, options.toBundle()) - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - } - } + context.startActivity(launchIntent, options.toBundle()) } else { Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() } @@ -1497,23 +1484,3 @@ fun InstallSystemUpdate(navCtrl: NavHostController) { InfoCard(R.string.auto_reboot_after_install_succeed) } } - -@SuppressLint("NewApi") -private fun sendStopLockTaskNotification(context: Context) { - val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (VERSION.SDK_INT >= 26) { - val channel = NotificationChannel("LockTaskMode", context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH).apply { - description = "Notification channel for stop lock task mode" - setShowBadge(false) - } - nm.createNotificationChannel(channel) - } - val intent = Intent(context, StopLockTaskModeReceiver::class.java) - val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) - val builder = NotificationCompat.Builder(context, "LockTaskMode") - .setContentTitle(context.getText(R.string.lock_task_mode)) - .setSmallIcon(R.drawable.lock_fill0) - .addAction(NotificationCompat.Action.Builder(R.drawable.lock_fill0, context.getText(R.string.stop), pendingIntent).build()) - .setPriority(NotificationCompat.PRIORITY_HIGH) - nm.notify(1, builder.build()) -} 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 c1e8582..0d8e8be 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController import com.bintianqi.owndroid.R import com.bintianqi.owndroid.writeClipBoard @@ -79,21 +78,6 @@ fun NavIcon(operation: () -> Unit) { ) } -@Composable -fun Information(content: @Composable ()->Unit) { - Column(modifier = Modifier.fillMaxWidth().padding(start = 5.dp, top = 20.dp)) { - Icon( - painter = painterResource(R.drawable.info_fill0), - contentDescription = "info", - tint = colorScheme.onBackground.copy(alpha = 0.8F) - ) - Spacer(Modifier.padding(vertical = 1.dp)) - Column(modifier = Modifier.padding(start = 2.dp)) { - content() - } - } -} - @Composable fun RadioButtonItem( @StringRes text: Int, diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4608d0d..3aa2940 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -63,6 +63,7 @@ Alias Unknown error Permission denied + Error diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 80d2991..1e42e9c 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -64,6 +64,7 @@ Alias Unknown error Permission denied + Error Etkinleştirmek İçin Tıklayın diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 74350c2..a4ab158 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -60,6 +60,7 @@ 别名 未知错误 无权限 + 错误 点击以激活 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f69941e..83de418 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -64,6 +64,7 @@ Unknown error Permission denied API + Error Click to activate