feat: improve intent filter presets

Fix app installer and uninstall app bug
This commit is contained in:
BinTianqi
2026-03-02 23:44:58 +08:00
parent f79474224c
commit 735177ac46
9 changed files with 135 additions and 30 deletions

View File

@@ -10,14 +10,20 @@ import androidx.fragment.app.FragmentActivity
import com.bintianqi.owndroid.feature.applications.AppInstaller
import com.bintianqi.owndroid.feature.applications.AppInstallerViewModel
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import com.bintianqi.owndroid.utils.viewModelFactory
class AppInstallerActivity:FragmentActivity() {
class AppInstallerActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
val vm by viewModels<AppInstallerViewModel>()
val myApp = application as MyApplication
val vm by viewModels<AppInstallerViewModel> {
viewModelFactory {
AppInstallerViewModel(myApp, myApp.container.settingsRepo)
}
}
vm.initialize(intent)
val themeState = (application as MyApplication).container.themeState
val themeState = myApp.container.themeState
setContent {
val theme by themeState.collectAsState()
OwnDroidTheme(theme) {

View File

@@ -10,7 +10,24 @@ data class IntentFilterOptions(
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)
val directionTextMap = mapOf(
1 to R.string.personal_to_work,
2 to R.string.work_to_personal,
3 to R.string.both_direction
)
class IntentFilterPreset(
val name: Int, val action: String, val category: String = "", val mimeType: String = ""
)
val crossProfileIntentFilterPresets = listOf(
IntentFilterPreset(R.string.open_file, Intent.ACTION_VIEW, Intent.CATEGORY_DEFAULT, "*/*"),
IntentFilterPreset(R.string.share, Intent.ACTION_SEND, Intent.CATEGORY_DEFAULT, "*/*"),
IntentFilterPreset(R.string.share_multiple, Intent.ACTION_SEND_MULTIPLE),
IntentFilterPreset(R.string.edit, Intent.ACTION_EDIT, Intent.CATEGORY_DEFAULT, "*/*"),
IntentFilterPreset(R.string.get_content, Intent.ACTION_GET_CONTENT, Intent.CATEGORY_DEFAULT, "*/*"),
IntentFilterPreset(R.string.install_app, Intent.ACTION_INSTALL_PACKAGE),
IntentFilterPreset(R.string.uninstall_app, Intent.ACTION_UNINSTALL_PACKAGE),
IntentFilterPreset(R.string.choose_file, Intent.ACTION_OPEN_DOCUMENT, Intent.CATEGORY_DEFAULT, "*/*"),
IntentFilterPreset(R.string.choose_folder, Intent.ACTION_OPEN_DOCUMENT_TREE)
)

View File

@@ -2,6 +2,7 @@ package com.bintianqi.owndroid.feature.work_profile
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -9,11 +10,13 @@ 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.lazy.items
import androidx.compose.foundation.rememberScrollState
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.filled.Add
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
@@ -26,6 +29,7 @@ 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
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@@ -39,6 +43,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -46,17 +51,17 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.ui.MyLazyScaffold
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.Notes
import com.bintianqi.owndroid.utils.BottomPadding
import com.bintianqi.owndroid.utils.HorizontalPadding
import com.bintianqi.owndroid.utils.adaptiveInsets
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CrossProfileIntentFilterScreen(
vm: CrossProfileIntentFilterViewModel, onNavigateUp: () -> Unit
vm: CrossProfileIntentFilterViewModel, onNavigateUp: () -> Unit, navigateToPresets: () -> Unit
) {
val focusMgr = LocalFocusManager.current
var action by remember { mutableStateOf("") }
@@ -66,7 +71,6 @@ fun CrossProfileIntentFilterScreen(
var mimeType by remember { mutableStateOf("") }
var dropdown by remember { mutableStateOf(false) }
var direction by remember { mutableIntStateOf(3) }
var dialog by remember { mutableStateOf(false) }
val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it != null) vm.importFilters(it)
}
@@ -90,7 +94,7 @@ fun CrossProfileIntentFilterScreen(
DropdownMenuItem(
{ Text(stringResource(R.string.presets)) },
{
dialog = true
navigateToPresets()
menu = false
},
leadingIcon = {
@@ -124,11 +128,6 @@ fun CrossProfileIntentFilterScreen(
},
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
)
Column(
Modifier
.padding(paddingValues)
@@ -205,24 +204,82 @@ fun CrossProfileIntentFilterScreen(
Notes(R.string.info_cross_profile_intent_filter)
Spacer(Modifier.height(BottomPadding))
}
if (dialog) AlertDialog(
title = { Text(stringResource(R.string.presets)) },
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CrossProfileIntentFilterPresetsScreen(
vm: CrossProfileIntentFilterViewModel, navigateUp: () -> Unit
) {
var dialog by remember { mutableStateOf<IntentFilterPreset?>(null) }
MyLazyScaffold(R.string.presets, navigateUp) {
items(crossProfileIntentFilterPresets) {
Row(
Modifier.padding(start = HorizontalPadding, end = 8.dp, bottom = 2.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(Modifier.weight(1F)) {
Text(stringResource(it.name))
Text(
it.action,
Modifier.alpha(0.7F),
style = MaterialTheme.typography.bodyMedium
)
}
IconButton({ dialog = it }) {
Icon(Icons.Default.Add, null)
}
}
}
}
if (dialog != null) {
var direction by remember { mutableIntStateOf(3) }
AlertDialog(
title = {
Text(stringResource(dialog!!.name))
},
text = {
crossProfileIntentFilterPresets.forEach {
Button({
vm.addFilter(it.value)
dialog = false
}) {
Text(stringResource(it.key))
Column {
var dropdown by remember { mutableStateOf(false) }
Text(dialog!!.action)
ExposedDropdownMenuBox(
dropdown, { dropdown = it }, Modifier.padding(top = 5.dp)
) {
OutlinedTextField(
stringResource(directionTextMap[direction]!!), {},
Modifier
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyLarge,
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
})
}
}
}
}
},
confirmButton = {
TextButton({ dialog = false }) {
TextButton({
vm.addPreset(dialog!!, direction)
dialog = null
}) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton({ dialog = null }) {
Text(stringResource(R.string.cancel))
}
},
onDismissRequest = { dialog = false }
onDismissRequest = { dialog = null }
)
}
}

View File

@@ -25,6 +25,10 @@ class CrossProfileIntentFilterViewModel(
toastChannel.sendStatus(true)
}
fun addPreset(preset: IntentFilterPreset, direction: Int) {
addFilter(IntentFilterOptions(preset.action, preset.category, preset.mimeType, direction))
}
fun clearFilters() = ph.safeDpmCall {
dpm.clearCrossProfileIntentFilters(dar)
repo.deleteAllCrossProfileIntentFilters()

View File

@@ -60,6 +60,7 @@ sealed class Destination : NavKey {
@Serializable object CreateWorkProfile : Destination()
@Serializable object SuspendPersonalApp : Destination()
@Serializable object CrossProfileIntentFilter : Destination()
@Serializable object CrossProfileIntentFilterPresets: Destination()
@Serializable object DeleteWorkProfile : Destination()
@Serializable object ApplicationFeatures : Destination()

View File

@@ -95,6 +95,7 @@ import com.bintianqi.owndroid.feature.users.UserSessionMessageScreen
import com.bintianqi.owndroid.feature.users.UsersOptionsScreen
import com.bintianqi.owndroid.feature.users.UsersScreen
import com.bintianqi.owndroid.feature.work_profile.CreateWorkProfileScreen
import com.bintianqi.owndroid.feature.work_profile.CrossProfileIntentFilterPresetsScreen
import com.bintianqi.owndroid.feature.work_profile.CrossProfileIntentFilterScreen
import com.bintianqi.owndroid.feature.work_profile.DeleteWorkProfileScreen
import com.bintianqi.owndroid.feature.work_profile.SuspendPersonalAppScreen
@@ -360,7 +361,14 @@ fun myEntryProvider(
entry<Destination.CrossProfileIntentFilter> {
CrossProfileIntentFilterScreen(
viewModel(factory = container.viewModelFactory), ::navigateUp
)
) {
navigate(Destination.CrossProfileIntentFilterPresets)
}
}
entry<Destination.CrossProfileIntentFilterPresets>(
metadata = navParentKey(Destination.CrossProfileIntentFilter)
) {
CrossProfileIntentFilterPresetsScreen(viewModel(), ::navigateUp)
}
entry<Destination.DeleteWorkProfile>(
metadata = navParentKey(Destination.WorkProfile)

View File

@@ -233,9 +233,11 @@ fun uninstallPackage(
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999)
if(statusExtra == PackageInstaller.STATUS_PENDING_USER_ACTION) {
if (statusExtra == PackageInstaller.STATUS_PENDING_USER_ACTION) {
@SuppressWarnings("UnsafeIntentLaunch")
context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?)
val confirmIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
confirmIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(confirmIntent)
} else {
context.unregisterReceiver(this)
if (statusExtra == PackageInstaller.STATUS_SUCCESS) {

View File

@@ -338,7 +338,12 @@
<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="open_file">打开文件</string>
<string name="share">分享</string>
<string name="share_multiple">分享(多个)</string>
<string name="get_content">获取内容</string>
<string name="choose_file">选择文件</string>
<string name="choose_folder">选择文件夹</string>
<string name="direction">方向</string>
<string name="both_direction">双向</string>
<string name="work_to_personal">工作到个人</string>

View File

@@ -372,7 +372,12 @@
<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="open_file">Open file</string>
<string name="share">Share</string>
<string name="share_multiple">Share (multiple)</string>
<string name="get_content">Get content</string>
<string name="choose_file">Choose file</string>
<string name="choose_folder">Choose folder</string>
<string name="direction">Direction</string>
<string name="both_direction">Both direction</string>
<string name="work_to_personal">Work to personal</string>