diff --git a/app/src/main/java/me/bmax/apatch/ui/component/SearchBar.kt b/app/src/main/java/me/bmax/apatch/ui/component/SearchBar.kt index d81515a57..250d7dbc5 100644 --- a/app/src/main/java/me/bmax/apatch/ui/component/SearchBar.kt +++ b/app/src/main/java/me/bmax/apatch/ui/component/SearchBar.kt @@ -51,6 +51,7 @@ fun SearchAppBar( onBackClick: (() -> Unit)? = null, onConfirm: (() -> Unit)? = null, dropdownContent: @Composable (() -> Unit)? = null, + leadingActions: @Composable (() -> Unit)? = null, ) { val keyboardController = LocalSoftwareKeyboardController.current val focusRequester = remember { FocusRequester() } @@ -144,10 +145,15 @@ fun SearchAppBar( AnimatedVisibility( visible = !onSearch ) { - IconButton( - onClick = { onSearch = true }, - content = { Icon(Icons.Filled.Search, null) } - ) + androidx.compose.foundation.layout.Row( + verticalAlignment = Alignment.CenterVertically + ) { + leadingActions?.invoke() + IconButton( + onClick = { onSearch = true }, + content = { Icon(Icons.Filled.Search, null) } + ) + } } dropdownContent?.invoke() diff --git a/app/src/main/java/me/bmax/apatch/ui/screen/SuperUser.kt b/app/src/main/java/me/bmax/apatch/ui/screen/SuperUser.kt index 6dea0eda7..dceee1f52 100644 --- a/app/src/main/java/me/bmax/apatch/ui/screen/SuperUser.kt +++ b/app/src/main/java/me/bmax/apatch/ui/screen/SuperUser.kt @@ -18,7 +18,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.PlaylistAddCheck import androidx.compose.material.icons.filled.Security +import androidx.compose.material3.AlertDialog import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -28,6 +30,7 @@ import androidx.compose.material3.ListItem import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -66,6 +69,31 @@ import me.bmax.apatch.util.PkgConfig fun SuperUserScreen() { val viewModel = viewModel() val scope = rememberCoroutineScope() + var showBatchExcludeDialog by remember { mutableStateOf(false) } + + if (showBatchExcludeDialog) { + AlertDialog( + onDismissRequest = { showBatchExcludeDialog = false }, + title = { Text(stringResource(R.string.su_batch_exclude_title)) }, + text = { Text(stringResource(R.string.su_batch_exclude_content)) }, + confirmButton = { + TextButton(onClick = { + viewModel.excludeAll() + showBatchExcludeDialog = false + }) { + Text(stringResource(R.string.su_exclude_btn)) + } + }, + dismissButton = { + TextButton(onClick = { + viewModel.reverseExcludeAll() + showBatchExcludeDialog = false + }) { + Text(stringResource(R.string.su_exclude_reverse_btn)) + } + } + ) + } LaunchedEffect(Unit) { if (viewModel.appList.isEmpty()) { @@ -80,6 +108,11 @@ fun SuperUserScreen() { searchText = viewModel.search, onSearchTextChange = { viewModel.search = it }, onClearClick = { viewModel.search = "" }, + leadingActions = { + IconButton(onClick = { showBatchExcludeDialog = true }) { + Icon(Icons.Filled.PlaylistAddCheck, null) + } + }, dropdownContent = { var showDropdown by remember { mutableStateOf(false) } diff --git a/app/src/main/java/me/bmax/apatch/ui/viewmodel/SuperUserViewModel.kt b/app/src/main/java/me/bmax/apatch/ui/viewmodel/SuperUserViewModel.kt index 76afcbdf6..797b0e256 100644 --- a/app/src/main/java/me/bmax/apatch/ui/viewmodel/SuperUserViewModel.kt +++ b/app/src/main/java/me/bmax/apatch/ui/viewmodel/SuperUserViewModel.kt @@ -17,6 +17,7 @@ import com.topjohnwu.superuser.Shell import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize +import me.bmax.apatch.APApplication import me.bmax.apatch.IAPRootService import me.bmax.apatch.Natives import me.bmax.apatch.apApp @@ -98,6 +99,49 @@ class SuperUserViewModel : ViewModel() { task?.let { it1 -> shell.execTask(it1) } } + fun excludeAll() { + val modifiedConfigs = mutableListOf() + val currentApps = apps + + currentApps.forEach { app -> + if (app.config.allow == 0 && app.config.exclude == 0) { + app.config.exclude = 1 + app.config.profile.scontext = APApplication.DEFAULT_SCONTEXT + Natives.setUidExclude(app.uid, 1) + modifiedConfigs.add(app.config) + } + } + + if (modifiedConfigs.isNotEmpty()) { + PkgConfig.batchChangeConfigs(modifiedConfigs) + // Force UI update + apps = ArrayList(currentApps) + } + } + + fun reverseExcludeAll() { + val modifiedConfigs = mutableListOf() + val currentApps = apps + + currentApps.forEach { app -> + if (app.config.allow == 0) { + val newExclude = if (app.config.exclude == 1) 0 else 1 + app.config.exclude = newExclude + if (newExclude == 1) { + app.config.profile.scontext = APApplication.DEFAULT_SCONTEXT + } + Natives.setUidExclude(app.uid, newExclude) + modifiedConfigs.add(app.config) + } + } + + if (modifiedConfigs.isNotEmpty()) { + PkgConfig.batchChangeConfigs(modifiedConfigs) + // Force UI update + apps = ArrayList(currentApps) + } + } + private fun stopRootService() { val intent = Intent(apApp, RootServices::class.java) RootServices.stop(intent) diff --git a/app/src/main/java/me/bmax/apatch/util/PkgConfig.kt b/app/src/main/java/me/bmax/apatch/util/PkgConfig.kt index dd3afdae4..967bd07e4 100644 --- a/app/src/main/java/me/bmax/apatch/util/PkgConfig.kt +++ b/app/src/main/java/me/bmax/apatch/util/PkgConfig.kt @@ -88,4 +88,28 @@ object PkgConfig { } } } + + fun batchChangeConfigs(newConfigs: List) { + thread { + synchronized(PkgConfig.javaClass) { + Natives.su() + val configs = readConfigs() + + newConfigs.forEach { config -> + val uid = config.profile.uid + // Root App should not be excluded + if (config.allow == 1) { + config.exclude = 0 + } + + if (config.isDefault() && configs[uid] != null) { + configs.remove(uid) + } else { + configs[uid] = config + } + } + writeConfigs(configs) + } + } + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3d6350eaf..aebf2fc87 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,6 +140,10 @@ Show system apps Hide system apps Refresh + Batch Exclude + Exclude injection for all non-ROOT apps, please select an action + Exclude + Reverse APModule AndroidPatch not installed