From c678fa72a9c2c9144ef7ca45fc3a8ef370ea24cd Mon Sep 17 00:00:00 2001 From: PiX <69745008+pixincreate@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:51:09 +0530 Subject: [PATCH 1/2] Hide direct install and uninstall when bootloader is locked On devices with locked bootloaders and integrated Magisk root (e.g. GrapheneOS with Magisk), direct install to boot partition and uninstall operations present a softbrick risk. Only allow patching a file (which the user handles externally) in such environments. - Add isBootloaderLocked detection via ro.boot.vbmeta.device_state - apk-ng: Hide Direct Install and Install to Inactive Slot when locked - apk-ng: Hide Uninstall button when locked - apk (legacy): Same changes for the older UI --- .../java/com/topjohnwu/magisk/ui/home/HomeScreen.kt | 10 ++++++---- .../topjohnwu/magisk/ui/install/InstallBottomSheet.kt | 2 +- .../topjohnwu/magisk/ui/install/InstallViewModel.kt | 3 ++- .../topjohnwu/magisk/ui/install/InstallViewModel.kt | 3 ++- app/apk/src/main/res/layout/fragment_home_md2.xml | 2 +- app/apk/src/main/res/layout/fragment_install_md2.xml | 2 +- .../src/main/java/com/topjohnwu/magisk/core/Info.kt | 7 +++++++ 7 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/home/HomeScreen.kt b/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/home/HomeScreen.kt index 18d5247daac2e..d5e96857f10bd 100644 --- a/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/home/HomeScreen.kt +++ b/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/home/HomeScreen.kt @@ -230,10 +230,12 @@ fun HomeScreen(viewModel: HomeViewModel, installVm: InstallViewModel) { onHideRestorePressed = viewModel::onHideRestorePressed, ) - UninstallButton( - onClick = { viewModel.onDeletePressed() }, - enabled = Info.env.isActive - ) + if (!Info.isBootloaderLocked) { + UninstallButton( + onClick = { viewModel.onDeletePressed() }, + enabled = Info.env.isActive + ) + } Text( text = stringResource(CoreR.string.home_support_title), diff --git a/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallBottomSheet.kt b/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallBottomSheet.kt index 5854df2730a59..eaf07a23c214c 100644 --- a/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallBottomSheet.kt +++ b/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallBottomSheet.kt @@ -129,7 +129,7 @@ fun InstallBottomSheet( }, ) - if (installVm.isRooted) { + if (installVm.isRooted && !installVm.isBootloaderLocked) { SettingsArrow( title = stringResource(CoreR.string.direct_install), onClick = { diff --git a/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt b/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt index 0605c90ef724e..921fc7c9efe17 100644 --- a/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt +++ b/app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt @@ -38,8 +38,9 @@ class InstallViewModel(svc: NetworkService) : BaseViewModel() { ) val isRooted get() = Info.isRooted + val isBootloaderLocked get() = Info.isBootloaderLocked val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && Info.ramdisk) - val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator + val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator || isBootloaderLocked private val _uiState = MutableStateFlow(UiState(step = if (skipOptions) 1 else 0)) val uiState: StateFlow = _uiState.asStateFlow() diff --git a/app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt b/app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt index b6a64dffd3470..f897dca037aeb 100644 --- a/app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt +++ b/app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt @@ -38,8 +38,9 @@ import com.topjohnwu.magisk.core.R as CoreR class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel() { val isRooted get() = Info.isRooted + val isBootloaderLocked get() = Info.isBootloaderLocked val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && Info.ramdisk) - val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator + val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator || isBootloaderLocked @get:Bindable var step = if (skipOptions) 1 else 0 diff --git a/app/apk/src/main/res/layout/fragment_home_md2.xml b/app/apk/src/main/res/layout/fragment_home_md2.xml index a3d2c60cd5618..dd885a4964fd2 100644 --- a/app/apk/src/main/res/layout/fragment_home_md2.xml +++ b/app/apk/src/main/res/layout/fragment_home_md2.xml @@ -114,7 +114,7 @@