mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 11:05:59 +00:00
feat: import/export cross profile intent filters (#240)
Add intent filter presets
This commit is contained in:
@@ -494,7 +494,11 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
)
|
||||
}
|
||||
composable<CrossProfileIntentFilter> {
|
||||
CrossProfileIntentFilterScreen(vm::addCrossProfileIntentFilter, ::navigateUp)
|
||||
CrossProfileIntentFilterScreen(
|
||||
vm::addCrossProfileIntentFilter, vm::clearCrossProfileIntentFilters,
|
||||
vm::importCrossProfileIntentFilters, vm::exportCrossProfileIntentFilters,
|
||||
::navigateUp
|
||||
)
|
||||
}
|
||||
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(vm::wipeData, ::navigateUp) }
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ class ManageSpaceActivity: FragmentActivity() {
|
||||
cacheDir.deleteRecursively()
|
||||
codeCacheDir.deleteRecursively()
|
||||
if(Build.VERSION.SDK_INT >= 24) {
|
||||
dataDir.resolve("databases").deleteRecursively()
|
||||
dataDir.resolve("shared_prefs").deleteRecursively()
|
||||
} else {
|
||||
val sharedPref = applicationContext.getSharedPreferences("data", MODE_PRIVATE)
|
||||
|
||||
@@ -4,12 +4,13 @@ import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
|
||||
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 4) {
|
||||
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 5) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(DHIZUKU_CLIENTS_TABLE)
|
||||
db.execSQL(SECURITY_LOGS_TABLE)
|
||||
db.execSQL(NETWORK_LOGS_TABLE)
|
||||
db.execSQL(APP_GROUPS_TABLE)
|
||||
db.execSQL(CP_INTENTS_TABLE)
|
||||
}
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion < 2) {
|
||||
@@ -21,6 +22,9 @@ class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 4) {
|
||||
if (oldVersion < 4) {
|
||||
db.execSQL(APP_GROUPS_TABLE)
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
db.execSQL(CP_INTENTS_TABLE)
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
const val DHIZUKU_CLIENTS_TABLE = "CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
|
||||
@@ -33,5 +37,7 @@ class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 4) {
|
||||
const val APP_GROUPS_TABLE = "CREATE TABLE app_groups(" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
"name TEXT, apps TEXT)"
|
||||
const val CP_INTENTS_TABLE = "CREATE TABLE cross_profile_intent_filters (" +
|
||||
"action_str TEXT, category TEXT, mime_type TEXT, direction INTEGER)"
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import androidx.core.database.getIntOrNull
|
||||
import androidx.core.database.getLongOrNull
|
||||
import androidx.core.database.getStringOrNull
|
||||
import com.bintianqi.owndroid.dpm.AppGroup
|
||||
import com.bintianqi.owndroid.dpm.IntentFilterOptions
|
||||
import com.bintianqi.owndroid.dpm.NetworkLog
|
||||
import com.bintianqi.owndroid.dpm.SecurityEvent
|
||||
import com.bintianqi.owndroid.dpm.SecurityEventWithData
|
||||
@@ -248,4 +249,29 @@ class MyRepository(val dbHelper: MyDbHelper) {
|
||||
fun deleteAppGroup(id: Int) {
|
||||
dbHelper.writableDatabase.delete("app_groups", "id = ?", arrayOf(id.toString()))
|
||||
}
|
||||
|
||||
fun setCrossProfileIntentFilter(data: IntentFilterOptions) {
|
||||
val cv = ContentValues()
|
||||
cv.put("action_str", data.action)
|
||||
cv.put("category", data.category)
|
||||
cv.put("mime_type", data.mimeType)
|
||||
cv.put("direction", data.direction)
|
||||
dbHelper.writableDatabase.insert("cross_profile_intent_filters", null, cv)
|
||||
}
|
||||
fun getAllCrossProfileIntentFilters(): List<IntentFilterOptions> {
|
||||
val list = mutableListOf<IntentFilterOptions>()
|
||||
dbHelper.readableDatabase.rawQuery(
|
||||
"SELECT * FROM cross_profile_intent_filters", null
|
||||
).use {
|
||||
while (it.moveToNext()) {
|
||||
list += IntentFilterOptions(
|
||||
it.getString(0), it.getString(1), it.getString(2), it.getInt(3)
|
||||
)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
fun deleteAllCrossProfileIntentFilters() {
|
||||
dbHelper.writableDatabase.delete("cross_profile_intent_filters", null, null);
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,6 @@ import com.bintianqi.owndroid.dpm.DelegatedAdmin
|
||||
import com.bintianqi.owndroid.dpm.DeviceAdmin
|
||||
import com.bintianqi.owndroid.dpm.FrpPolicyInfo
|
||||
import com.bintianqi.owndroid.dpm.HardwareProperties
|
||||
import com.bintianqi.owndroid.dpm.IntentFilterDirection
|
||||
import com.bintianqi.owndroid.dpm.IntentFilterOptions
|
||||
import com.bintianqi.owndroid.dpm.IpMode
|
||||
import com.bintianqi.owndroid.dpm.KeyguardDisableConfig
|
||||
@@ -1417,13 +1416,28 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
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, options.direction)
|
||||
myRepo.setCrossProfileIntentFilter(options)
|
||||
}
|
||||
fun clearCrossProfileIntentFilters() {
|
||||
DPM.clearCrossProfileIntentFilters(DAR)
|
||||
myRepo.deleteAllCrossProfileIntentFilters()
|
||||
}
|
||||
fun importCrossProfileIntentFilters(uri: Uri) {
|
||||
val bytes = application.contentResolver.openInputStream(uri)!!.use {
|
||||
it.readBytes().decodeToString()
|
||||
}
|
||||
val data = Json.decodeFromString<List<IntentFilterOptions>>(bytes)
|
||||
data.forEach {
|
||||
addCrossProfileIntentFilter(it)
|
||||
}
|
||||
}
|
||||
fun exportCrossProfileIntentFilters(uri: Uri) {
|
||||
val data = myRepo.getAllCrossProfileIntentFilters()
|
||||
val bytes = Json.encodeToString(data).encodeToByteArray()
|
||||
application.contentResolver.openOutputStream(uri)!!.use {
|
||||
it.write(bytes)
|
||||
}
|
||||
DPM.addCrossProfileIntentFilter(DAR, filter, flags)
|
||||
}
|
||||
|
||||
val UM = application.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
|
||||
@@ -4,34 +4,46 @@ import android.app.admin.DevicePolicyManager
|
||||
import android.app.admin.DevicePolicyManager.WIPE_EUICC
|
||||
import android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Build.VERSION
|
||||
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.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -45,20 +57,24 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.BottomPadding
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
import com.bintianqi.owndroid.Privilege
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.adaptiveInsets
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
||||
import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem
|
||||
import com.bintianqi.owndroid.ui.FunctionItem
|
||||
import com.bintianqi.owndroid.ui.MyScaffold
|
||||
import com.bintianqi.owndroid.ui.NavIcon
|
||||
import com.bintianqi.owndroid.ui.Notes
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import com.bintianqi.owndroid.yesOrNo
|
||||
@@ -240,21 +256,24 @@ fun SuspendPersonalAppScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class IntentFilterOptions(
|
||||
val action: String, val category: String, val mimeType: String,
|
||||
val direction: IntentFilterDirection
|
||||
val direction: Int // 1: private to work, 2: work to private, 3: both
|
||||
)
|
||||
|
||||
val crossProfileIntentFilterPresets = mapOf(
|
||||
R.string.allow_file_sharing to
|
||||
IntentFilterOptions(Intent.ACTION_SEND, Intent.CATEGORY_DEFAULT, "*/*", 3)
|
||||
)
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CrossProfileIntentFilterScreen(
|
||||
addFilter: (IntentFilterOptions) -> Unit,
|
||||
addFilter: (IntentFilterOptions) -> Unit, clearFilters: () -> Unit,
|
||||
importFilters: (Uri) -> Unit, exportFilters: (Uri) -> Unit,
|
||||
onNavigateUp: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -265,74 +284,171 @@ fun CrossProfileIntentFilterScreen(
|
||||
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(
|
||||
value = action, onValueChange = { action = it },
|
||||
label = { Text("Action") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() }),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
var direction by remember { mutableIntStateOf(3) }
|
||||
var dialog by remember { mutableStateOf(false) }
|
||||
val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
if (it != null) {
|
||||
importFilters(it)
|
||||
context.showOperationResultToast(true)
|
||||
}
|
||||
}
|
||||
val exportLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("application/json")
|
||||
) {
|
||||
if (it != null) {
|
||||
exportFilters(it)
|
||||
context.showOperationResultToast(true)
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.intent_filter)) },
|
||||
navigationIcon = { NavIcon(onNavigateUp) },
|
||||
actions = {
|
||||
var menu by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
IconButton({ menu = !menu }) {
|
||||
Icon(Icons.Default.MoreVert, null)
|
||||
}
|
||||
DropdownMenu(menu, { menu = false }) {
|
||||
DropdownMenuItem(
|
||||
{ Text(stringResource(R.string.presets)) },
|
||||
{
|
||||
dialog = true
|
||||
menu = false
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(painterResource(R.drawable.list_fill0), null)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
{ Text(stringResource(R.string.import_str)) },
|
||||
{
|
||||
importLauncher.launch(arrayOf("application/json"))
|
||||
menu = false
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(painterResource(R.drawable.file_open_fill0), null)
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
{ Text(stringResource(R.string.export)) },
|
||||
{
|
||||
exportLauncher.launch("owndroid_intent_filters")
|
||||
menu = false
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(painterResource(R.drawable.file_export_fill0), null)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
contentWindowInsets = adaptiveInsets()
|
||||
) { paddingValues ->
|
||||
val directionTextMap = mapOf(
|
||||
1 to R.string.personal_to_work,
|
||||
2 to R.string.work_to_personal,
|
||||
3 to R.string.both_direction
|
||||
)
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(customCategory, {
|
||||
customCategory = it
|
||||
category = ""
|
||||
})
|
||||
Column(
|
||||
Modifier
|
||||
.padding(paddingValues)
|
||||
.padding(horizontal = HorizontalPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
OutlinedTextField(
|
||||
category, { category = it }, Modifier.fillMaxWidth(),
|
||||
label = { Text("Category") }, enabled = customCategory
|
||||
value = action, onValueChange = { action = it },
|
||||
label = { Text("Action") },
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() }),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
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 = { ExposedDropdownMenuDefaults.TrailingIcon(dropdown) }
|
||||
)
|
||||
ExposedDropdownMenu(dropdown, { dropdown = false }) {
|
||||
IntentFilterDirection.entries.forEach {
|
||||
DropdownMenuItem({ Text(stringResource(it.text)) }, {
|
||||
direction = it
|
||||
dropdown = false
|
||||
})
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(customCategory, {
|
||||
customCategory = it
|
||||
category = ""
|
||||
})
|
||||
OutlinedTextField(
|
||||
category, { category = it }, Modifier.fillMaxWidth(),
|
||||
label = { Text("Category") }, enabled = customCategory
|
||||
)
|
||||
}
|
||||
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(directionTextMap[direction]!!), {},
|
||||
Modifier
|
||||
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
|
||||
.fillMaxWidth(),
|
||||
label = { Text(stringResource(R.string.direction)) }, readOnly = true,
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(dropdown) }
|
||||
)
|
||||
ExposedDropdownMenu(dropdown, { dropdown = false }) {
|
||||
directionTextMap.forEach {
|
||||
DropdownMenuItem({ Text(stringResource(it.value)) }, {
|
||||
direction = it.key
|
||||
dropdown = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Button(
|
||||
{
|
||||
addFilter(IntentFilterOptions(action, category, mimeType, direction))
|
||||
context.showOperationResultToast(true)
|
||||
},
|
||||
Modifier.fillMaxWidth(),
|
||||
enabled = action.isNotBlank() && (!customCategory || category.isNotBlank()) &&
|
||||
(!customMimeType || mimeType.isNotBlank())
|
||||
) {
|
||||
Text(stringResource(R.string.add))
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
clearFilters()
|
||||
context.showOperationResultToast(true)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.clear_cross_profile_filters))
|
||||
}
|
||||
Notes(R.string.info_cross_profile_intent_filter)
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
Button(
|
||||
{
|
||||
addFilter(IntentFilterOptions(
|
||||
action, category, mimeType, direction
|
||||
))
|
||||
context.showOperationResultToast(true)
|
||||
if (dialog) AlertDialog(
|
||||
title = { Text(stringResource(R.string.presets)) },
|
||||
text = {
|
||||
crossProfileIntentFilterPresets.forEach {
|
||||
Button({
|
||||
addFilter(it.value)
|
||||
context.showOperationResultToast(true)
|
||||
dialog = false
|
||||
}) {
|
||||
Text(stringResource(it.key))
|
||||
}
|
||||
}
|
||||
},
|
||||
Modifier.fillMaxWidth(),
|
||||
enabled = action.isNotBlank() && (!customCategory || category.isNotBlank()) &&
|
||||
(!customMimeType || mimeType.isNotBlank())
|
||||
) {
|
||||
Text(stringResource(R.string.add))
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
Privilege.DPM.clearCrossProfileIntentFilters(Privilege.DAR)
|
||||
context.showOperationResultToast(true)
|
||||
confirmButton = {
|
||||
TextButton({ dialog = false }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.clear_cross_profile_filters))
|
||||
}
|
||||
Notes(R.string.info_cross_profile_intent_filter)
|
||||
onDismissRequest = { dialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -331,6 +331,8 @@
|
||||
<string name="profile_max_time_out_desc">工作资料处于关闭状态的时间达到该限制后会挂起个人应用,0为无限制</string>
|
||||
<string name="personal_app_suspended_because_timeout">个人应用已经因此挂起:%1$s</string>
|
||||
<string name="intent_filter">Intent过滤器</string>
|
||||
<string name="presets">预设</string>
|
||||
<string name="allow_file_sharing">允许分享文件</string>
|
||||
<string name="direction">方向</string>
|
||||
<string name="both_direction">双向</string>
|
||||
<string name="work_to_personal">工作到个人</string>
|
||||
|
||||
@@ -365,6 +365,8 @@
|
||||
<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="intent_filter">Intent filter</string>
|
||||
<string name="presets">Presets</string>
|
||||
<string name="allow_file_sharing">Allow file sharing</string>
|
||||
<string name="direction">Direction</string>
|
||||
<string name="both_direction">Both direction</string>
|
||||
<string name="work_to_personal">Work to personal</string>
|
||||
|
||||
Reference in New Issue
Block a user