Use pager to split Lock task mode into 3 page

Change version name to v6.3
Update workflow file
Fix a typo in Readme.md
This commit is contained in:
BinTianqi
2024-12-31 22:28:40 +08:00
parent ec6bccc0b5
commit 65bf0f75d8
15 changed files with 307 additions and 247 deletions

View File

@@ -6,8 +6,6 @@ on:
- '**.md'
tags-ignore:
- '**'
branches-ignore:
- 'master'
jobs:
build:
@@ -63,7 +61,7 @@ jobs:
upload-telegram:
name: Upload Builds
if: ${{ success() }}
if: ${{ success() && github.ref_name == 'dev' }}
runs-on: ubuntu-latest
needs:
- build

View File

@@ -31,7 +31,7 @@
- 应用:禁止安装/卸载应用...
- 用户:禁止添加/删除/切换用户...
- 媒体:禁止调整亮度、禁止调整音量...
- 其他:禁止修改账号、禁止修改语言、禁止恢复出设置、禁用调试功能...
- 其他:禁止修改账号、禁止修改语言、禁止恢复出设置、禁用调试功能...
- 用户管理
- 用户信息
- 启动/切换/停止/删除用户

View File

@@ -24,8 +24,8 @@ android {
applicationId = "com.bintianqi.owndroid"
minSdk = 21
targetSdk = 34
versionCode = 34
versionName = "6.2"
versionCode = 35
versionName = "6.3"
multiDexEnabled = false
}

View File

@@ -1,6 +1,5 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
@@ -20,6 +19,7 @@ import android.provider.Settings
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
@@ -413,7 +413,7 @@ private fun Home(navCtrl:NavHostController, pkgName: String) {
}
@SuppressLint("NewApi")
@RequiresApi(30)
@Composable
private fun UserCtrlDisabledPkg(pkgName:String) {
val context = LocalContext.current
@@ -456,7 +456,7 @@ private fun UserCtrlDisabledPkg(pkgName:String) {
}
}
@SuppressLint("NewApi")
@RequiresApi(23)
@Composable
private fun PermissionManage(pkgName: String) {
val context = LocalContext.current
@@ -556,7 +556,7 @@ private fun PermissionManage(pkgName: String) {
}
}
@SuppressLint("NewApi")
@RequiresApi(30)
@Composable
private fun CrossProfilePkg(pkgName: String) {
val context = LocalContext.current
@@ -636,7 +636,7 @@ private fun CrossProfileWidget(pkgName: String) {
}
}
@SuppressLint("NewApi")
@RequiresApi(34)
@Composable
private fun CredentialManagePolicy(pkgName: String) {
val context = LocalContext.current
@@ -822,7 +822,7 @@ private fun PermittedIME(pkgName: String) {
}
}
@SuppressLint("NewApi")
@RequiresApi(28)
@Composable
private fun KeepUninstalledApp(pkgName: String) {
val context = LocalContext.current

View File

@@ -1,7 +1,6 @@
package com.bintianqi.owndroid.dpm
import android.accounts.Account
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ALLOW_OFFLINE
@@ -21,6 +20,7 @@ import android.os.Build.VERSION
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -59,8 +59,8 @@ import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CardItem
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.CopyTextButton
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.yesOrNo
@@ -160,7 +160,7 @@ fun CreateWorkProfile(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(30)
@Composable
fun OrgOwnedProfile(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -180,7 +180,7 @@ fun OrgOwnedProfile(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(30)
@Composable
fun SuspendPersonalApp(navCtrl: NavHostController) {
val context = LocalContext.current

View File

@@ -1,7 +1,6 @@
package com.bintianqi.owndroid.dpm
import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF
import android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC
@@ -51,6 +50,7 @@ import android.telephony.data.ApnSetting.PROTOCOL_UNSTRUCTURED
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
@@ -717,7 +717,7 @@ private fun AddNetwork(wifiConfig: WifiConfiguration? = null, navCtrl: NavHostCo
}
}
@SuppressLint("NewApi")
@RequiresApi(33)
@Composable
fun WifiSecurityLevel(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -743,7 +743,7 @@ fun WifiSecurityLevel(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(33)
@Composable
fun WifiSsidPolicy(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -817,7 +817,7 @@ fun WifiSsidPolicy(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(29)
@Composable
fun PrivateDNS(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -889,7 +889,7 @@ fun PrivateDNS(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(24)
@Composable
fun AlwaysOnVPNPackage(navCtrl: NavHostController, vm: MyViewModel) {
val context = LocalContext.current
@@ -1056,7 +1056,7 @@ fun RecommendedGlobalProxy(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(26)
@Composable
fun NetworkLogging(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -1112,7 +1112,7 @@ fun NetworkLogging(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(31)
@Composable
fun WifiAuthKeypair(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -1154,7 +1154,7 @@ fun WifiAuthKeypair(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(33)
@Composable
fun PreferentialNetworkService(navCtrl: NavHostController) {
val focusMgr = LocalFocusManager.current
@@ -1306,7 +1306,7 @@ fun PreferentialNetworkService(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(28)
@Composable
fun OverrideAPN(navCtrl: NavHostController) {
val context = LocalContext.current

View File

@@ -33,6 +33,7 @@ import android.content.Intent
import android.os.Build.VERSION
import android.os.UserManager
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -232,7 +233,7 @@ fun PasswordInfo(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(26)
@Composable
fun ResetPasswordToken(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -412,7 +413,7 @@ fun ResetPassword(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(31)
@Composable
fun PasswordComplexity(navCtrl: NavHostController) {
val context = LocalContext.current

View File

@@ -12,6 +12,7 @@ import android.os.Build.VERSION
import android.os.RemoteException
import android.os.UserManager
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
@@ -234,7 +235,7 @@ private fun toggleDhizukuMode(status: Boolean, context: Context) {
}
}
@SuppressLint("NewApi")
@RequiresApi(24)
@Composable
fun LockScreenInfo(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -496,7 +497,7 @@ fun DeviceInfo(navCtrl: NavHostController) {
)
}
@SuppressLint("NewApi")
@RequiresApi(24)
@Composable
fun SupportMessages(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -574,7 +575,7 @@ fun SupportMessages(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(28)
@Composable
fun TransferOwnership(navCtrl: NavHostController) {
val context = LocalContext.current

View File

@@ -50,8 +50,10 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -60,9 +62,11 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.Add
@@ -77,14 +81,18 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Slider
import androidx.compose.material3.Switch
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TimePicker
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
@@ -123,6 +131,7 @@ import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.ListItem
import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.RadioButtonItem
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.uriToStream
@@ -133,9 +142,9 @@ import java.io.ByteArrayOutputStream
import java.util.Date
import java.util.TimeZone
import java.util.concurrent.Executors
import kotlin.collections.addAll
import kotlin.math.roundToLong
@SuppressLint("NewApi")
@Composable
fun SystemManage(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -198,7 +207,7 @@ fun SystemManage(navCtrl: NavHostController) {
FunctionItem(R.string.wipe_data, icon = R.drawable.device_reset_fill0) { navCtrl.navigate("WipeData") }
}
}
if(dialog != 0) AlertDialog(
if(dialog != 0 &&VERSION.SDK_INT >= 24) AlertDialog(
onDismissRequest = { dialog = 0 },
title = { Text(stringResource(if(dialog == 1) R.string.reboot else R.string.bug_report)) },
text = { Text(stringResource(if(dialog == 1) R.string.info_reboot else R.string.confirm_bug_report)) },
@@ -448,7 +457,7 @@ fun HardwareMonitor(navCtrl: NavHostController) {
}
@OptIn(ExperimentalMaterial3Api::class)
@SuppressLint("NewApi")
@RequiresApi(28)
@Composable
fun ChangeTime(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -558,7 +567,7 @@ fun ChangeTime(navCtrl: NavHostController) {
)
}
@SuppressLint("NewApi")
@RequiresApi(28)
@Composable
fun ChangeTimeZone(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -621,7 +630,7 @@ fun ChangeTimeZone(navCtrl: NavHostController) {
)
}
@SuppressLint("NewApi")
@RequiresApi(23)
@Composable
fun PermissionPolicy(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -646,7 +655,7 @@ fun PermissionPolicy(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(34)
@Composable
fun MTEPolicy(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -674,7 +683,7 @@ fun MTEPolicy(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(31)
@Composable
fun NearbyStreamingPolicy(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -739,206 +748,261 @@ fun NearbyStreamingPolicy(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(28)
@Composable
fun LockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
val coroutine = rememberCoroutineScope()
val pagerState = rememberPagerState { 3 }
var tabIndex by remember { mutableIntStateOf(0) }
tabIndex = pagerState.targetPage
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.lock_task_mode)) },
navigationIcon = { NavIcon { navCtrl.navigateUp() } }
)
}
) { paddingValues ->
Column(
modifier = Modifier.fillMaxSize().padding(paddingValues)
) {
TabRow(tabIndex) {
Tab(
tabIndex == 0, onClick = { coroutine.launch { pagerState.animateScrollToPage(0) } },
text = { Text(stringResource(R.string.start)) }
)
Tab(
tabIndex == 1, onClick = { coroutine.launch { pagerState.animateScrollToPage(1) } },
text = { Text(stringResource(R.string.applications)) }
)
Tab(
tabIndex == 2, onClick = { coroutine.launch { pagerState.animateScrollToPage(2) } },
text = { Text(stringResource(R.string.features)) }
)
}
HorizontalPager(pagerState, verticalAlignment = Alignment.Top) { page ->
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 8.dp, end = 8.dp, bottom = 80.dp)
) {
if(page == 0) StartLockTaskMode(navCtrl, vm)
else if(page == 1) LockTaskPackages(navCtrl, vm)
else LockTaskFeatures()
}
}
}
}
}
@RequiresApi(28)
@Composable
private fun ColumnScope.StartLockTaskMode(navCtrl: NavHostController, vm: MyViewModel) {
val context = LocalContext.current
val dpm = context.getDPM()
val focusMgr = LocalFocusManager.current
var startLockTaskApp by rememberSaveable { mutableStateOf("") }
var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
var specifyActivity by rememberSaveable { mutableStateOf(false) }
val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle()
LaunchedEffect(updatePackage) {
if(updatePackage != "") {
startLockTaskApp = updatePackage
vm.selectedPackage.value = ""
}
}
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = startLockTaskApp,
onValueChange = { startLockTaskApp = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
trailingIcon = {
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = {
focusMgr.clearFocus()
navCtrl.navigate("PackageSelector")
})
.padding(3.dp))
},
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
CheckBoxItem(R.string.specify_activity, specifyActivity) { specifyActivity = it }
AnimatedVisibility(specifyActivity) {
OutlinedTextField(
value = startLockTaskActivity,
onValueChange = { startLockTaskActivity = it },
label = { Text("Activity") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth().padding(bottom = 5.dp)
)
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
if(!NotificationUtils.checkPermission(context)) return@Button
if(!dpm.isLockTaskPermitted(startLockTaskApp)) {
Toast.makeText(context, R.string.app_not_allowed, Toast.LENGTH_SHORT).show()
return@Button
}
val options = ActivityOptions.makeBasic().setLockTaskEnabled(true)
val packageManager = context.packageManager
val launchIntent = if(specifyActivity) Intent().setComponent(ComponentName(startLockTaskApp, startLockTaskActivity))
else packageManager.getLaunchIntentForPackage(startLockTaskApp)
if (launchIntent != null) {
context.startActivity(launchIntent, options.toBundle())
} else {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
}
) {
Text(stringResource(R.string.start))
}
InfoCard(R.string.info_start_lock_task_mode)
}
@RequiresApi(26)
@Composable
private fun ColumnScope.LockTaskPackages(navCtrl: NavHostController, vm: MyViewModel) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
var appSelectorRequest by rememberSaveable { mutableIntStateOf(0) }
MyScaffold(R.string.lock_task_mode, 8.dp, navCtrl, false) {
var lockTaskFeatures by remember { mutableIntStateOf(0) }
var custom by rememberSaveable { mutableStateOf(false) }
fun refreshFeature() {
lockTaskFeatures = dpm.getLockTaskFeatures(receiver)
custom = lockTaskFeatures != 0
val lockTaskPackages = remember { mutableStateListOf<String>() }
var input by rememberSaveable { mutableStateOf("") }
val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle()
LaunchedEffect(updatePackage) {
if(updatePackage != "") {
input = updatePackage
vm.selectedPackage.value = ""
}
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 }
AnimatedVisibility(custom) {
Column {
CheckBoxItem(
R.string.ltf_sys_info,
lockTaskFeatures and LOCK_TASK_FEATURE_SYSTEM_INFO != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_SYSTEM_INFO }
CheckBoxItem(
R.string.ltf_notifications,
lockTaskFeatures and LOCK_TASK_FEATURE_NOTIFICATIONS != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_NOTIFICATIONS }
CheckBoxItem(
R.string.ltf_home,
lockTaskFeatures and LOCK_TASK_FEATURE_HOME != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_HOME }
CheckBoxItem(
R.string.ltf_overview,
lockTaskFeatures and LOCK_TASK_FEATURE_OVERVIEW != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_OVERVIEW }
CheckBoxItem(
R.string.ltf_global_actions,
lockTaskFeatures and LOCK_TASK_FEATURE_GLOBAL_ACTIONS != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_GLOBAL_ACTIONS }
CheckBoxItem(
R.string.ltf_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,
lockTaskFeatures and LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK != 0
) { lockTaskFeatures = lockTaskFeatures xor LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK }
}
}
}
LaunchedEffect(Unit) { lockTaskPackages.addAll(dpm.getLockTaskPackages(receiver)) }
Spacer(Modifier.padding(vertical = 5.dp))
if(lockTaskPackages.isEmpty()) Text(text = stringResource(R.string.none))
for(i in lockTaskPackages) {
ListItem(i) { lockTaskPackages -= i }
}
OutlinedTextField(
value = input,
onValueChange = { input = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
trailingIcon = {
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = {
focusMgr.clearFocus()
navCtrl.navigate("PackageSelector")
})
.padding(3.dp))
},
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
lockTaskPackages.add(input)
input = ""
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
try {
dpm.setLockTaskFeatures(receiver, lockTaskFeatures)
context.showOperationResultToast(true)
} catch (e: IllegalArgumentException) {
AlertDialog.Builder(context)
.setTitle(R.string.error)
.setMessage(e.message)
.setPositiveButton(R.string.confirm) { dialog, _ -> dialog.dismiss() }
.show()
}
refreshFeature()
}
lockTaskPackages.remove(input)
input = ""
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.apply))
Text(stringResource(R.string.remove))
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
dpm.setLockTaskPackages(receiver, lockTaskPackages.toTypedArray())
context.showOperationResultToast(true)
}
) {
Text(stringResource(R.string.apply))
}
InfoCard(R.string.info_lock_task_packages)
}
val lockTaskPackages = remember { mutableStateListOf<String>() }
var inputLockTaskPkg by rememberSaveable { mutableStateOf("") }
LaunchedEffect(Unit) { lockTaskPackages.addAll(dpm.getLockTaskPackages(receiver)) }
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.lock_task_packages), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
Column(modifier = Modifier.animateContentSize()) {
if(lockTaskPackages.isEmpty()) Text(text = stringResource(R.string.none))
for(i in lockTaskPackages) {
ListItem(i) { lockTaskPackages -= i }
@RequiresApi(28)
@Composable
private fun ColumnScope.LockTaskFeatures() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var flags by remember { mutableIntStateOf(0) }
var custom by rememberSaveable { mutableStateOf(false) }
fun refresh() {
flags = dpm.getLockTaskFeatures(receiver)
custom = flags != 0
}
LaunchedEffect(Unit) { refresh() }
Spacer(Modifier.padding(vertical = 5.dp))
RadioButtonItem(R.string.disable_all, !custom) { custom = false }
RadioButtonItem(R.string.custom, custom) { custom = true }
AnimatedVisibility(custom) {
Column {
CheckBoxItem(
R.string.ltf_sys_info,
flags and LOCK_TASK_FEATURE_SYSTEM_INFO != 0
) { flags = flags xor LOCK_TASK_FEATURE_SYSTEM_INFO }
CheckBoxItem(
R.string.ltf_notifications,
flags and LOCK_TASK_FEATURE_NOTIFICATIONS != 0
) { flags = flags xor LOCK_TASK_FEATURE_NOTIFICATIONS }
CheckBoxItem(
R.string.ltf_home,
flags and LOCK_TASK_FEATURE_HOME != 0
) { flags = flags xor LOCK_TASK_FEATURE_HOME }
CheckBoxItem(
R.string.ltf_overview,
flags and LOCK_TASK_FEATURE_OVERVIEW != 0
) { flags = flags xor LOCK_TASK_FEATURE_OVERVIEW }
CheckBoxItem(
R.string.ltf_global_actions,
flags and LOCK_TASK_FEATURE_GLOBAL_ACTIONS != 0
) { flags = flags xor LOCK_TASK_FEATURE_GLOBAL_ACTIONS }
CheckBoxItem(
R.string.ltf_keyguard,
flags and LOCK_TASK_FEATURE_KEYGUARD != 0
) { flags = flags xor LOCK_TASK_FEATURE_KEYGUARD }
if(VERSION.SDK_INT >= 30) {
CheckBoxItem(
R.string.ltf_block_activity_start_in_task,
flags and LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK != 0
) { flags = flags xor LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK }
}
}
OutlinedTextField(
value = inputLockTaskPkg,
onValueChange = { inputLockTaskPkg = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
trailingIcon = {
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = {
focusMgr.clearFocus()
appSelectorRequest = 1
navCtrl.navigate("PackageSelector")
})
.padding(3.dp))
},
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
lockTaskPackages.add(inputLockTaskPkg)
inputLockTaskPkg = ""
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = {
lockTaskPackages.remove(inputLockTaskPkg)
inputLockTaskPkg = ""
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
dpm.setLockTaskPackages(receiver, lockTaskPackages.toTypedArray())
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
try {
dpm.setLockTaskFeatures(receiver, flags)
context.showOperationResultToast(true)
} catch (e: IllegalArgumentException) {
AlertDialog.Builder(context)
.setTitle(R.string.error)
.setMessage(e.message)
.setPositiveButton(R.string.confirm) { dialog, _ -> dialog.dismiss() }
.show()
}
) {
Text(stringResource(R.string.apply))
refresh()
}
InfoCard(R.string.info_lock_task_packages)
var startLockTaskApp by rememberSaveable { mutableStateOf("") }
var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
var specifyActivity by rememberSaveable { mutableStateOf(false) }
val updatePackage by vm.selectedPackage.collectAsStateWithLifecycle()
LaunchedEffect(updatePackage) {
if(updatePackage != "") {
if(appSelectorRequest == 1) inputLockTaskPkg = updatePackage else startLockTaskApp = updatePackage
vm.selectedPackage.value = ""
}
}
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.start_lock_task_mode), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = startLockTaskApp,
onValueChange = { startLockTaskApp = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
trailingIcon = {
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = {
focusMgr.clearFocus()
appSelectorRequest = 2
navCtrl.navigate("PackageSelector")
})
.padding(3.dp))
},
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
CheckBoxItem(R.string.specify_activity, specifyActivity) { specifyActivity = it }
AnimatedVisibility(specifyActivity) {
OutlinedTextField(
value = startLockTaskActivity,
onValueChange = { startLockTaskActivity = it },
label = { Text("Activity") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth().padding(bottom = 5.dp)
)
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
if(!NotificationUtils.checkPermission(context)) return@Button
if(!dpm.isLockTaskPermitted(startLockTaskApp)) {
Toast.makeText(context, R.string.app_not_allowed, Toast.LENGTH_SHORT).show()
return@Button
}
val options = ActivityOptions.makeBasic().setLockTaskEnabled(true)
val packageManager = context.packageManager
val launchIntent = if(specifyActivity) Intent().setComponent(ComponentName(startLockTaskApp, startLockTaskActivity))
else packageManager.getLaunchIntentForPackage(startLockTaskApp)
if (launchIntent != null) {
context.startActivity(launchIntent, options.toBundle())
} else {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
}
) {
Text(stringResource(R.string.start))
}
InfoCard(R.string.info_start_lock_task_mode)
) {
Text(stringResource(R.string.apply))
}
}
@@ -1016,7 +1080,7 @@ fun CACert(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(24)
@Composable
fun SecurityLogging(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -1152,7 +1216,7 @@ fun DisableAccountManagement(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(30)
@Composable
fun FRPPolicy(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -1244,7 +1308,6 @@ fun FRPPolicy(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@Composable
fun WipeData(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -1305,7 +1368,10 @@ fun WipeData(navCtrl: NavHostController) {
},
text = {
Text(
text = stringResource(if(userManager.isSystemUser) R.string.wipe_data_warning else R.string.info_wipe_data_in_managed_user),
text = stringResource(
if(VERSION.SDK_INT >= 23 && userManager.isSystemUser) R.string.wipe_data_warning
else R.string.info_wipe_data_in_managed_user
),
color = colorScheme.error
)
},
@@ -1322,7 +1388,7 @@ fun WipeData(navCtrl: NavHostController) {
TextButton(
onClick = {
if(silent && VERSION.SDK_INT >= 29) { flag = flag or WIPE_SILENTLY }
if(wipeDevice) {
if(wipeDevice && VERSION.SDK_INT >= 34) {
dpm.wipeDevice(flag)
} else {
if(VERSION.SDK_INT >= 28 && reason != "") {

View File

@@ -1,10 +1,10 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.os.Build.VERSION
import android.os.UserManager
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
@@ -48,7 +48,7 @@ fun UserRestriction(navCtrl:NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(24)
@Composable
fun UserRestrictionItem(restriction: Restriction) {
val context = LocalContext.current

View File

@@ -1,6 +1,5 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
@@ -15,6 +14,7 @@ import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
@@ -299,7 +299,7 @@ fun UserOperation(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(24)
@Composable
fun CreateUser(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -350,7 +350,7 @@ fun CreateUser(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(26)
@Composable
fun AffiliationID(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -441,7 +441,7 @@ fun ChangeUsername(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(28)
@Composable
fun UserSessionMessage(navCtrl: NavHostController) {
val context = LocalContext.current
@@ -519,7 +519,7 @@ fun UserSessionMessage(navCtrl: NavHostController) {
}
}
@SuppressLint("NewApi")
@RequiresApi(23)
@Composable
fun ChangeUserIcon(navCtrl: NavHostController) {
val context = LocalContext.current

View File

@@ -65,6 +65,9 @@
<string name="permission_denied">Permission denied</string>
<string name="error">Error</string>
<string name="status">Status</string>
<string name="edit">Edit</string>
<string name="overview">Overview</string>
<string name="features">Features</string>
<!--Разрешения-->
@@ -170,10 +173,7 @@
<string name="nearby_notification_streaming">Политика потоковой передачи уведомлений Nearby</string>
<string name="enable_if_secure_enough">Только для одной управляемой учетной записи</string>
<string name="lock_task_mode">Режим закрепления задачи</string>
<string name="lock_task_feature">Функция закрепления задачи</string>
<string name="lock_task_packages">Закрепленные пакеты</string>
<string name="specify_activity">Указать Acitvity</string>
<string name="start_lock_task_mode">Запустить режим закрепления задачи</string>
<string name="app_not_allowed">Приложение не разрешено</string>
<string name="disable_all">Отключить все</string>
<!--ltf: функция закрепления задачи-->
@@ -553,7 +553,6 @@
<string name="disable_keyguard_features_unredacted_notification">Отключить неотредактированные уведомления</string>
<string name="disable_keyguard_features_trust_agents">Отключить доверенные агенты</string>
<string name="disable_keyguard_features_fingerprint">Отключить отпечаток пальца</string>
<string name="disable_keyguard_features_remote_input">Отключить удаленный ввод</string>
<string name="disable_keyguard_features_face">Отключить распознавание лица</string>
<string name="disable_keyguard_features_iris">Отключить сканер радужной оболочки</string>
<string name="disable_keyguard_features_biometrics">Отключить биометрию</string>

View File

@@ -66,6 +66,9 @@
<string name="permission_denied">Permission denied</string>
<string name="error">Error</string>
<string name="status">Status</string>
<string name="edit">Edit</string>
<string name="overview">Overview</string>
<string name="features">Features</string>
<!--Permissions-->
<string name="click_to_activate">Etkinleştirmek İçin Tıklayın</string>
@@ -171,10 +174,7 @@
<string name="nearby_notification_streaming">Yakındaki bildirim akış politikası</string>
<string name="enable_if_secure_enough">Yalnızca aynı yönetilen hesap</string>
<string name="lock_task_mode">Lock task mode</string> <!--TODO-->
<string name="lock_task_feature">Görev kilitleme özelliği</string>
<string name="lock_task_packages">Lock task packages</string> <!--TODO-->
<string name="specify_activity">Specify Activity</string> <!--TODO-->
<string name="start_lock_task_mode">Start lock task mode</string> <!--TODO-->
<string name="app_not_allowed">App is not allowed</string> <!--TODO-->
<string name="disable_all">Hepsini devre dışı bırak</string>
@@ -549,7 +549,6 @@
<string name="disable_keyguard_features_unredacted_notification">Sansürsüz bildirimleri devre dışı bırak</string>
<string name="disable_keyguard_features_trust_agents">Güvenilir ajanları devre dışı bırak</string>
<string name="disable_keyguard_features_fingerprint">Parmak izini devre dışı bırak</string>
<string name="disable_keyguard_features_remote_input">Uzaktan girişleri devre dışı bırak</string>
<string name="disable_keyguard_features_face">Yüz tanımayı devre dışı bırak</string>
<string name="disable_keyguard_features_iris">İris tarayıcısını devre dışı bırak</string>
<string name="disable_keyguard_features_biometrics">Biyometrikleri devre dışı bırak</string>

View File

@@ -62,6 +62,9 @@
<string name="permission_denied">无权限</string>
<string name="error">错误</string>
<string name="status">状态</string>
<string name="edit">编辑</string>
<string name="overview">概览</string>
<string name="features">功能</string>
<!--Permissions-->
<string name="click_to_activate">点击以激活</string>
@@ -163,10 +166,7 @@
<string name="nearby_notification_streaming">附近通知传输</string>
<string name="enable_if_secure_enough">在足够安全时启用</string>
<string name="lock_task_mode">锁定任务模式</string>
<string name="lock_task_feature">锁定任务功能</string>
<string name="lock_task_packages">锁定任务应用</string>
<string name="specify_activity">指定Activity</string>
<string name="start_lock_task_mode">启动锁定任务模式</string>
<string name="app_not_allowed">应用未被允许</string>
<string name="disable_all">禁用全部</string>
<string name="ltf_sys_info">允许状态栏信息</string>
@@ -537,7 +537,6 @@
<string name="disable_keyguard_features_unredacted_notification">禁用未经编辑的通知</string>
<string name="disable_keyguard_features_trust_agents">禁用可信代理</string>
<string name="disable_keyguard_features_fingerprint">禁用指纹解锁</string>
<string name="disable_keyguard_features_remote_input">禁止远程输入(弃用)</string>
<string name="disable_keyguard_features_face">禁用人脸解锁</string>
<string name="disable_keyguard_features_iris">禁用虹膜解锁(?)</string>
<string name="disable_keyguard_features_biometrics">禁用生物识别</string>

View File

@@ -69,6 +69,7 @@
<string name="status">Status</string>
<string name="edit">Edit</string>
<string name="overview">Overview</string>
<string name="features">Features</string>
<!--Permissions-->
<string name="click_to_activate">Click to activate</string>
@@ -177,10 +178,7 @@
<string name="nearby_notification_streaming">Nearby notification streaming policy</string>
<string name="enable_if_secure_enough">Same managed account only</string>
<string name="lock_task_mode">Lock task mode</string>
<string name="lock_task_feature">Lock task feature</string>
<string name="lock_task_packages">Lock task packages</string>
<string name="specify_activity">Specify Activity</string>
<string name="start_lock_task_mode">Start lock task mode</string>
<string name="app_not_allowed">App is not allowed</string>
<string name="disable_all">Disable all</string>
<!--ltf: lock task feature-->
@@ -556,7 +554,6 @@
<string name="disable_keyguard_features_unredacted_notification">Disable unredacted notification</string>
<string name="disable_keyguard_features_trust_agents">Disable trust agents</string>
<string name="disable_keyguard_features_fingerprint">Disable fingerprint</string>
<string name="disable_keyguard_features_remote_input">Disable remote input</string>
<string name="disable_keyguard_features_face">Disable face</string>
<string name="disable_keyguard_features_iris">Disable iris</string>
<string name="disable_keyguard_features_biometrics">Disable biometrics</string>