From f636cd1a2a924996c73c8e8f35ec4733709a8e60 Mon Sep 17 00:00:00 2001 From: oxmc <67136658+oxmc@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:20:18 -0700 Subject: [PATCH] SetupWizard: hide + lock system UI during setup Decouple the setup UI hiding from the managed kiosk lockdown: - New WizardFlags.IMMERSIVE (default true): hide the status/nav bars during setup (sticky immersive) independent of KIOSK. KIOSK stays the aggressive managed lockdown (overlay window + non-sticky + future lock-task). - BaseKioskActivity / MainActivity hide the bars when IMMERSIVE || KIOSK. - Add StatusBarManager.setDisabledForSetup(true) on wizard start (EntryPoint + MainActivity) and setDisabledForSetup(false) in finishSetupWizard, so the shade/quick-settings/Home/Recents are actually locked during setup (matches LineageOS). Called via reflection so the module still builds under Gradle (the API is @SystemApi). --- .../setupwizard/activities/MainActivity.kt | 16 ++++++++++-- .../java/dev/oxmc/setupwizard/entryPoint.kt | 6 +++++ .../oxmc/setupwizard/lib/KioskBaseActivity.kt | 14 ++++++---- .../dev/oxmc/setupwizard/lib/WizardFlags.kt | 13 +++++++--- .../oxmc/setupwizard/lib/utils/SetupUtils.kt | 26 +++++++++++++++++++ 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/dev/oxmc/setupwizard/activities/MainActivity.kt b/app/src/main/java/dev/oxmc/setupwizard/activities/MainActivity.kt index 1692627..489210d 100644 --- a/app/src/main/java/dev/oxmc/setupwizard/activities/MainActivity.kt +++ b/app/src/main/java/dev/oxmc/setupwizard/activities/MainActivity.kt @@ -15,6 +15,7 @@ import me.pawlet.setupwizard.lib.BaseKioskActivity import me.pawlet.setupwizard.lib.SetupWizardManager import me.pawlet.setupwizard.lib.WizardFlags import me.pawlet.setupwizard.lib.buildBuiltinPages +import me.pawlet.setupwizard.lib.utils.SetupUtils import me.pawlet.setupwizard.ui.screens.AboutDeviceScreen import me.pawlet.setupwizard.ui.screens.AndroidVersionScreen import me.pawlet.setupwizard.ui.theme.MainTheme @@ -23,12 +24,20 @@ class MainActivity : BaseKioskActivity() { private lateinit var wizardManager: SetupWizardManager override fun onCreate(savedInstanceState: Bundle?) { + // Kiosk needs the overlay window type set before setContent; the actual + // bar-hiding (immersive or kiosk) is applied in onResume via + // BaseKioskActivity for both KIOSK and IMMERSIVE. if (WizardFlags.KIOSK) { FullScreenHelper.prepareKioskWindow(this) - FullScreenHelper.enableKioskMode(this) } super.onCreate(savedInstanceState) + // Lock the status bar (shade/home/recents) for the duration of setup; + // restored in SetupUtils.finishSetupWizard(). + if (WizardFlags.KIOSK || WizardFlags.IMMERSIVE) { + SetupUtils().disableStatusBarForSetup(this) + } + wizardManager = SetupWizardManager.getInstance(this) wizardManager.registerPages( buildBuiltinPages( @@ -46,7 +55,10 @@ class MainActivity : BaseKioskActivity() { val deviceInfo by wizardManager.deviceInfo.collectAsState() LaunchedEffect(index) { - if (WizardFlags.KIOSK) FullScreenHelper.enableKioskMode(this@MainActivity) + // Re-assert hidden system bars when the page changes. + if (WizardFlags.KIOSK || WizardFlags.IMMERSIVE) { + FullScreenHelper.onResume(this@MainActivity, WizardFlags.KIOSK) + } } when (overlay) { diff --git a/app/src/main/java/dev/oxmc/setupwizard/entryPoint.kt b/app/src/main/java/dev/oxmc/setupwizard/entryPoint.kt index 2178a66..dd0b868 100644 --- a/app/src/main/java/dev/oxmc/setupwizard/entryPoint.kt +++ b/app/src/main/java/dev/oxmc/setupwizard/entryPoint.kt @@ -15,6 +15,7 @@ import me.pawlet.setupwizard.lib.SetupWizardManager import me.pawlet.setupwizard.lib.WizardFlags import me.pawlet.setupwizard.lib.internal.PrefManager import me.pawlet.setupwizard.lib.internal.UriHandler +import me.pawlet.setupwizard.lib.utils.SetupUtils import me.pawlet.setupwizard.ui.screens.SplashScreen import me.pawlet.setupwizard.ui.theme.MainTheme import kotlinx.coroutines.runBlocking @@ -41,6 +42,11 @@ class EntryPoint : BaseKioskActivity() { super.onCreate(savedInstanceState) + // Lock the status bar for setup as early as the splash. + if (WizardFlags.KIOSK || WizardFlags.IMMERSIVE) { + SetupUtils().disableStatusBarForSetup(this) + } + // Initialize managers prefs = PrefManager(this) uriHandler = UriHandler(this) diff --git a/app/src/main/java/dev/oxmc/setupwizard/lib/KioskBaseActivity.kt b/app/src/main/java/dev/oxmc/setupwizard/lib/KioskBaseActivity.kt index 0768aa7..599404f 100644 --- a/app/src/main/java/dev/oxmc/setupwizard/lib/KioskBaseActivity.kt +++ b/app/src/main/java/dev/oxmc/setupwizard/lib/KioskBaseActivity.kt @@ -6,14 +6,18 @@ import me.pawlet.setupwizard.lib.FullScreenHelper open class BaseKioskActivity : ComponentActivity() { override fun onResume() { super.onResume() - // When kiosk is off we intentionally do NOT touch FullScreenHelper: - // onResume(_, false) still applies immersive mode (hides the bars), so - // the only way to show normal system bars is to skip the call entirely. - if (WizardFlags.KIOSK) FullScreenHelper.onResume(this, true) + // Hide the system bars during setup (immersive), or full kiosk when + // enabled. onResume(_, isKioskMode) hides the bars either way; the bool + // only picks sticky-immersive vs non-sticky kiosk style. + if (WizardFlags.KIOSK || WizardFlags.IMMERSIVE) { + FullScreenHelper.onResume(this, WizardFlags.KIOSK) + } } override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) - if (WizardFlags.KIOSK) FullScreenHelper.onWindowFocusChanged(this, hasFocus, true) + if (WizardFlags.KIOSK || WizardFlags.IMMERSIVE) { + FullScreenHelper.onWindowFocusChanged(this, hasFocus, WizardFlags.KIOSK) + } } } \ No newline at end of file diff --git a/app/src/main/java/dev/oxmc/setupwizard/lib/WizardFlags.kt b/app/src/main/java/dev/oxmc/setupwizard/lib/WizardFlags.kt index 672332a..edd9afa 100644 --- a/app/src/main/java/dev/oxmc/setupwizard/lib/WizardFlags.kt +++ b/app/src/main/java/dev/oxmc/setupwizard/lib/WizardFlags.kt @@ -6,12 +6,19 @@ package me.pawlet.setupwizard.lib */ object WizardFlags { /** - * Kiosk / immersive lock-down (hides system bars, re-asserts on focus). - * Intended for managed/enrolled devices; disabled for the normal first-run - * experience so users can use the status bar and back gesture. + * Kiosk lock-down: overlay window type + non-sticky immersive + (future) + * lock-task, so the user cannot leave or reveal the bars. Intended for + * managed/enrolled devices. Off for the normal first-run experience. */ const val KIOSK = false + /** + * Hide the system bars (status + navigation) during setup — standard + * first-run behaviour, independent of the managed KIOSK lockdown. Uses + * sticky immersive when KIOSK is off. Turn off only for debugging. + */ + const val IMMERSIVE = true + /** * Deep-link / URI entry points (oxn:// IPC scheme and http(s) VIEW links). * Off for now — the wizard is launched only as the SETUP_WIZARD HOME app. diff --git a/app/src/main/java/dev/oxmc/setupwizard/lib/utils/SetupUtils.kt b/app/src/main/java/dev/oxmc/setupwizard/lib/utils/SetupUtils.kt index feb836e..d50cde7 100644 --- a/app/src/main/java/dev/oxmc/setupwizard/lib/utils/SetupUtils.kt +++ b/app/src/main/java/dev/oxmc/setupwizard/lib/utils/SetupUtils.kt @@ -533,6 +533,29 @@ class SetupUtils { } } + /** + * Lock the status bar for setup: disables the notification shade / quick + * settings, Home, Recents, and assist so the user cannot leave setup. Uses + * the platform's setup API (StatusBarManager.setDisabledForSetup, a + * @SystemApi needing STATUS_BAR — which the wizard holds) via reflection so + * the module still builds under Gradle, where that API isn't on the SDK. + */ + fun disableStatusBarForSetup(context: Context) = setStatusBarDisabledForSetup(context, true) + + /** Restore the status bar locked by [disableStatusBarForSetup]. */ + fun enableStatusBar(context: Context) = setStatusBarDisabledForSetup(context, false) + + private fun setStatusBarDisabledForSetup(context: Context, disabled: Boolean) { + try { + val sbm = context.getSystemService("statusbar") ?: return + Class.forName("android.app.StatusBarManager") + .getMethod("setDisabledForSetup", java.lang.Boolean.TYPE) + .invoke(sbm, disabled) + } catch (e: Exception) { + Log.w(TAG, "setDisabledForSetup($disabled) failed", e) + } + } + fun finishSetupWizard(context: Context) { if (LOGV) { Log.v(TAG, "finishSetupWizard") @@ -566,6 +589,9 @@ class SetupUtils { WallpaperManager.getInstance(context).forgetLoadedWallpaper() disableHome(context) + // Restore the status bar that was locked for setup. + enableStatusBar(context) + // Notify partner/system apps (e.g. ConfigProvisioner) that setup is done // so they can apply their own customizations. Guarded by FINISH_SETUP. runCatching {