Optimize start/stop lock task mode flow

Optimize Package selector
This commit is contained in:
BinTianqi
2024-12-21 20:07:32 +08:00
parent 873896ec10
commit 84c1dff9e6
16 changed files with 216 additions and 285 deletions

View File

@@ -91,10 +91,6 @@
android:description="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN">
</receiver>
<receiver
android:name=".StopLockTaskModeReceiver"
android:description="@string/app_name">
</receiver>
<receiver
android:name=".ApiReceiver"
android:exported="true">

View File

@@ -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",

View File

@@ -10,6 +10,8 @@ import kotlinx.coroutines.launch
class MyViewModel: ViewModel() {
val theme = MutableStateFlow(ThemeSettings())
val installedPackages = mutableListOf<PackageInfo>()
val selectedPackage = MutableStateFlow("")
val shizukuBinder = MutableStateFlow<IBinder?>(null)
var initialized = false

View File

@@ -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)
}
}

View File

@@ -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<PkgInfo>()
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)

View File

@@ -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)
}
}

View File

@@ -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<String>
lateinit var exportFile: ActivityResultLauncher<Intent>
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<Boolean?>(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

View File

@@ -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()
)
}

View File

@@ -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 ->

View File

@@ -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)
}
}

View File

@@ -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<Int>() }
@@ -788,7 +780,7 @@ fun LockTaskMode(navCtrl: NavHostController) {
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()
}
}
} 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())
}

View File

@@ -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,

View File

@@ -63,6 +63,7 @@
<string name="alias">Alias</string>
<string name="unknown_error">Unknown error</string>
<string name="permission_denied">Permission denied</string>
<string name="error">Error</string>
<!--Разрешения-->

View File

@@ -64,6 +64,7 @@
<string name="alias">Alias</string>
<string name="unknown_error">Unknown error</string>
<string name="permission_denied">Permission denied</string>
<string name="error">Error</string>
<!--Permissions-->
<string name="click_to_activate">Etkinleştirmek İçin Tıklayın</string>

View File

@@ -60,6 +60,7 @@
<string name="alias">别名</string>
<string name="unknown_error">未知错误</string>
<string name="permission_denied">无权限</string>
<string name="error">错误</string>
<!--Permissions-->
<string name="click_to_activate">点击以激活</string>

View File

@@ -64,6 +64,7 @@
<string name="unknown_error">Unknown error</string>
<string name="permission_denied">Permission denied</string>
<string name="api" translatable="false">API</string>
<string name="error">Error</string>
<!--Permissions-->
<string name="click_to_activate">Click to activate</string>