From 3da523051b43aa9a4897ed1c2b7c5fcf29e79d19 Mon Sep 17 00:00:00 2001 From: BinTianqi Date: Thu, 15 Jan 2026 13:04:52 +0800 Subject: [PATCH] feat: app groups importing and exporting (#222) Show group name in app group operation dialog (#224) --- .../com/bintianqi/owndroid/MainActivity.kt | 2 +- .../com/bintianqi/owndroid/MyViewModel.kt | 19 +++++++ .../bintianqi/owndroid/dpm/Applications.kt | 57 ++++++++++++++++++- .../main/res/drawable/file_export_fill0.xml | 9 +++ app/src/main/res/drawable/file_open_fill0.xml | 9 +++ app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/file_export_fill0.xml create mode 100644 app/src/main/res/drawable/file_open_fill0.xml diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index c35d8ca..5aedebe 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -642,7 +642,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) { } composable { ManageAppGroupsScreen( - vm.appGroups, + vm.appGroups, vm::exportAppGroups, vm::importAppGroups, { id, name, apps -> navController.navigate(EditAppGroup(id, name, apps)) }, ::navigateUp ) diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt index 1790e22..de923cd 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt @@ -65,6 +65,7 @@ import com.bintianqi.owndroid.dpm.ApnProtocol import com.bintianqi.owndroid.dpm.AppGroup import com.bintianqi.owndroid.dpm.AppRestriction import com.bintianqi.owndroid.dpm.AppStatus +import com.bintianqi.owndroid.dpm.BasicAppGroup import com.bintianqi.owndroid.dpm.CaCertInfo import com.bintianqi.owndroid.dpm.CreateUserResult import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions @@ -119,6 +120,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.addJsonObject +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.put import java.net.InetAddress import java.security.MessageDigest import java.security.cert.CertificateException @@ -632,6 +637,20 @@ class MyViewModel(application: Application): AndroidViewModel(application) { group.filter { it.id != id } } } + fun exportAppGroups(uri: Uri) { + application.contentResolver.openOutputStream(uri)!!.use { + val list: List = appGroups.value + it.write(Json.encodeToString(list).encodeToByteArray()) + } + } + fun importAppGroups(uri: Uri) { + application.contentResolver.openInputStream(uri)!!.use { + Json.decodeFromString>(it.readBytes().decodeToString()) + }.forEach { + myRepo.setAppGroup(null, it.name, it.apps) + } + getAppGroups() + } @RequiresApi(24) fun reboot() { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt index cbae39f..d3dc8b4 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt @@ -5,8 +5,11 @@ import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED import android.app.admin.PackagePolicy import android.content.Intent +import android.net.Uri import android.os.Build.VERSION import android.os.Looper +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -904,6 +907,8 @@ fun PackageFunctionScreen( if (dialog) AlertDialog( text = { Column { + Text(selectedGroup!!.name, style = typography.titleLarge) + Spacer(Modifier.height(6.dp)) Button({ onSet(selectedGroup!!.apps, true) dialog = false @@ -927,22 +932,68 @@ fun PackageFunctionScreen( ) } -class AppGroup(val id: Int, val name: String, val apps: List) +@Serializable +open class BasicAppGroup(open val name: String, open val apps: List) + +class AppGroup( + val id: Int, override val name: String, override val apps: List +) : BasicAppGroup(name, apps) @Serializable object ManageAppGroups @OptIn(ExperimentalMaterial3Api::class) @Composable fun ManageAppGroupsScreen( - appGroups: StateFlow>, + appGroups: StateFlow>, exportData: (Uri) -> Unit, importData: (Uri) -> Unit, navigateToEditScreen: (Int?, String, List) -> Unit, navigateUp: () -> Unit ) { val groups by appGroups.collectAsStateWithLifecycle() + val exportLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.CreateDocument("application/json") + ) { + if (it != null) exportData(it) + } + val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { + if (it != null) importData(it) + } Scaffold( topBar = { TopAppBar( { Text(stringResource(R.string.app_group)) }, - navigationIcon = { NavIcon(navigateUp) } + navigationIcon = { NavIcon(navigateUp) }, + actions = { + var dropdown by remember { mutableStateOf(false) } + Box { + IconButton({ + dropdown = true + }) { + Icon(Icons.Default.MoreVert, null) + } + DropdownMenu(dropdown, { dropdown = false }) { + DropdownMenuItem( + { Text(stringResource(R.string.export)) }, + { + exportLauncher.launch("owndroid_app_groups") + dropdown = false + }, + leadingIcon = { + Icon(painterResource(R.drawable.file_export_fill0), null) + } + ) + DropdownMenuItem( + { Text(stringResource(R.string.import_str)) }, + { + importLauncher.launch(arrayOf("application/json")) + dropdown = false + }, + leadingIcon = { + Icon(painterResource(R.drawable.file_open_fill0), null) + } + ) + } + } + + } ) }, floatingActionButton = { diff --git a/app/src/main/res/drawable/file_export_fill0.xml b/app/src/main/res/drawable/file_export_fill0.xml new file mode 100644 index 0000000..a2e4e8b --- /dev/null +++ b/app/src/main/res/drawable/file_export_fill0.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/file_open_fill0.xml b/app/src/main/res/drawable/file_open_fill0.xml new file mode 100644 index 0000000..64664a0 --- /dev/null +++ b/app/src/main/res/drawable/file_open_fill0.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index cf5b18f..08879fb 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -376,6 +376,7 @@ 搜索 应用组 管理组 + 导入 编辑组 添加到列表 从列表中移除 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9df041d..e8ff66c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -410,6 +410,7 @@ Search App group Manage groups + Import Edit group Add to list Remove from list