feat: a snackbar to undo removal in PackageFunctionScreen (#216)

Remove redundant SDK version check
This commit is contained in:
BinTianqi
2025-12-27 14:45:09 +08:00
parent 434e9c1b25
commit 20a422ba51
12 changed files with 115 additions and 129 deletions

View File

@@ -16,6 +16,7 @@ class AppInstallerActivity:FragmentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val vm by viewModels<AppInstallerViewModel>() val vm by viewModels<AppInstallerViewModel>()
vm.initialize(intent) vm.initialize(intent)
vm.registerInstallerReceiver(this)
val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme) val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)
setContent { setContent {
OwnDroidTheme(theme) { OwnDroidTheme(theme) {

View File

@@ -36,13 +36,19 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { list += it } intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { list += it }
intent.getParcelableArrayExtra(Intent.EXTRA_STREAM)?.forEach { list += it as Uri } intent.getParcelableArrayExtra(Intent.EXTRA_STREAM)?.forEach { list += it as Uri }
intent.clipData?.let { clipData -> intent.clipData?.let { clipData ->
for(i in 0..clipData.itemCount - 1) { for(i in 0..<clipData.itemCount) {
list += clipData.getItemAt(i).uri list += clipData.getItemAt(i).uri
} }
} }
uiState.update { it.copy(it.packages + list.distinct()) } uiState.update { it.copy(it.packages + list.distinct()) }
} }
fun registerInstallerReceiver(context: Context) {
ContextCompat.registerReceiver(
context, Receiver(), IntentFilter(ACTION), ContextCompat.RECEIVER_NOT_EXPORTED
)
}
fun onPackagesAdd(packages: List<Uri>) { fun onPackagesAdd(packages: List<Uri>) {
uiState.update { uiState.update {
it.copy(packages = it.packages.plus(packages).distinct()) it.copy(packages = it.packages.plus(packages).distinct())
@@ -93,17 +99,14 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
uiState.update { it.copy(installing = false, packageWriting = -1) } uiState.update { it.copy(installing = false, packageWriting = -1) }
return return
} }
ContextCompat.registerReceiver( val intent = Intent(ACTION).setPackage(application.packageName)
application, Receiver(), IntentFilter(ACTION), null,
null, ContextCompat.RECEIVER_EXPORTED
)
val pi = if(Build.VERSION.SDK_INT >= 34) { val pi = if(Build.VERSION.SDK_INT >= 34) {
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
application, sessionId, Intent(ACTION), application, sessionId, intent,
PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
).intentSender ).intentSender
} else { } else {
PendingIntent.getBroadcast(application, sessionId, Intent(ACTION), PendingIntent.FLAG_MUTABLE).intentSender PendingIntent.getBroadcast(application, sessionId, intent, PendingIntent.FLAG_MUTABLE).intentSender
} }
session.commit(pi) session.commit(pi)
} }
@@ -119,7 +122,6 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
) )
} else { } else {
uiState.update { it.copy(result = intent) } uiState.update { it.copy(result = intent) }
context.unregisterReceiver(this)
} }
} }
} }

View File

@@ -269,10 +269,6 @@ class MainActivity : FragmentActivity() {
} }
} }
override fun onResume() {
super.onResume()
}
} }
@ExperimentalMaterial3Api @ExperimentalMaterial3Api

View File

@@ -276,7 +276,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
val packagePermissions = MutableStateFlow(emptyMap<String, Int>()) val packagePermissions = MutableStateFlow(emptyMap<String, Int>())
@RequiresApi(23)
fun getPackagePermissions(name: String) { fun getPackagePermissions(name: String) {
if (name.isValidPackageName) { if (name.isValidPackageName) {
packagePermissions.value = runtimePermissions.associate { packagePermissions.value = runtimePermissions.associate {
@@ -286,7 +285,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
packagePermissions.value = emptyMap() packagePermissions.value = emptyMap()
} }
} }
@RequiresApi(23)
fun setPackagePermission(name: String, permission: String, status: Int): Boolean { fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
val result = DPM.setPermissionGrantState(DAR, name, permission, status) val result = DPM.setPermissionGrantState(DAR, name, permission, status)
getPackagePermissions(name) getPackagePermissions(name)
@@ -366,6 +364,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) { fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) {
val action = "com.bintianqi.owndroid.action.PACKAGE_UNINSTALLED"
val receiver = object : BroadcastReceiver() { val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999) val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999)
@@ -383,16 +382,17 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
ContextCompat.registerReceiver( ContextCompat.registerReceiver(
application, receiver, IntentFilter(AppInstallerViewModel.ACTION), null, application, receiver, IntentFilter(action), null,
null, ContextCompat.RECEIVER_EXPORTED null, ContextCompat.RECEIVER_NOT_EXPORTED
) )
val intent = Intent(action).setPackage(application.packageName)
val pi = if(VERSION.SDK_INT >= 34) { val pi = if(VERSION.SDK_INT >= 34) {
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
application, 0, Intent(AppInstallerViewModel.ACTION), application, 0, intent,
PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
).intentSender ).intentSender
} else { } else {
PendingIntent.getBroadcast(application, 0, Intent(AppInstallerViewModel.ACTION), PendingIntent.FLAG_MUTABLE).intentSender PendingIntent.getBroadcast(application, 0, intent, PendingIntent.FLAG_MUTABLE).intentSender
} }
application.getPackageInstaller().uninstall(packageName, pi) application.getPackageInstaller().uninstall(packageName, pi)
} }
@@ -545,7 +545,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
val appRestrictions = MutableStateFlow(emptyList<AppRestriction>()) val appRestrictions = MutableStateFlow(emptyList<AppRestriction>())
@RequiresApi(23)
fun getAppRestrictions(name: String) { fun getAppRestrictions(name: String) {
val rm = application.getSystemService(RestrictionsManager::class.java) val rm = application.getSystemService(RestrictionsManager::class.java)
try { try {
@@ -569,7 +568,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
@RequiresApi(23)
fun setAppRestrictions(name: String, item: AppRestriction) { fun setAppRestrictions(name: String, item: AppRestriction) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val bundle = transformAppRestriction( val bundle = transformAppRestriction(
@@ -580,7 +578,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
@RequiresApi(23)
fun clearAppRestrictions(name: String) { fun clearAppRestrictions(name: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
DPM.setApplicationRestrictions(DAR, name, Bundle()) DPM.setApplicationRestrictions(DAR, name, Bundle())
@@ -699,7 +696,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
autoTimeRequired = if (VERSION.SDK_INT < 30) DPM.autoTimeRequired else false, autoTimeRequired = if (VERSION.SDK_INT < 30) DPM.autoTimeRequired else false,
masterVolumeMuted = DPM.isMasterVolumeMuted(DAR), masterVolumeMuted = DPM.isMasterVolumeMuted(DAR),
backupServiceEnabled = if (VERSION.SDK_INT >= 26) DPM.isBackupServiceEnabled(DAR) else false, backupServiceEnabled = if (VERSION.SDK_INT >= 26) DPM.isBackupServiceEnabled(DAR) else false,
btContactSharingDisabled = if (VERSION.SDK_INT >= 23 && privilege.work) btContactSharingDisabled = if (privilege.work)
DPM.getBluetoothContactSharingDisabled(DAR) else false, DPM.getBluetoothContactSharingDisabled(DAR) else false,
commonCriteriaMode = if (VERSION.SDK_INT >= 30 && privilege.run { device || org }) commonCriteriaMode = if (VERSION.SDK_INT >= 30 && privilege.run { device || org })
DPM.isCommonCriteriaModeEnabled(DAR) else false, DPM.isCommonCriteriaModeEnabled(DAR) else false,
@@ -718,7 +715,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
it.copy(screenCaptureDisabled = DPM.getScreenCaptureDisabled(null)) it.copy(screenCaptureDisabled = DPM.getScreenCaptureDisabled(null))
} }
} }
@RequiresApi(23)
fun setStatusBarDisabled(disabled: Boolean) { fun setStatusBarDisabled(disabled: Boolean) {
val result = DPM.setStatusBarDisabled(DAR, disabled) val result = DPM.setStatusBarDisabled(DAR, disabled)
if (result) systemOptionsStatus.update { it.copy(statusBarDisabled = disabled) } if (result) systemOptionsStatus.update { it.copy(statusBarDisabled = disabled) }
@@ -752,7 +748,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
it.copy(backupServiceEnabled = DPM.isBackupServiceEnabled(DAR)) it.copy(backupServiceEnabled = DPM.isBackupServiceEnabled(DAR))
} }
} }
@RequiresApi(23)
fun setBtContactSharingDisabled(disabled: Boolean) { fun setBtContactSharingDisabled(disabled: Boolean) {
DPM.setBluetoothContactSharingDisabled(DAR, disabled) DPM.setBluetoothContactSharingDisabled(DAR, disabled)
systemOptionsStatus.update { systemOptionsStatus.update {
@@ -771,7 +766,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
DPM.isUsbDataSignalingEnabled = enabled DPM.isUsbDataSignalingEnabled = enabled
systemOptionsStatus.update { it.copy(usbSignalEnabled = DPM.isUsbDataSignalingEnabled) } systemOptionsStatus.update { it.copy(usbSignalEnabled = DPM.isUsbDataSignalingEnabled) }
} }
@RequiresApi(23)
fun setKeyguardDisabled(disabled: Boolean): Boolean { fun setKeyguardDisabled(disabled: Boolean): Boolean {
return DPM.setKeyguardDisabled(DAR, disabled) return DPM.setKeyguardDisabled(DAR, disabled)
} }
@@ -841,11 +835,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
fun setContentProtectionPolicy(policy: Int) { fun setContentProtectionPolicy(policy: Int) {
DPM.setContentProtectionPolicy(DAR, policy) DPM.setContentProtectionPolicy(DAR, policy)
} }
@RequiresApi(23)
fun getPermissionPolicy(): Int { fun getPermissionPolicy(): Int {
return DPM.getPermissionPolicy(DAR) return DPM.getPermissionPolicy(DAR)
} }
@RequiresApi(23)
fun setPermissionPolicy(policy: Int) { fun setPermissionPolicy(policy: Int) {
DPM.setPermissionPolicy(DAR, policy) DPM.setPermissionPolicy(DAR, policy)
} }
@@ -1031,14 +1023,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
} }
@RequiresApi(23)
fun getSystemUpdatePolicy(): SystemUpdatePolicyInfo { fun getSystemUpdatePolicy(): SystemUpdatePolicyInfo {
val policy = DPM.systemUpdatePolicy val policy = DPM.systemUpdatePolicy
return SystemUpdatePolicyInfo( return SystemUpdatePolicyInfo(
policy?.policyType ?: -1, policy?.installWindowStart ?: 0, policy?.installWindowEnd ?: 0 policy?.policyType ?: -1, policy?.installWindowStart ?: 0, policy?.installWindowEnd ?: 0
) )
} }
@RequiresApi(23)
fun setSystemUpdatePolicy(info: SystemUpdatePolicyInfo) { fun setSystemUpdatePolicy(info: SystemUpdatePolicyInfo) {
val policy = when (info.type) { val policy = when (info.type) {
SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC -> SystemUpdatePolicy.createAutomaticInstallPolicy() SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC -> SystemUpdatePolicy.createAutomaticInstallPolicy()
@@ -1352,18 +1342,10 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
fun createWorkProfile(options: CreateWorkProfileOptions): Intent { fun createWorkProfile(options: CreateWorkProfileOptions): Intent {
val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
if (VERSION.SDK_INT >= 23) {
intent.putExtra( intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
MyAdminComponent MyAdminComponent
) )
} else {
intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
application.packageName
)
}
if (options.migrateAccount && VERSION.SDK_INT >= 22) {
intent.putExtra( intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE,
Account(options.accountName, options.accountType) Account(options.accountName, options.accountType)
@@ -1374,7 +1356,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
options.keepAccount options.keepAccount
) )
} }
}
if (VERSION.SDK_INT >= 24) { if (VERSION.SDK_INT >= 24) {
intent.putExtra( intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION,
@@ -1440,10 +1421,10 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
return UserInformation( return UserInformation(
if (VERSION.SDK_INT >= 24) UserManager.supportsMultipleUsers() else false, if (VERSION.SDK_INT >= 24) UserManager.supportsMultipleUsers() else false,
if (VERSION.SDK_INT >= 31) UserManager.isHeadlessSystemUserMode() else false, if (VERSION.SDK_INT >= 31) UserManager.isHeadlessSystemUserMode() else false,
if (VERSION.SDK_INT >= 23) UM.isSystemUser else false, UM.isSystemUser,
if (VERSION.SDK_INT >= 34) UM.isAdminUser else false, if (VERSION.SDK_INT >= 34) UM.isAdminUser else false,
if (VERSION.SDK_INT >= 25) UM.isDemoUser else false, if (VERSION.SDK_INT >= 25) UM.isDemoUser else false,
if (VERSION.SDK_INT >= 23) UM.getUserCreationTime(uh) else 0, UM.getUserCreationTime(uh),
if (VERSION.SDK_INT >= 28) DPM.isLogoutEnabled else false, if (VERSION.SDK_INT >= 28) DPM.isLogoutEnabled else false,
if (VERSION.SDK_INT >= 28) DPM.isEphemeralUser(DAR) else false, if (VERSION.SDK_INT >= 28) DPM.isEphemeralUser(DAR) else false,
if (VERSION.SDK_INT >= 28) DPM.isAffiliatedUser else false, if (VERSION.SDK_INT >= 28) DPM.isAffiliatedUser else false,
@@ -1518,7 +1499,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
fun setProfileName(name: String) { fun setProfileName(name: String) {
DPM.setProfileName(DAR, name) DPM.setProfileName(DAR, name)
} }
@RequiresApi(23)
fun setUserIcon(bitmap: Bitmap) { fun setUserIcon(bitmap: Bitmap) {
DPM.setUserIcon(DAR, bitmap) DPM.setUserIcon(DAR, bitmap)
} }
@@ -1656,7 +1636,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
return PM.getPackageUid(name, 0) return PM.getPackageUid(name, 0)
} }
var networkStatsData = emptyList<NetworkStatsData>() var networkStatsData = emptyList<NetworkStatsData>()
@RequiresApi(23)
fun readNetworkStats(stats: NetworkStats): List<NetworkStatsData> { fun readNetworkStats(stats: NetworkStats): List<NetworkStatsData> {
val list = mutableListOf<NetworkStatsData>() val list = mutableListOf<NetworkStatsData>()
while (stats.hasNextBucket()) { while (stats.hasNextBucket()) {
@@ -1667,7 +1646,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
stats.close() stats.close()
return list return list
} }
@RequiresApi(23)
fun readNetworkStatsBucket(bucket: NetworkStats.Bucket): NetworkStatsData { fun readNetworkStatsBucket(bucket: NetworkStats.Bucket): NetworkStatsData {
return NetworkStatsData( return NetworkStatsData(
bucket.rxBytes, bucket.rxPackets, bucket.txBytes, bucket.txPackets, bucket.rxBytes, bucket.rxPackets, bucket.txBytes, bucket.txPackets,

View File

@@ -65,6 +65,10 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -78,6 +82,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -121,6 +126,7 @@ import com.google.accompanist.drawablepainter.rememberDrawablePainter
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -213,9 +219,7 @@ fun ApplicationsFeaturesScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Un
if(VERSION.SDK_INT >= 30 && (privilege.device || (VERSION.SDK_INT >= 33 && privilege.profile))) { if(VERSION.SDK_INT >= 30 && (privilege.device || (VERSION.SDK_INT >= 33 && privilege.profile))) {
FunctionItem(R.string.disable_user_control, icon = R.drawable.do_not_touch_fill0) { onNavigate(DisableUserControl) } FunctionItem(R.string.disable_user_control, icon = R.drawable.do_not_touch_fill0) { onNavigate(DisableUserControl) }
} }
if(VERSION.SDK_INT >= 23) {
FunctionItem(R.string.permissions, icon = R.drawable.shield_fill0) { onNavigate(PermissionsManager()) } FunctionItem(R.string.permissions, icon = R.drawable.shield_fill0) { onNavigate(PermissionsManager()) }
}
if(VERSION.SDK_INT >= 28) { if(VERSION.SDK_INT >= 28) {
FunctionItem(R.string.disable_metered_data, icon = R.drawable.money_off_fill0) { onNavigate(DisableMeteredData) } FunctionItem(R.string.disable_metered_data, icon = R.drawable.money_off_fill0) { onNavigate(DisableMeteredData) }
} }
@@ -278,7 +282,7 @@ fun ApplicationDetailsScreen(
val appRestrictions by vm.appRestrictions.collectAsStateWithLifecycle() val appRestrictions by vm.appRestrictions.collectAsStateWithLifecycle()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
vm.getAppStatus(packageName) vm.getAppStatus(packageName)
if (VERSION.SDK_INT >= 23) vm.getAppRestrictions(packageName) vm.getAppRestrictions(packageName)
} }
MySmallTitleScaffold(R.string.place_holder, onNavigateUp, 0.dp) { MySmallTitleScaffold(R.string.place_holder, onNavigateUp, 0.dp) {
Column(Modifier Column(Modifier
@@ -320,7 +324,7 @@ fun ApplicationDetailsScreen(
state = status.keepUninstalled, state = status.keepUninstalled,
onCheckedChange = { vm.adSetPackageKu(packageName, it) } onCheckedChange = { vm.adSetPackageKu(packageName, it) }
) )
if (VERSION.SDK_INT >= 23 && appRestrictions.isNotEmpty()) { if (appRestrictions.isNotEmpty()) {
FunctionItem(R.string.managed_configuration, icon = R.drawable.description_fill0) { FunctionItem(R.string.managed_configuration, icon = R.drawable.description_fill0) {
onNavigate(ManagedConfiguration(packageName)) onNavigate(ManagedConfiguration(packageName))
} }
@@ -347,7 +351,6 @@ fun ApplicationDetailsScreen(
@Serializable data class PermissionsManager(val packageName: String? = null) @Serializable data class PermissionsManager(val packageName: String? = null)
@RequiresApi(23)
@Composable @Composable
fun PermissionsManagerScreen( fun PermissionsManagerScreen(
packagePermissions: MutableStateFlow<Map<String, Int>>, getPackagePermissions: (String) -> Unit, packagePermissions: MutableStateFlow<Map<String, Int>>, getPackagePermissions: (String) -> Unit,
@@ -806,12 +809,15 @@ fun PackageFunctionScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
navigateToGroups: () -> Unit, appGroups: StateFlow<List<AppGroup>>, notes: Int? = null navigateToGroups: () -> Unit, appGroups: StateFlow<List<AppGroup>>, notes: Int? = null
) { ) {
val context = LocalContext.current
val groups by appGroups.collectAsStateWithLifecycle() val groups by appGroups.collectAsStateWithLifecycle()
val packages by packagesState.collectAsStateWithLifecycle() val packages by packagesState.collectAsStateWithLifecycle()
var input by rememberSaveable { mutableStateOf("") } var input by rememberSaveable { mutableStateOf("") }
val inputPackages = parsePackageNames(input) val inputPackages = parsePackageNames(input)
var dialog by remember { mutableStateOf(false) } var dialog by remember { mutableStateOf(false) }
var selectedGroup by remember { mutableStateOf<AppGroup?>(null) } var selectedGroup by remember { mutableStateOf<AppGroup?>(null) }
val snackbar = remember { SnackbarHostState() }
val coroutine = rememberCoroutineScope()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
onGet() onGet()
input = chosenPackage.receive() input = chosenPackage.receive()
@@ -852,12 +858,25 @@ fun PackageFunctionScreen(
} }
} }
) )
},
snackbarHost = {
SnackbarHost(snackbar)
} }
) { paddingValues -> ) { paddingValues ->
LazyColumn(Modifier.padding(paddingValues)) { LazyColumn(Modifier.padding(paddingValues)) {
items(packages, { it.name }) { items(packages, { it.name }) {
ApplicationItem(it) { ApplicationItem(it) {
onSet(listOf(it.name), false) onSet(listOf(it.name), false)
coroutine.launch {
val result = snackbar.showSnackbar(
context.getString(R.string.package_removed, it.name),
context.getString(R.string.undo),
true, SnackbarDuration.Short
)
if (result == SnackbarResult.ActionPerformed) {
onSet(listOf(it.name), true)
}
}
} }
} }
item { item {

View File

@@ -152,7 +152,7 @@ fun NetworkScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
if(VERSION.SDK_INT >= 30) { if(VERSION.SDK_INT >= 30) {
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(NetworkOptions) } FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(NetworkOptions) }
} }
if (VERSION.SDK_INT >= 23 && !privilege.dhizuku) if (!privilege.dhizuku)
FunctionItem(R.string.network_stats, icon = R.drawable.query_stats_fill0) { onNavigate(QueryNetworkStats) } FunctionItem(R.string.network_stats, icon = R.drawable.query_stats_fill0) { onNavigate(QueryNetworkStats) }
if(VERSION.SDK_INT >= 29 && privilege.device) { if(VERSION.SDK_INT >= 29 && privilege.device) {
FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { onNavigate(PrivateDns) } FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { onNavigate(PrivateDns) }
@@ -937,7 +937,6 @@ enum class NetworkStatsState(val id: Int, val text: Int) {
Default(NetworkStats.Bucket.STATE_DEFAULT, R.string.default_str), Default(NetworkStats.Bucket.STATE_DEFAULT, R.string.default_str),
Foreground(NetworkStats.Bucket.STATE_FOREGROUND, R.string.foreground) Foreground(NetworkStats.Bucket.STATE_FOREGROUND, R.string.foreground)
} }
@RequiresApi(23)
enum class NetworkStatsUID(val uid: Int, val text: Int) { enum class NetworkStatsUID(val uid: Int, val text: Int) {
All(NetworkStats.Bucket.UID_ALL, R.string.all), All(NetworkStats.Bucket.UID_ALL, R.string.all),
Removed(NetworkStats.Bucket.UID_REMOVED, R.string.uninstalled), Removed(NetworkStats.Bucket.UID_REMOVED, R.string.uninstalled),
@@ -952,7 +951,6 @@ data class QueryNetworkStatsParams(
@Serializable object QueryNetworkStats @Serializable object QueryNetworkStats
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(23)
@Composable @Composable
fun NetworkStatsScreen( fun NetworkStatsScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, getUid: (String) -> Int, chosenPackage: Channel<String>, onChoosePackage: () -> Unit, getUid: (String) -> Int,
@@ -1270,7 +1268,6 @@ data class NetworkStatsData(
@Serializable object NetworkStatsViewer @Serializable object NetworkStatsViewer
@RequiresApi(23)
@Composable @Composable
fun NetworkStatsViewerScreen( fun NetworkStatsViewerScreen(
data: List<NetworkStatsData>, clearData: () -> Unit, onNavigateUp: () -> Unit data: List<NetworkStatsData>, clearData: () -> Unit, onNavigateUp: () -> Unit

View File

@@ -333,12 +333,10 @@ fun ResetPasswordScreen(resetPassword: (String, String, Int) -> Boolean, onNavig
visualTransformation = PasswordVisualTransformation() visualTransformation = PasswordVisualTransformation()
) )
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 23) {
CheckBoxItem( CheckBoxItem(
R.string.do_not_ask_credentials_on_boot, R.string.do_not_ask_credentials_on_boot,
flags and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0 flags and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0
) { flags = flags xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT } ) { flags = flags xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT }
}
CheckBoxItem( CheckBoxItem(
R.string.reset_password_require_entry, R.string.reset_password_require_entry,
flags and RESET_PASSWORD_REQUIRE_ENTRY != 0 flags and RESET_PASSWORD_REQUIRE_ENTRY != 0

View File

@@ -171,9 +171,7 @@ fun SystemManagerScreen(
FunctionItem(R.string.key_pairs, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("KeyPairs") }*/ FunctionItem(R.string.key_pairs, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("KeyPairs") }*/
if(VERSION.SDK_INT >= 35 && (privilege.device || (privilege.profile && privilege.affiliated))) if(VERSION.SDK_INT >= 35 && (privilege.device || (privilege.profile && privilege.affiliated)))
FunctionItem(R.string.content_protection_policy, icon = R.drawable.search_fill0) { onNavigate(ContentProtectionPolicy) } FunctionItem(R.string.content_protection_policy, icon = R.drawable.search_fill0) { onNavigate(ContentProtectionPolicy) }
if(VERSION.SDK_INT >= 23) {
FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { onNavigate(PermissionPolicy) } FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { onNavigate(PermissionPolicy) }
}
if(VERSION.SDK_INT >= 34 && privilege.device) { if(VERSION.SDK_INT >= 34 && privilege.device) {
FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { onNavigate(MtePolicy) } FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { onNavigate(MtePolicy) }
} }
@@ -204,7 +202,7 @@ fun SystemManagerScreen(
FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { onNavigate(SupportMessage) } FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { onNavigate(SupportMessage) }
} }
FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { onNavigate(DisableAccountManagement) } FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { onNavigate(DisableAccountManagement) }
if(VERSION.SDK_INT >= 23 && (privilege.device || privilege.org)) { if (privilege.device || privilege.org) {
FunctionItem(R.string.system_update_policy, icon = R.drawable.system_update_fill0) { onNavigate(SetSystemUpdatePolicy) } FunctionItem(R.string.system_update_policy, icon = R.drawable.system_update_fill0) { onNavigate(SetSystemUpdatePolicy) }
} }
if(VERSION.SDK_INT >= 29 && (privilege.device || privilege.org)) { if(VERSION.SDK_INT >= 29 && (privilege.device || privilege.org)) {
@@ -368,7 +366,7 @@ fun SystemOptionsScreen(vm: MyViewModel, onNavigateUp: () -> Unit) {
SwitchItem(R.string.enable_usb_signal, status.usbSignalEnabled, SwitchItem(R.string.enable_usb_signal, status.usbSignalEnabled,
vm::setUsbSignalEnabled, R.drawable.usb_fill0) vm::setUsbSignalEnabled, R.drawable.usb_fill0)
} }
if (VERSION.SDK_INT >= 23 && VERSION.SDK_INT < 34) { if (VERSION.SDK_INT < 34) {
Row( Row(
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding), Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -413,8 +411,8 @@ fun KeyguardScreen(
val context = LocalContext.current val context = LocalContext.current
val privilege by Privilege.status.collectAsStateWithLifecycle() val privilege by Privilege.status.collectAsStateWithLifecycle()
MyScaffold(R.string.keyguard, onNavigateUp) { MyScaffold(R.string.keyguard, onNavigateUp) {
if (VERSION.SDK_INT >= 23 && (privilege.device || if (privilege.device ||
(VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated))) { (VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated)) {
Row( Row(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@@ -435,7 +433,7 @@ fun KeyguardScreen(
Notes(R.string.info_disable_keyguard) Notes(R.string.info_disable_keyguard)
Spacer(Modifier.padding(vertical = 12.dp)) Spacer(Modifier.padding(vertical = 12.dp))
} }
if(VERSION.SDK_INT >= 23) Text(text = stringResource(R.string.lock_now), style = typography.headlineLarge) Text(text = stringResource(R.string.lock_now), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 2.dp)) Spacer(Modifier.padding(vertical = 2.dp))
var evictKey by rememberSaveable { mutableStateOf(false) } var evictKey by rememberSaveable { mutableStateOf(false) }
Button( Button(
@@ -1007,7 +1005,6 @@ fun ContentProtectionPolicyScreen(
@Serializable object PermissionPolicy @Serializable object PermissionPolicy
@RequiresApi(23)
@Composable @Composable
fun PermissionPolicyScreen( fun PermissionPolicyScreen(
getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit
@@ -1215,7 +1212,7 @@ private fun StartLockTaskMode(
var activity by rememberSaveable { mutableStateOf("") } var activity by rememberSaveable { mutableStateOf("") }
var specifyActivity by rememberSaveable { mutableStateOf(false) } var specifyActivity by rememberSaveable { mutableStateOf(false) }
var clearTask by rememberSaveable { mutableStateOf(true) } var clearTask by rememberSaveable { mutableStateOf(true) }
var showNotification by rememberSaveable() { mutableStateOf(true) } var showNotification by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
packageName = chosenPackage.receive() packageName = chosenPackage.receive()
} }
@@ -1785,7 +1782,7 @@ fun WipeDataScreen(
FullWidthCheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { FullWidthCheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) {
flag = flag xor WIPE_EXTERNAL_STORAGE flag = flag xor WIPE_EXTERNAL_STORAGE
} }
if(VERSION.SDK_INT >= 22 && privilege.device) FullWidthCheckBoxItem( if (privilege.device) FullWidthCheckBoxItem(
R.string.wipe_reset_protection_data, flag and WIPE_RESET_PROTECTION_DATA != 0) { R.string.wipe_reset_protection_data, flag and WIPE_RESET_PROTECTION_DATA != 0) {
flag = flag xor WIPE_RESET_PROTECTION_DATA flag = flag xor WIPE_RESET_PROTECTION_DATA
} }
@@ -1839,7 +1836,7 @@ fun WipeDataScreen(
text = { text = {
Text( Text(
text = stringResource( text = stringResource(
if(VERSION.SDK_INT >= 23 && userManager.isSystemUser) R.string.wipe_data_warning if (userManager.isSystemUser) R.string.wipe_data_warning
else R.string.info_wipe_data_in_managed_user else R.string.info_wipe_data_in_managed_user
), ),
color = colorScheme.error color = colorScheme.error
@@ -1880,7 +1877,6 @@ data class PendingSystemUpdateInfo(val exists: Boolean, val time: Long, val secu
@Serializable object SetSystemUpdatePolicy @Serializable object SetSystemUpdatePolicy
@RequiresApi(23)
@Composable @Composable
fun SystemUpdatePolicyScreen( fun SystemUpdatePolicyScreen(
getPolicy: () -> SystemUpdatePolicyInfo, setPolicy: (SystemUpdatePolicyInfo) -> Unit, getPolicy: () -> SystemUpdatePolicyInfo, setPolicy: (SystemUpdatePolicyInfo) -> Unit,

View File

@@ -30,12 +30,12 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SegmentedButtonDefaults
@@ -105,7 +105,6 @@ fun UsersScreen(vm: MyViewModel, onNavigateUp: () -> Unit, onNavigate: (Any) ->
FunctionItem(R.string.create_user, icon = R.drawable.person_add_fill0) { onNavigate(CreateUser) } FunctionItem(R.string.create_user, icon = R.drawable.person_add_fill0) { onNavigate(CreateUser) }
} }
FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) } FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) }
if(VERSION.SDK_INT >= 23) {
var changeUserIconDialog by remember { mutableStateOf(false) } var changeUserIconDialog by remember { mutableStateOf(false) }
var bitmap: Bitmap? by remember { mutableStateOf(null) } var bitmap: Bitmap? by remember { mutableStateOf(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
@@ -123,7 +122,6 @@ fun UsersScreen(vm: MyViewModel, onNavigateUp: () -> Unit, onNavigate: (Any) ->
vm.setUserIcon(bitmap!!) vm.setUserIcon(bitmap!!)
changeUserIconDialog = false changeUserIconDialog = false
}) { changeUserIconDialog = false } }) { changeUserIconDialog = false }
}
if(VERSION.SDK_INT >= 28 && privilege.device) { if(VERSION.SDK_INT >= 28 && privilege.device) {
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) } FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) }
} }
@@ -191,7 +189,7 @@ fun UserInfoScreen(getInfo: () -> UserInformation, onNavigateUp: () -> Unit) {
if (VERSION.SDK_INT >= 24) InfoItem(R.string.support_multiuser, info.multiUser.yesOrNo) if (VERSION.SDK_INT >= 24) InfoItem(R.string.support_multiuser, info.multiUser.yesOrNo)
if (VERSION.SDK_INT >= 31) InfoItem(R.string.headless_system_user_mode, info.headless.yesOrNo, true) { infoDialog = 1 } if (VERSION.SDK_INT >= 31) InfoItem(R.string.headless_system_user_mode, info.headless.yesOrNo, true) { infoDialog = 1 }
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
if (VERSION.SDK_INT >= 23) InfoItem(R.string.system_user, info.system.yesOrNo) InfoItem(R.string.system_user, info.system.yesOrNo)
if (VERSION.SDK_INT >= 34) InfoItem(R.string.admin_user, info.admin.yesOrNo) if (VERSION.SDK_INT >= 34) InfoItem(R.string.admin_user, info.admin.yesOrNo)
if (VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, info.demo.yesOrNo) if (VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, info.demo.yesOrNo)
if (info.time != 0L) InfoItem(R.string.creation_time, formatDate(info.time)) if (info.time != 0L) InfoItem(R.string.creation_time, formatDate(info.time))
@@ -264,7 +262,7 @@ fun UserOperationScreen(
input, { input = it }, input, { input = it },
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.menuAnchor(MenuAnchorType.PrimaryEditable) .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable)
.padding(top = 4.dp, bottom = 8.dp), .padding(top = 4.dp, bottom = 8.dp),
label = { label = {
Text(stringResource(if(useUserId) R.string.user_id else R.string.serial_number)) Text(stringResource(if(useUserId) R.string.user_id else R.string.serial_number))
@@ -586,7 +584,6 @@ fun UserSessionMessageScreen(
} }
} }
@RequiresApi(23)
@Composable @Composable
private fun ChangeUserIconDialog(bitmap: Bitmap, onSet: () -> Unit, onClose: () -> Unit) { private fun ChangeUserIconDialog(bitmap: Bitmap, onSet: () -> Unit, onClose: () -> Unit) {
AlertDialog( AlertDialog(

View File

@@ -101,7 +101,6 @@ fun CreateWorkProfileScreen(
var migrateAccountName by remember { mutableStateOf("") } var migrateAccountName by remember { mutableStateOf("") }
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) {
FullWidthCheckBoxItem(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()
@@ -128,7 +127,6 @@ fun CreateWorkProfileScreen(
} }
} }
} }
}
if (VERSION.SDK_INT >= 24) FullWidthCheckBoxItem( if (VERSION.SDK_INT >= 24) FullWidthCheckBoxItem(
R.string.skip_encryption, skipEncrypt R.string.skip_encryption, skipEncrypt
) { skipEncrypt = it } ) { skipEncrypt = it }

View File

@@ -68,6 +68,7 @@
<string name="timeout">超时</string> <string name="timeout">超时</string>
<string name="continue_str">继续</string> <string name="continue_str">继续</string>
<string name="exit">退出</string> <string name="exit">退出</string>
<string name="undo">撤销</string>
<!--Permissions--> <!--Permissions-->
<string name="profile_owner">Profile owner</string> <string name="profile_owner">Profile owner</string>
@@ -381,6 +382,7 @@
<string name="managed_configuration">托管配置</string> <string name="managed_configuration">托管配置</string>
<string name="clear_configurations">清除配置</string> <string name="clear_configurations">清除配置</string>
<string name="specify_value">指定值</string> <string name="specify_value">指定值</string>
<string name="package_removed">移除了 %1$s</string>
<!--UserRestriction--> <!--UserRestriction-->
<string name="user_restriction">用户限制</string> <string name="user_restriction">用户限制</string>

View File

@@ -73,6 +73,7 @@
<string name="timeout">Timeout</string> <string name="timeout">Timeout</string>
<string name="continue_str">Continue</string> <string name="continue_str">Continue</string>
<string name="exit">Exit</string> <string name="exit">Exit</string>
<string name="undo">Undo</string>
<!--Permissions--> <!--Permissions-->
<string name="profile_owner">Profile owner</string> <string name="profile_owner">Profile owner</string>
@@ -415,6 +416,7 @@
<string name="managed_configuration">Managed configuration</string> <string name="managed_configuration">Managed configuration</string>
<string name="clear_configurations">Clear configurations</string> <string name="clear_configurations">Clear configurations</string>
<string name="specify_value">Specify value</string> <string name="specify_value">Specify value</string>
<string name="package_removed">Removed %1$s</string>
<!--UserRestriction--> <!--UserRestriction-->
<string name="user_restriction">User restriction</string> <string name="user_restriction">User restriction</string>