ViewModel refactoring: Work profile part

close #169
Improve UI, fix #172
fix #165
This commit is contained in:
BinTianqi
2025-09-27 15:57:45 +08:00
parent 5928dbb657
commit a9452ac14e
11 changed files with 275 additions and 146 deletions

View File

@@ -38,6 +38,7 @@
<action android:name="android.app.action.PROVISION_MANAGED_PROFILE"/> <action android:name="android.app.action.PROVISION_MANAGED_PROFILE"/>
<action android:name="android.app.action.MANAGED_PROFILE_PROVISIONED"/> <action android:name="android.app.action.MANAGED_PROFILE_PROVISIONED"/>
<action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/> <action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/>
<action android:name="android.app.action.CHECK_POLICY_COMPLIANCE"/>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>

View File

@@ -417,11 +417,22 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<AddApnSetting> { AddApnSettingScreen(it.arguments?.getParcelable("setting"), ::navigateUp) } composable<AddApnSetting> { AddApnSettingScreen(it.arguments?.getParcelable("setting"), ::navigateUp) }
composable<WorkProfile> { WorkProfileScreen(::navigateUp, ::navigate) } composable<WorkProfile> { WorkProfileScreen(::navigateUp, ::navigate) }
composable<OrganizationOwnedProfile> { OrganizationOwnedProfileScreen(::navigateUp) } composable<OrganizationOwnedProfile> {
composable<CreateWorkProfile> { CreateWorkProfileScreen(::navigateUp) } OrganizationOwnedProfileScreen(vm::activateOrgProfileByShizuku, ::navigateUp)
composable<SuspendPersonalApp> { SuspendPersonalAppScreen(::navigateUp) } }
composable<CrossProfileIntentFilter> { CrossProfileIntentFilterScreen(::navigateUp) } composable<CreateWorkProfile> {
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(::navigateUp) } CreateWorkProfileScreen(vm::createWorkProfile, ::navigateUp)
}
composable<SuspendPersonalApp> {
SuspendPersonalAppScreen(
vm::getPersonalAppsSuspendedReason, vm::setPersonalAppsSuspended,
vm::getProfileMaxTimeOff, vm::setProfileMaxTimeOff, ::navigateUp
)
}
composable<CrossProfileIntentFilter> {
CrossProfileIntentFilterScreen(vm::addCrossProfileIntentFilter, ::navigateUp)
}
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(vm::wipeData, ::navigateUp) }
composable<ApplicationsList> { composable<ApplicationsList> {
val canSwitchView = (it.toRoute() as ApplicationsList).canSwitchView val canSwitchView = (it.toRoute() as ApplicationsList).canSwitchView

View File

@@ -1,5 +1,6 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.accounts.Account
import android.app.ActivityOptions import android.app.ActivityOptions
import android.app.Application import android.app.Application
import android.app.PendingIntent import android.app.PendingIntent
@@ -35,13 +36,17 @@ import com.bintianqi.owndroid.Privilege.DPM
import com.bintianqi.owndroid.dpm.ACTIVATE_DEVICE_OWNER_COMMAND import com.bintianqi.owndroid.dpm.ACTIVATE_DEVICE_OWNER_COMMAND
import com.bintianqi.owndroid.dpm.AppStatus import com.bintianqi.owndroid.dpm.AppStatus
import com.bintianqi.owndroid.dpm.CaCertInfo import com.bintianqi.owndroid.dpm.CaCertInfo
import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions
import com.bintianqi.owndroid.dpm.DelegatedAdmin import com.bintianqi.owndroid.dpm.DelegatedAdmin
import com.bintianqi.owndroid.dpm.DeviceAdmin import com.bintianqi.owndroid.dpm.DeviceAdmin
import com.bintianqi.owndroid.dpm.FrpPolicyInfo import com.bintianqi.owndroid.dpm.FrpPolicyInfo
import com.bintianqi.owndroid.dpm.HardwareProperties import com.bintianqi.owndroid.dpm.HardwareProperties
import com.bintianqi.owndroid.dpm.IntentFilterDirection
import com.bintianqi.owndroid.dpm.IntentFilterOptions
import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo
import com.bintianqi.owndroid.dpm.SystemOptionsStatus import com.bintianqi.owndroid.dpm.SystemOptionsStatus
import com.bintianqi.owndroid.dpm.SystemUpdatePolicyInfo import com.bintianqi.owndroid.dpm.SystemUpdatePolicyInfo
import com.bintianqi.owndroid.dpm.activateOrgProfileCommand
import com.bintianqi.owndroid.dpm.delegatedScopesList import com.bintianqi.owndroid.dpm.delegatedScopesList
import com.bintianqi.owndroid.dpm.getPackageInstaller import com.bintianqi.owndroid.dpm.getPackageInstaller
import com.bintianqi.owndroid.dpm.handlePrivilegeChange import com.bintianqi.owndroid.dpm.handlePrivilegeChange
@@ -294,7 +299,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
fun setCmPackage(name: String, status: Boolean) { fun setCmPackage(name: String, status: Boolean) {
cmPackages.update { list -> cmPackages.update { list ->
if (status) list + getAppInfo(name) else list.dropWhile { it.name == name } if (status) list + getAppInfo(name) else list.filter { it.name != name }
} }
} }
@RequiresApi(34) @RequiresApi(34)
@@ -315,7 +320,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
fun setPimPackage(name: String, status: Boolean) { fun setPimPackage(name: String, status: Boolean) {
pimPackages.update { packages -> pimPackages.update { packages ->
if (status) packages + getAppInfo(name) else packages.dropWhile { it.name == name } if (status) packages + getAppInfo(name) else packages.filter { it.name != name }
} }
} }
fun setPimPolicy(allowAll: Boolean): Boolean { fun setPimPolicy(allowAll: Boolean): Boolean {
@@ -335,7 +340,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
fun setPasPackage(name: String, status: Boolean) { fun setPasPackage(name: String, status: Boolean) {
pasPackages.update { packages -> pasPackages.update { packages ->
if (status) packages + getAppInfo(name) else packages.dropWhile { it.name == name } if (status) packages + getAppInfo(name) else packages.filter { it.name != name }
} }
} }
fun setPasPolicy(allowAll: Boolean): Boolean { fun setPasPolicy(allowAll: Boolean): Boolean {
@@ -1022,6 +1027,81 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
false false
} }
} }
fun createWorkProfile(options: CreateWorkProfileOptions): Intent {
val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
if (VERSION.SDK_INT >= 23) {
intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
MyAdminComponent
)
} else {
intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
application.packageName
)
}
if (options.migrateAccount && VERSION.SDK_INT >= 22) {
intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE,
Account(options.accountName, options.accountType)
)
if (VERSION.SDK_INT >= 26) {
intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION,
options.keepAccount
)
}
}
if (VERSION.SDK_INT >= 24) {
intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION,
options.skipEncrypt
)
}
if (VERSION.SDK_INT >= 33) {
intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_ALLOW_OFFLINE, options.offline)
}
return intent
}
fun activateOrgProfileByShizuku(callback: (Boolean) -> Unit) {
viewModelScope.launch {
var succeed = false
useShizuku(application) { service ->
val result = IUserService.Stub.asInterface(service).execute(activateOrgProfileCommand)
succeed = result?.getInt("code", -1) == 0
callback(succeed)
}
if (succeed) Privilege.updateStatus()
}
}
@RequiresApi(30)
fun getPersonalAppsSuspendedReason(): Int {
return DPM.getPersonalAppsSuspendedReasons(DAR)
}
@RequiresApi(30)
fun setPersonalAppsSuspended(suspended: Boolean) {
DPM.setPersonalAppsSuspended(DAR, suspended)
}
@RequiresApi(30)
fun getProfileMaxTimeOff(): Long {
return DPM.getManagedProfileMaximumTimeOff(DAR)
}
@RequiresApi(30)
fun setProfileMaxTimeOff(time: Long) {
DPM.setManagedProfileMaximumTimeOff(DAR, time)
}
fun addCrossProfileIntentFilter(options: IntentFilterOptions) {
val filter = IntentFilter(options.action)
if (options.category.isNotEmpty()) filter.addCategory(options.category)
if (options.mimeType.isNotEmpty()) filter.addDataType(options.mimeType)
val flags = when(options.direction) {
IntentFilterDirection.ToManaged -> DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED
IntentFilterDirection.ToParent -> DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
IntentFilterDirection.Both -> DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED or
DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
}
DPM.addCrossProfileIntentFilter(DAR, filter, flags)
}
} }
data class ThemeSettings( data class ThemeSettings(

View File

@@ -57,7 +57,9 @@ fun useShizuku(context: Context, action: (IBinder?) -> Unit) {
} else { } else {
Sui.init(context.packageName) Sui.init(context.packageName)
fun requestPermissionResultListener(requestCode: Int, grantResult: Int) { fun requestPermissionResultListener(requestCode: Int, grantResult: Int) {
if(grantResult != PackageManager.PERMISSION_GRANTED) { if (grantResult == PackageManager.PERMISSION_GRANTED) {
Shizuku.bindUserService(getShizukuArgs(context), connection)
} else {
context.popToast(R.string.permission_denied) context.popToast(R.string.permission_denied)
} }
Shizuku.removeRequestPermissionResultListener(::requestPermissionResultListener) Shizuku.removeRequestPermissionResultListener(::requestPermissionResultListener)

View File

@@ -273,6 +273,7 @@ fun ApplicationDetailsScreen(
) )
if(VERSION.SDK_INT >= 28) FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { dialog = 1 } if(VERSION.SDK_INT >= 28) FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { dialog = 1 }
FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 } FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 }
Spacer(Modifier.height(40.dp))
} }
if(dialog == 1 && VERSION.SDK_INT >= 28) if(dialog == 1 && VERSION.SDK_INT >= 28)
ClearAppStorageDialog(packageName, vm::clearAppData) { dialog = 0 } ClearAppStorageDialog(packageName, vm::clearAppData) { dialog = 0 }
@@ -604,6 +605,7 @@ fun CredentialManagerPolicyScreen(
) { ) {
Text(stringResource(R.string.apply)) Text(stringResource(R.string.apply))
} }
Spacer(Modifier.height(40.dp))
} }
} }
} }
@@ -658,6 +660,7 @@ fun PermittedAsAndImPackages(
} }
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))
Notes(note, HorizontalPadding) Notes(note, HorizontalPadding)
Spacer(Modifier.height(40.dp))
} }
} }
} }
@@ -767,6 +770,7 @@ fun PackageFunctionScreen(
Text(stringResource(R.string.add)) Text(stringResource(R.string.add))
} }
if (notes != null) Notes(notes, HorizontalPadding) if (notes != null) Notes(notes, HorizontalPadding)
Spacer(Modifier.height(40.dp))
} }
} }
} }

View File

@@ -1262,6 +1262,7 @@ private fun LockTaskPackages(
Text(stringResource(R.string.add)) Text(stringResource(R.string.add))
} }
Notes(R.string.info_lock_task_packages) Notes(R.string.info_lock_task_packages)
Spacer(Modifier.height(40.dp))
} }
} }
} }

View File

@@ -1,22 +1,9 @@
package com.bintianqi.owndroid.dpm package com.bintianqi.owndroid.dpm
import android.accounts.Account import android.app.admin.DevicePolicyManager
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
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION
import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION
import android.app.admin.DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
import android.app.admin.DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED
import android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED
import android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT
import android.app.admin.DevicePolicyManager.WIPE_EUICC import android.app.admin.DevicePolicyManager.WIPE_EUICC
import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE
import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.os.Binder import android.os.Binder
import android.os.Build.VERSION import android.os.Build.VERSION
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
@@ -24,6 +11,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -33,8 +21,13 @@ import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@@ -45,6 +38,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@@ -55,18 +49,19 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.IUserService import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.MyAdminComponent
import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.Privilege
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.popToast
import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.CircularProgressDialog
import com.bintianqi.owndroid.ui.ExpandExposedTextFieldIcon
import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem
import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.Notes import com.bintianqi.owndroid.ui.Notes
import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.useShizuku import com.bintianqi.owndroid.yesOrNo
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable object WorkProfile @Serializable object WorkProfile
@@ -86,14 +81,20 @@ fun WorkProfileScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
} }
} }
data class CreateWorkProfileOptions(
val skipEncrypt: Boolean, val offline: Boolean, val migrateAccount: Boolean,
val accountName: String, val accountType: String, val keepAccount: Boolean
)
@Serializable object CreateWorkProfile @Serializable object CreateWorkProfile
@Composable @Composable
fun CreateWorkProfileScreen(onNavigateUp: () -> Unit) { fun CreateWorkProfileScreen(
val context = LocalContext.current createIntent: (CreateWorkProfileOptions) -> Intent, onNavigateUp: () -> Unit
) {
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { }
MyScaffold(R.string.create_work_profile, onNavigateUp) { MyScaffold(R.string.create_work_profile, onNavigateUp, 0.dp) {
var skipEncrypt by remember { mutableStateOf(false) } var skipEncrypt by remember { mutableStateOf(false) }
var offlineProvisioning by remember { mutableStateOf(true) } var offlineProvisioning by remember { mutableStateOf(true) }
var migrateAccount by remember { mutableStateOf(false) } var migrateAccount by remember { mutableStateOf(false) }
@@ -101,7 +102,7 @@ fun CreateWorkProfileScreen(onNavigateUp: () -> Unit) {
var migrateAccountType by remember { mutableStateOf("") } var migrateAccountType by remember { mutableStateOf("") }
var keepAccount by remember { mutableStateOf(true) } var keepAccount by remember { mutableStateOf(true) }
if (VERSION.SDK_INT >= 22) { if (VERSION.SDK_INT >= 22) {
CheckBoxItem(R.string.migrate_account, migrateAccount) { migrateAccount = it } FullWidthCheckBoxItem(R.string.migrate_account, migrateAccount) { migrateAccount = it }
AnimatedVisibility(migrateAccount) { AnimatedVisibility(migrateAccount) {
val fr = FocusRequester() val fr = FocusRequester()
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
@@ -110,47 +111,40 @@ fun CreateWorkProfileScreen(onNavigateUp: () -> Unit) {
label = { Text(stringResource(R.string.account_name)) }, label = { Text(stringResource(R.string.account_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions { fr.requestFocus() }, keyboardActions = KeyboardActions { fr.requestFocus() },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)
) )
OutlinedTextField( OutlinedTextField(
value = migrateAccountType, onValueChange = { migrateAccountType = it }, value = migrateAccountType, onValueChange = { migrateAccountType = it },
label = { Text(stringResource(R.string.account_type)) }, label = { Text(stringResource(R.string.account_type)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { focusMgr.clearFocus() }, keyboardActions = KeyboardActions { focusMgr.clearFocus() },
modifier = Modifier.fillMaxWidth().focusRequester(fr) modifier = Modifier
.fillMaxWidth()
.padding(horizontal = HorizontalPadding)
.focusRequester(fr)
) )
if(VERSION.SDK_INT >= 26) { if(VERSION.SDK_INT >= 26) {
CheckBoxItem(R.string.keep_account, keepAccount) { keepAccount = it } FullWidthCheckBoxItem(R.string.keep_account, keepAccount) { keepAccount = it }
} }
} }
} }
} }
if(VERSION.SDK_INT >= 24) CheckBoxItem(R.string.skip_encryption, skipEncrypt) { skipEncrypt = it } if (VERSION.SDK_INT >= 24) FullWidthCheckBoxItem(
if(VERSION.SDK_INT >= 33) CheckBoxItem(R.string.offline_provisioning, offlineProvisioning) { offlineProvisioning = it } R.string.skip_encryption, skipEncrypt
) { skipEncrypt = it }
if (VERSION.SDK_INT >= 33) FullWidthCheckBoxItem(
R.string.offline_provisioning, offlineProvisioning
) { offlineProvisioning = it }
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
Button( Button(
onClick = { onClick = {
try { val intent = createIntent(CreateWorkProfileOptions(
val intent = Intent(ACTION_PROVISION_MANAGED_PROFILE) skipEncrypt, offlineProvisioning, migrateAccount, migrateAccountName,
if(VERSION.SDK_INT >= 23) { migrateAccountType, keepAccount
intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, MyAdminComponent) ))
} else {
intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, context.packageName)
}
if(migrateAccount && VERSION.SDK_INT >= 22) {
intent.putExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, Account(migrateAccountName, migrateAccountType))
if(VERSION.SDK_INT >= 26) {
intent.putExtra(EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION, keepAccount)
}
}
if(VERSION.SDK_INT >= 24) { intent.putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION, skipEncrypt) }
if(VERSION.SDK_INT >= 33) { intent.putExtra(EXTRA_PROVISIONING_ALLOW_OFFLINE, offlineProvisioning) }
launcher.launch(intent) launcher.launch(intent)
} catch(_: ActivityNotFoundException) {
context.popToast(R.string.unsupported)
}
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)
) { ) {
Text(stringResource(R.string.create)) Text(stringResource(R.string.create))
} }
@@ -161,18 +155,19 @@ fun CreateWorkProfileScreen(onNavigateUp: () -> Unit) {
@RequiresApi(30) @RequiresApi(30)
@Composable @Composable
fun OrganizationOwnedProfileScreen(onNavigateUp: () -> Unit) { fun OrganizationOwnedProfileScreen(
onActivate: ((Boolean) -> Unit) -> Unit, onNavigateUp: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
var activating by remember { mutableStateOf(false) }
var dialog by remember { mutableStateOf(false) } var dialog by remember { mutableStateOf(false) }
MyScaffold(R.string.org_owned_work_profile, onNavigateUp) { MyScaffold(R.string.org_owned_work_profile, onNavigateUp) {
Button({ Button({
useShizuku(context) { service -> activating = true
val result = IUserService.Stub.asInterface(service).execute(activateOrgProfileCommand) onActivate {
if (result?.getInt("code", -1) == 0) { activating = false
context.showOperationResultToast(true) context.showOperationResultToast(it)
} else { if (it) onNavigateUp()
context.showOperationResultToast(false)
}
} }
}) { }) {
Text(stringResource(R.string.shizuku)) Text(stringResource(R.string.shizuku))
@@ -189,6 +184,7 @@ fun OrganizationOwnedProfileScreen(onNavigateUp: () -> Unit) {
}, },
onDismissRequest = { dialog = false } onDismissRequest = { dialog = false }
) )
if (activating) CircularProgressDialog { }
} }
} }
@@ -199,41 +195,46 @@ val activateOrgProfileCommand = "dpm mark-profile-owner-on-organization-owned-de
@RequiresApi(30) @RequiresApi(30)
@Composable @Composable
fun SuspendPersonalAppScreen(onNavigateUp: () -> Unit) { fun SuspendPersonalAppScreen(
getSuspendedReasons: () -> Int, setSuspended: (Boolean) -> Unit, getMaxTime: () -> Long,
setMaxTime: (Long) -> Unit, onNavigateUp: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
var suspend by remember { mutableStateOf(Privilege.DPM.getPersonalAppsSuspendedReasons(Privilege.DAR) != PERSONAL_APPS_NOT_SUSPENDED) } var reason by remember { mutableIntStateOf(DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED) }
var time by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
reason = getSuspendedReasons()
time = getMaxTime().toString()
}
MyScaffold(R.string.suspend_personal_app, onNavigateUp) { MyScaffold(R.string.suspend_personal_app, onNavigateUp) {
SwitchItem(R.string.suspend_personal_app, state = suspend, SwitchItem(R.string.suspend_personal_app, state = reason != 0,
onCheckedChange = { onCheckedChange = {
Privilege.DPM.setPersonalAppsSuspended(Privilege.DAR, it) setSuspended(it)
suspend = Privilege.DPM.getPersonalAppsSuspendedReasons(Privilege.DAR) != PERSONAL_APPS_NOT_SUSPENDED reason = if (it) DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY
else DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED
}, padding = false }, padding = false
) )
var time by remember { mutableStateOf("") }
time = Privilege.DPM.getManagedProfileMaximumTimeOff(Privilege.DAR).toString()
Spacer(Modifier.padding(vertical = 10.dp)) Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.profile_max_time_off), style = typography.titleLarge) Text(text = stringResource(R.string.profile_max_time_off), style = typography.titleLarge)
Text(text = stringResource(R.string.profile_max_time_out_desc)) Text(text = stringResource(R.string.profile_max_time_out_desc))
Text( Text(stringResource(
text = stringResource(
R.string.personal_app_suspended_because_timeout, R.string.personal_app_suspended_because_timeout,
Privilege.DPM.getPersonalAppsSuspendedReasons(Privilege.DAR) == PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT stringResource((reason == DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT).yesOrNo)
) ))
)
OutlinedTextField( OutlinedTextField(
value = time, onValueChange = { time=it }, modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp), value = time, onValueChange = { time=it }, modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
label = { Text(stringResource(R.string.time_unit_ms)) }, label = { Text(stringResource(R.string.time_unit_ms)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() }) keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() })
) )
Text(text = stringResource(R.string.cannot_less_than_72_hours))
Button( Button(
onClick = { onClick = {
Privilege.DPM.setManagedProfileMaximumTimeOff(Privilege.DAR, time.toLong()) setMaxTime(time.toLong())
context.showOperationResultToast(true) context.showOperationResultToast(true)
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth(),
enabled = time.toLongOrNull() != null
) { ) {
Text(stringResource(R.string.apply)) Text(stringResource(R.string.apply))
} }
@@ -241,14 +242,33 @@ fun SuspendPersonalAppScreen(onNavigateUp: () -> Unit) {
} }
} }
data class IntentFilterOptions(
val action: String, val category: String, val mimeType: String,
val direction: IntentFilterDirection
)
enum class IntentFilterDirection(val text: Int) {
ToParent(R.string.work_to_personal), ToManaged(R.string.personal_to_work),
Both(R.string.both_direction)
}
@Serializable object CrossProfileIntentFilter @Serializable object CrossProfileIntentFilter
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun CrossProfileIntentFilterScreen(onNavigateUp: () -> Unit) { fun CrossProfileIntentFilterScreen(
addFilter: (IntentFilterOptions) -> Unit,
onNavigateUp: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
MyScaffold(R.string.intent_filter, onNavigateUp) {
var action by remember { mutableStateOf("") } var action by remember { mutableStateOf("") }
var customCategory by remember { mutableStateOf(false) }
var category by remember { mutableStateOf("") }
var customMimeType by remember { mutableStateOf(false) }
var mimeType by remember { mutableStateOf("") }
var dropdown by remember { mutableStateOf(false) }
var direction by remember { mutableStateOf(IntentFilterDirection.Both) }
MyScaffold(R.string.intent_filter, onNavigateUp) {
OutlinedTextField( OutlinedTextField(
value = action, onValueChange = { action = it }, value = action, onValueChange = { action = it },
label = { Text("Action") }, label = { Text("Action") },
@@ -256,32 +276,61 @@ fun CrossProfileIntentFilterScreen(onNavigateUp: () -> Unit) {
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() }), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(Modifier.padding(vertical = 5.dp)) Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Button( Checkbox(customCategory, {
onClick = { customCategory = it
Privilege.DPM.addCrossProfileIntentFilter(Privilege.DAR, IntentFilter(action), FLAG_PARENT_CAN_ACCESS_MANAGED) category = ""
context.showOperationResultToast(true) })
}, OutlinedTextField(
modifier = Modifier.fillMaxWidth() category, { category = it }, Modifier.fillMaxWidth(),
) { label = { Text("Category") }, enabled = customCategory
Text(stringResource(R.string.add_intent_filter_work_to_personal)) )
}
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Checkbox(customMimeType, {
customMimeType = it
mimeType = ""
})
OutlinedTextField(
mimeType, { mimeType = it }, Modifier.fillMaxWidth(),
label = { Text("MIME type") }, enabled = customMimeType
)
}
ExposedDropdownMenuBox(dropdown, { dropdown = it }, Modifier.padding(vertical = 5.dp)) {
OutlinedTextField(
stringResource(direction.text), {},
Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(),
label = { Text(stringResource(R.string.direction)) }, readOnly = true,
trailingIcon = { ExpandExposedTextFieldIcon(dropdown) }
)
ExposedDropdownMenu(dropdown, { dropdown = false }) {
IntentFilterDirection.entries.forEach {
DropdownMenuItem({ Text(stringResource(it.text)) }, {
direction = it
dropdown = false
})
}
}
} }
Button( Button(
onClick = { {
Privilege.DPM.addCrossProfileIntentFilter(Privilege.DAR, IntentFilter(action), FLAG_MANAGED_CAN_ACCESS_PARENT) addFilter(IntentFilterOptions(
action, category, mimeType, direction
))
context.showOperationResultToast(true) context.showOperationResultToast(true)
}, },
modifier = Modifier.fillMaxWidth() Modifier.fillMaxWidth(),
enabled = action.isNotBlank() && (!customCategory || category.isNotBlank()) &&
(!customMimeType || mimeType.isNotBlank())
) { ) {
Text(stringResource(R.string.add_intent_filter_personal_to_work)) Text(stringResource(R.string.add))
} }
Spacer(Modifier.padding(vertical = 2.dp))
Button( Button(
onClick = { onClick = {
Privilege.DPM.clearCrossProfileIntentFilters(Privilege.DAR) Privilege.DPM.clearCrossProfileIntentFilters(Privilege.DAR)
context.showOperationResultToast(true) context.showOperationResultToast(true)
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)
) { ) {
Text(stringResource(R.string.clear_cross_profile_filters)) Text(stringResource(R.string.clear_cross_profile_filters))
} }
@@ -292,28 +341,34 @@ fun CrossProfileIntentFilterScreen(onNavigateUp: () -> Unit) {
@Serializable object DeleteWorkProfile @Serializable object DeleteWorkProfile
@Composable @Composable
fun DeleteWorkProfileScreen(onNavigateUp: () -> Unit) { fun DeleteWorkProfileScreen(
deleteProfile: (Boolean, Int, String) -> Unit, onNavigateUp: () -> Unit
) {
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
var flag by remember { mutableIntStateOf(0) } var flags by remember { mutableIntStateOf(0) }
var warning by remember { mutableStateOf(false) } var warning by remember { mutableStateOf(false) }
var silent by remember { mutableStateOf(false) }
var reason by remember { mutableStateOf("") } var reason by remember { mutableStateOf("") }
MyScaffold(R.string.delete_work_profile, onNavigateUp) { MyScaffold(R.string.delete_work_profile, onNavigateUp) {
CheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { flag = flag xor WIPE_EXTERNAL_STORAGE } CheckBoxItem(R.string.wipe_external_storage, flags and WIPE_EXTERNAL_STORAGE != 0) {
if(VERSION.SDK_INT >= 28) CheckBoxItem(R.string.wipe_euicc, flag and WIPE_EUICC != 0) { flag = flag xor WIPE_EUICC } flags = flags xor WIPE_EXTERNAL_STORAGE
CheckBoxItem(R.string.wipe_silently, silent) { silent = it } }
AnimatedVisibility(!silent && VERSION.SDK_INT >= 28) { if(VERSION.SDK_INT >= 28) CheckBoxItem(R.string.wipe_euicc, flags and WIPE_EUICC != 0) {
OutlinedTextField( flags = flags xor WIPE_EUICC
}
CheckBoxItem(R.string.wipe_silently, flags and DevicePolicyManager.WIPE_SILENTLY != 0) {
flags = flags xor DevicePolicyManager.WIPE_SILENTLY
reason = ""
}
if (VERSION.SDK_INT >= 28) OutlinedTextField(
value = reason, onValueChange = { reason = it }, value = reason, onValueChange = { reason = it },
label = { Text(stringResource(R.string.reason)) }, label = { Text(stringResource(R.string.reason)) },
enabled = flags and DevicePolicyManager.WIPE_SILENTLY == 0,
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp) modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
) )
}
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
Button( Button(
onClick = { onClick = {
focusMgr.clearFocus() focusMgr.clearFocus()
silent = reason == ""
warning = true warning = true
}, },
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
@@ -323,7 +378,6 @@ fun DeleteWorkProfileScreen(onNavigateUp: () -> Unit) {
} }
} }
if (warning) { if (warning) {
LaunchedEffect(Unit) { silent = reason == "" }
AlertDialog( AlertDialog(
title = { title = {
Text(text = stringResource(R.string.warning), color = colorScheme.error) Text(text = stringResource(R.string.warning), color = colorScheme.error)
@@ -335,11 +389,7 @@ fun DeleteWorkProfileScreen(onNavigateUp: () -> Unit) {
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
if(VERSION.SDK_INT >= 28 && !silent) { deleteProfile(false, flags, reason)
Privilege.DPM.wipeData(flag, reason)
} else {
Privilege.DPM.wipeData(flag)
}
}, },
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error) colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
) { ) {

View File

@@ -75,7 +75,6 @@
<string name="profile_owner">Владелец профиля</string> <string name="profile_owner">Владелец профиля</string>
<string name="device_owner">Владелец устройства</string> <string name="device_owner">Владелец устройства</string>
<string name="delegated_admins">Делегированные администраторы</string> <string name="delegated_admins">Делегированные администраторы</string>
<string name="delegated_scope">делегированные возможности</string>
<string name="manage_application_restrictions">Управление ограничениями для приложений</string> <string name="manage_application_restrictions">Управление ограничениями для приложений</string>
<string name="manage_certificates">Управление сертификатами</string> <string name="manage_certificates">Управление сертификатами</string>
<string name="select_keychain_certificates">Выберите сертификат связки ключей</string> <string name="select_keychain_certificates">Выберите сертификат связки ключей</string>
@@ -101,7 +100,6 @@
<string name="disable_account_management">Отключить управление аккаунтами</string> <string name="disable_account_management">Отключить управление аккаунтами</string>
<string name="account_type">Тип аккаунта</string> <string name="account_type">Тип аккаунта</string>
<string name="transfer_ownership">Передача прав владения</string> <string name="transfer_ownership">Передача прав владения</string>
<string name="target_component_name">Имя целевого компонента</string>
<string name="lock_screen_info">Информация на экране блокировки</string> <string name="lock_screen_info">Информация на экране блокировки</string>
<string name="support_messages">Сообщение поддержки</string> <string name="support_messages">Сообщение поддержки</string>
<string name="short_support_msg">Краткое сообщение</string> <string name="short_support_msg">Краткое сообщение</string>
@@ -212,8 +210,6 @@
<string name="update_received_time">Время получения обновления: %1$s</string> <string name="update_received_time">Время получения обновления: %1$s</string>
<string name="install_system_update">Установить системное обновление</string> <string name="install_system_update">Установить системное обновление</string>
<string name="select_ota_package" tools:ignore="TypographyEllipsis">Выберите OTA-пакет...</string> <string name="select_ota_package" tools:ignore="TypographyEllipsis">Выберите OTA-пакет...</string>
<string name="start_install_system_update">Начать установку системного обновления</string>
<string name="install_system_update_failed">Установка системного обновления не удалась: </string>
<string name="battery_low">Низкий заряд батареи</string> <string name="battery_low">Низкий заряд батареи</string>
<string name="update_file_invalid">Файл обновления недействителен</string> <string name="update_file_invalid">Файл обновления недействителен</string>
<string name="incorrect_os_ver">Неверная версия ОС</string> <string name="incorrect_os_ver">Неверная версия ОС</string>
@@ -339,10 +335,7 @@
<string name="profile_max_time_off">Максимальное время отключения</string> <string name="profile_max_time_off">Максимальное время отключения</string>
<string name="profile_max_time_out_desc">Личные приложения будут приостановлены после закрытия рабочего профиля на указанное время. 0 означает отсутствие ограничения.</string> <string name="profile_max_time_out_desc">Личные приложения будут приостановлены после закрытия рабочего профиля на указанное время. 0 означает отсутствие ограничения.</string>
<string name="personal_app_suspended_because_timeout">Личное приложение приостановлено по причине: %1$s</string> <string name="personal_app_suspended_because_timeout">Личное приложение приостановлено по причине: %1$s</string>
<string name="cannot_less_than_72_hours">Не может быть меньше 72 часов</string>
<string name="intent_filter">Фильтр намерений</string> <string name="intent_filter">Фильтр намерений</string>
<string name="add_intent_filter_work_to_personal">Добавить (из рабочего в личный)</string>
<string name="add_intent_filter_personal_to_work">Добавить (из личного в рабочий)</string>
<string name="clear_cross_profile_filters">Очистить все фильтры</string> <string name="clear_cross_profile_filters">Очистить все фильтры</string>
<string name="org_id">Идентификатор организации</string> <string name="org_id">Идентификатор организации</string>
<string name="length_6_to_64">Длина должна быть от 6 до 64 символов</string> <string name="length_6_to_64">Длина должна быть от 6 до 64 символов</string>

View File

@@ -76,7 +76,6 @@
<string name="profile_owner">Profil Sahibi</string> <string name="profile_owner">Profil Sahibi</string>
<string name="device_owner">Cihaz Sahibi</string> <string name="device_owner">Cihaz Sahibi</string>
<string name="delegated_admins">Yetkilendirilmiş Yöneticiler</string> <string name="delegated_admins">Yetkilendirilmiş Yöneticiler</string>
<string name="delegated_scope">Yetkilendirilmiş Kapsam</string>
<string name="manage_application_restrictions">Uygulama Kısıtlamalarını Yönet</string> <string name="manage_application_restrictions">Uygulama Kısıtlamalarını Yönet</string>
<string name="manage_certificates">Sertifikaları Yönet</string> <string name="manage_certificates">Sertifikaları Yönet</string>
<string name="select_keychain_certificates">KeyChain Sertifikasını Seç</string> <string name="select_keychain_certificates">KeyChain Sertifikasını Seç</string>
@@ -102,7 +101,6 @@
<string name="disable_account_management">Hesap Yönetimini Devre Dışı Bırak</string> <string name="disable_account_management">Hesap Yönetimini Devre Dışı Bırak</string>
<string name="account_type">Hesap Türü</string> <string name="account_type">Hesap Türü</string>
<string name="transfer_ownership">Sahipliği Devret</string> <string name="transfer_ownership">Sahipliği Devret</string>
<string name="target_component_name">Hedef Bileşen Adı</string>
<string name="lock_screen_info">Kilit Ekranı Bilgisi</string> <string name="lock_screen_info">Kilit Ekranı Bilgisi</string>
<string name="support_messages">Destek Mesajları</string> <string name="support_messages">Destek Mesajları</string>
<string name="short_support_msg">Kısa Mesaj</string> <string name="short_support_msg">Kısa Mesaj</string>
@@ -243,8 +241,6 @@
<string name="install_system_update">Sistem Güncellemesini Yükle</string> <string name="install_system_update">Sistem Güncellemesini Yükle</string>
<string name="select_ota_package" tools:ignore="TypographyEllipsis">OTA paketini seç...</string> <string name="select_ota_package" tools:ignore="TypographyEllipsis">OTA paketini seç...</string>
<string name="start_install_system_update">Sistem güncellemesini yüklemeye başla</string>
<string name="install_system_update_failed">"Sistem güncellemesi yüklenemedi: "</string>
<string name="battery_low">Pil seviyesi düşük</string> <string name="battery_low">Pil seviyesi düşük</string>
<string name="update_file_invalid">Güncelleme dosyası geçersiz</string> <string name="update_file_invalid">Güncelleme dosyası geçersiz</string>
<string name="incorrect_os_ver">Yanlış işletim sistemi sürümü</string> <string name="incorrect_os_ver">Yanlış işletim sistemi sürümü</string>
@@ -365,10 +361,7 @@
<string name="profile_max_time_off">Maksimum Kapalı Kalma Süresi</string> <string name="profile_max_time_off">Maksimum Kapalı Kalma Süresi</string>
<string name="profile_max_time_out_desc">İş profili kapatıldıktan sonra kişisel uygulamalar bu süre boyunca askıya alınacak. 0, sınırsız anlamına gelir.</string> <string name="profile_max_time_out_desc">İş profili kapatıldıktan sonra kişisel uygulamalar bu süre boyunca askıya alınacak. 0, sınırsız anlamına gelir.</string>
<string name="personal_app_suspended_because_timeout">Kişisel uygulama şu nedenle askıya alındı: %1$s</string> <string name="personal_app_suspended_because_timeout">Kişisel uygulama şu nedenle askıya alındı: %1$s</string>
<string name="cannot_less_than_72_hours">72 saatten az olamaz</string>
<string name="intent_filter">Niyet Filtresi</string> <string name="intent_filter">Niyet Filtresi</string>
<string name="add_intent_filter_work_to_personal">Ekle (işten kişisel profile)</string>
<string name="add_intent_filter_personal_to_work">Ekle (kişisel profilden işe)</string>
<string name="clear_cross_profile_filters">Tüm filtreleri temizle</string> <string name="clear_cross_profile_filters">Tüm filtreleri temizle</string>
<string name="org_id">Kurum Kimliği</string> <string name="org_id">Kurum Kimliği</string>
<string name="length_6_to_64">Uzunluk 6 ile 64 karakter arasında olmalıdır</string> <string name="length_6_to_64">Uzunluk 6 ile 64 karakter arasında olmalıdır</string>

View File

@@ -74,7 +74,6 @@
<string name="profile_owner">Profile owner</string> <string name="profile_owner">Profile owner</string>
<string name="device_owner">Device owner</string> <string name="device_owner">Device owner</string>
<string name="delegated_admins">委托管理员</string> <string name="delegated_admins">委托管理员</string>
<string name="delegated_scope">委托作用域</string>
<string name="manage_application_restrictions">管理应用限制</string> <string name="manage_application_restrictions">管理应用限制</string>
<string name="manage_certificates">管理证书</string> <string name="manage_certificates">管理证书</string>
<string name="select_keychain_certificates">选择密钥链证书</string> <string name="select_keychain_certificates">选择密钥链证书</string>
@@ -99,7 +98,6 @@
<string name="disable_account_management">禁用账号管理</string> <string name="disable_account_management">禁用账号管理</string>
<string name="account_type">账号类型</string> <string name="account_type">账号类型</string>
<string name="transfer_ownership">转移所有权</string> <string name="transfer_ownership">转移所有权</string>
<string name="target_component_name">目标组件名</string>
<string name="lock_screen_info">锁屏提示信息</string> <string name="lock_screen_info">锁屏提示信息</string>
<string name="support_messages">提供支持的消息</string> <string name="support_messages">提供支持的消息</string>
<string name="short_support_msg">提供支持的短消息</string> <string name="short_support_msg">提供支持的短消息</string>
@@ -221,8 +219,6 @@
<string name="update_received_time">系统更新接收时间: %1$s</string> <string name="update_received_time">系统更新接收时间: %1$s</string>
<string name="install_system_update">安装系统更新</string> <string name="install_system_update">安装系统更新</string>
<string name="select_ota_package" tools:ignore="TypographyEllipsis">选择OTA包...</string> <string name="select_ota_package" tools:ignore="TypographyEllipsis">选择OTA包...</string>
<string name="start_install_system_update">开始安装系统更新</string>
<string name="install_system_update_failed">安装系统更新失败:</string>
<string name="battery_low">电量低</string> <string name="battery_low">电量低</string>
<string name="update_file_invalid">OTA包无效</string> <string name="update_file_invalid">OTA包无效</string>
<string name="incorrect_os_ver">系统版本错误</string> <string name="incorrect_os_ver">系统版本错误</string>
@@ -343,10 +339,11 @@
<string name="profile_max_time_off">资料关闭时间</string> <string name="profile_max_time_off">资料关闭时间</string>
<string name="profile_max_time_out_desc">工作资料处于关闭状态的时间达到该限制后会挂起个人应用0为无限制</string> <string name="profile_max_time_out_desc">工作资料处于关闭状态的时间达到该限制后会挂起个人应用0为无限制</string>
<string name="personal_app_suspended_because_timeout">个人应用已经因此挂起:%1$s</string> <string name="personal_app_suspended_because_timeout">个人应用已经因此挂起:%1$s</string>
<string name="cannot_less_than_72_hours">不能少于72小时</string>
<string name="intent_filter">Intent过滤器</string> <string name="intent_filter">Intent过滤器</string>
<string name="add_intent_filter_work_to_personal">添加(工作到个人)</string> <string name="direction">方向</string>
<string name="add_intent_filter_personal_to_work">添加(个人到工作)</string> <string name="both_direction">双向</string>
<string name="work_to_personal">工作到个人</string>
<string name="personal_to_work">个人到工作</string>
<string name="clear_cross_profile_filters">清除所有过滤器</string> <string name="clear_cross_profile_filters">清除所有过滤器</string>
<string name="org_id">组织ID</string> <string name="org_id">组织ID</string>
<string name="length_6_to_64">长度应在6~64个字符之间</string> <string name="length_6_to_64">长度应在6~64个字符之间</string>

View File

@@ -79,7 +79,6 @@
<string name="profile_owner">Profile owner</string> <string name="profile_owner">Profile owner</string>
<string name="device_owner">Device owner</string> <string name="device_owner">Device owner</string>
<string name="delegated_admins">Delegated admins</string> <string name="delegated_admins">Delegated admins</string>
<string name="delegated_scope">delegated scope</string>
<string name="manage_application_restrictions">Manage application restrictions</string> <string name="manage_application_restrictions">Manage application restrictions</string>
<string name="manage_certificates">Manage certificates</string> <string name="manage_certificates">Manage certificates</string>
<string name="select_keychain_certificates">Select KeyChain certificate</string> <string name="select_keychain_certificates">Select KeyChain certificate</string>
@@ -105,7 +104,6 @@
<string name="disable_account_management">Disable account management</string> <string name="disable_account_management">Disable account management</string>
<string name="account_type">Account type</string> <string name="account_type">Account type</string>
<string name="transfer_ownership">Transfer Ownership</string> <string name="transfer_ownership">Transfer Ownership</string>
<string name="target_component_name">Target component name</string>
<string name="lock_screen_info">Lock screen info</string> <string name="lock_screen_info">Lock screen info</string>
<string name="support_messages">Support Messages</string> <string name="support_messages">Support Messages</string>
<string name="short_support_msg">Short message</string> <string name="short_support_msg">Short message</string>
@@ -249,8 +247,6 @@
<string name="install_system_update">Install system update</string> <string name="install_system_update">Install system update</string>
<string name="select_ota_package" tools:ignore="TypographyEllipsis">Select OTA package...</string> <string name="select_ota_package" tools:ignore="TypographyEllipsis">Select OTA package...</string>
<string name="start_install_system_update">Start installing system update</string>
<string name="install_system_update_failed">"Install system update failed: "</string>
<string name="battery_low">Battery is low</string> <string name="battery_low">Battery is low</string>
<string name="update_file_invalid">Update file is invalid</string> <string name="update_file_invalid">Update file is invalid</string>
<string name="incorrect_os_ver">Incorrect OS version</string> <string name="incorrect_os_ver">Incorrect OS version</string>
@@ -374,10 +370,11 @@
<string name="profile_max_time_off">Max time off</string> <string name="profile_max_time_off">Max time off</string>
<string name="profile_max_time_out_desc">Personal apps will be suspended after the work profile is closed for this amount of time. 0 means no limit. </string> <string name="profile_max_time_out_desc">Personal apps will be suspended after the work profile is closed for this amount of time. 0 means no limit. </string>
<string name="personal_app_suspended_because_timeout">Personal app suspended because of this: %1$s</string> <string name="personal_app_suspended_because_timeout">Personal app suspended because of this: %1$s</string>
<string name="cannot_less_than_72_hours">Cannot less than 72 hours</string>
<string name="intent_filter">Intent filter</string> <string name="intent_filter">Intent filter</string>
<string name="add_intent_filter_work_to_personal">Add(work to personal)</string> <string name="direction">Direction</string>
<string name="add_intent_filter_personal_to_work">Add(personal to work)</string> <string name="both_direction">Both direction</string>
<string name="work_to_personal">Work to personal</string>
<string name="personal_to_work">Personal to work</string>
<string name="clear_cross_profile_filters">Clear all filters</string> <string name="clear_cross_profile_filters">Clear all filters</string>
<string name="org_id">Organization ID</string> <string name="org_id">Organization ID</string>
<string name="length_6_to_64">The length should be between 6~64 characters</string> <string name="length_6_to_64">The length should be between 6~64 characters</string>