Skip to content

App Block: sideload-aware Restricted Settings flow and in-app permission guides #47

Description

@marius4lui

Summary

Make the App Block permission setup resilient to Android's Restricted Settings model and provide an in-app, device-aware guide instead of sending users into system settings without enough context.

This matters especially for Habiter because Android builds are frequently installed directly as APKs / from GitHub rather than through Google Play. For covered apps installed from a downloaded or local file, modern Android can block sensitive special access until the user explicitly allows restricted settings from Habiter's App info page. Android 17 explicitly lists Usage Access (OPSTR_GET_USAGE_STATS) and Display over other apps (OPSTR_SYSTEM_ALERT_WINDOW) among the restricted special permissions for covered/sideloaded apps.

The app must not bypass or weaken Android security. It should explain the state, route the user to the correct supported settings surface, re-check on return, and provide verified instructions for common Android/OEM variants.

Related: #12, #10, #22, #38


Current problem

The existing App Block onboarding already has a dedicated Usage Access education screen:

apps/habiter/lib/features/app_lock/presentation/onboarding/usage_access_education_page.dart

Its primary CTA currently calls:

controller.requestUsageAccess();

The controller then opens the OS permission surface. On resume, reconcilePermissions() only has a positive path:

if (_state.stage == AppBlockOnboardingStage.usageEducation &&
    value.usageAccess) {
  await discover();
}

If access is still unavailable, the user is effectively left at the same onboarding step with no explanation of why Android refused the toggle and no recovery path.

A typical sideloaded install can therefore become:

Habiter
  -> "Analyze apps"
  -> Android Usage Access settings
  -> Habiter toggle unavailable / blocked
  -> user returns
  -> Habiter asks for the same permission again
  -> dead end

That is not sufficient for a permission that is a prerequisite for App Block discovery.


Platform behavior that must be handled

Google documents Restricted Settings on Android 13+ and instructs users to enable them from:

Settings
  -> Apps
  -> Habiter / App info
  -> More / three-dot menu
  -> Allow restricted settings

The user then returns to the original special-access page and grants the requested access.

The Android 17 Compatibility Definition is even more explicit for sideloaded/downloaded-file installs:

  • apps installed from a downloaded file or local file are covered applications;
  • Usage Access is an enforced restricted setting;
  • Display over other apps is an enforced restricted setting;
  • confirmation must be obtained from the covered application's AppInfo page;
  • implementations are expected to use the platform's restricted-setting confirmation model rather than apps bypassing it.

References:


Goal

Turn permission setup into a stateful system-setup flow rather than a one-shot deep link.

Habiter should be able to distinguish at product level between:

granted
requestable
returnedWithoutGrant
likelyRestrictedByInstallSource
restrictedByPolicyOrDevice
unsupported
unknown

The exact native implementation may use a richer typed model, but the UI must never collapse all non-granted states into "try the same button again".


1. Sideload-aware Usage Access flow

Recommended journey:

Usage Access education
  -> Open Usage Access settings
  -> app goes to background
  -> user returns
  -> reconcile actual permission state
      |
      +-- granted -> continue to local app analysis
      |
      +-- still denied -> show contextual recovery UI
                           |
                           +-- sideload/restricted likely
                           |    -> explain Restricted Settings
                           |    -> Open Habiter App info
                           |    -> guide: More -> Allow restricted settings
                           |    -> return -> reopen Usage Access
                           |
                           +-- generic denial / unknown OEM
                                -> show manual guide + settings shortcuts

Requirements

  • Re-check permission state every time the app resumes from Settings.
  • If the permission is granted, advance automatically without another redundant tap.
  • If it remains unavailable, do not immediately send the user back to the same screen.
  • Present a clear recovery card/page after the first unsuccessful return.
  • Explain that the extra Android restriction is related to how the app was installed, not that Habiter itself can override it.
  • Keep Maybe later / opt-out available.
  • If access is revoked later, reuse the same recovery system instead of creating a second flow.

2. Restricted Settings guide inside Habiter

Add an in-app page or bottom-sheet flow such as:

Android blocked this permission

Explain in plain language:

  • Habiter needs Usage Access only to read local app-usage timing used for distraction recommendations / App Block.
  • Android may require an extra confirmation for apps installed directly from an APK or another non-store source.
  • The user should only enable Restricted Settings if they trust the Habiter build/source they installed.
  • Habiter cannot and must not disable this Android protection automatically.

Primary action:

Open Habiter app info

Then show the expected steps:

1. Open the More / three-dot menu.
2. Tap "Allow restricted settings".
3. Confirm with the device security prompt if requested.
4. Return to Habiter.
5. Habiter reopens / offers Usage Access and verifies the result.

Secondary actions:

Show steps for my device
Try Usage Access again
Maybe later

Do not instruct users to disable Play Protect, globally disable security protections, enable Developer Options, or broadly allow unknown-source installs as a workaround.


3. Install-source detection is a hint, not the source of truth

Use public Android APIs where available to inspect install-source/package-source information and improve the initial guidance.

Possible signals include PackageManager install-source/package-source metadata on supported API levels.

Rules:

  • Treat the detected install source as a hint.
  • A Play/store install must not be assumed to be unrestricted.
  • A sideload install must not be assumed to be blocked on every device/version.
  • Actual permission state after returning from Settings remains authoritative.
  • Do not use hidden/private APIs merely to detect Restricted Settings.
  • If the platform offers a supported API to determine restricted-setting state on a given API level, isolate it behind the native capability layer and provide a safe fallback.
  • Unknown source/device states must degrade to a generic manual guide, never a broken flow.

4. Add a reusable system-permission guide model

Avoid hard-coding this as a one-off wall of text inside UsageAccessEducationPage.

Introduce a small typed permission/setup guide abstraction that can later be reused by:

Conceptual direction:

SystemCapabilityGuide
├── capability
├── platform
├── Android API range
├── device/OEM family
├── current status
├── title / explanation
├── ordered manual steps
├── supported settings actions
└── fallback guidance

Do not duplicate permission semantics in every onboarding page.


5. Device / OS-specific in-app guide pages

The Restricted Settings mechanism is Android-specific, but the exact Settings navigation and labels vary across OEM skins and Android releases.

Create maintained guide variants for at least:

Android / Pixel / AOSP
Samsung One UI
Xiaomi HyperOS / MIUI
OnePlus / Oppo (OxygenOS / ColorOS)
Nothing OS
Motorola / near-stock Android
Generic Android fallback

The first implementation does not need to perfectly fingerprint every OEM. Device/manufacturer/version detection can choose a likely guide, while the user can manually switch to another guide/fallback.

Each guide should contain:

  • tested Android / OEM version range;
  • exact path to Habiter App info;
  • where the More/overflow action is expected;
  • how to return to Usage Access;
  • screenshots or illustrations only when verified and maintainable;
  • fallback wording for devices where Allow restricted settings is absent or moved;
  • a note that work/school/parental/device-management policy may prevent the setting entirely.

Samsung and OnePlus community reports confirm that OEM paths often still use the App info overflow menu, but there are also version-specific reports where the option moves/disappears. Therefore do not assume one static OEM path forever; guides need version metadata and a generic fallback.

Desktop platforms

Usage Access / Android Restricted Settings are not desktop concepts. Windows, macOS, and Linux must not receive fake equivalent instructions.

If the reusable guide center is exposed outside App Block, it may include platform-specific setup pages only for capabilities that actually exist on those platforms. This issue's required first-class guide matrix is Android because that is where this permission flow exists.


6. Repository documentation as a maintained source

Add a dedicated permission/system-setup documentation section, for example:

docs/guide/permissions/
├── index.md
└── android/
    ├── index.md
    ├── pixel-aosp.md
    ├── samsung-one-ui.md
    ├── xiaomi-hyperos-miui.md
    ├── oneplus-oppo.md
    ├── nothing-os.md
    ├── motorola.md
    └── generic.md

The app's short in-flow guide and the long-form docs should share the same terminology and verified navigation paths. Avoid maintaining two contradictory sets of instructions.

Where practical, keep guide metadata/copy in a structured source that can feed both the app and documentation, or add tests that ensure the documented variants stay in sync with the app registry.

Update VitePress navigation if these become public docs pages.


7. Overlay permission must use the same recovery pattern

App Block also requests overlay/display-over-other-apps access. Android 17 explicitly treats this as another enforced restricted setting for covered/sideloaded apps.

Therefore overlay_education_page.dart must not remain a separate one-shot implementation that can reproduce the same dead end later.

Apply the same lifecycle:

explain -> open Settings -> resume -> reconcile -> contextual recovery -> retry

The copy must explain the actual purpose of the overlay and should not imply broader access than Habiter uses.


8. UX requirements

  • No raw Android jargon on the first permission screen unless needed.
  • Explain the reason before sending the user into Settings.
  • After a failed return, explicitly say what happened and what to do next.
  • Use visual step indicators / small device-specific illustrations where useful.
  • Preserve onboarding state while external Settings are open.
  • Returning to Habiter must not restart the whole subflow.
  • Never show an infinite redirect loop.
  • Provide a manual fallback when a settings intent is unavailable.
  • Localize all user-facing copy.
  • Support TalkBack, large text, and reduced motion.

Suggested failure-state CTA hierarchy:

Primary:   Fix Android permission
Secondary: Show manual steps
Tertiary:  Maybe later

9. Native / architecture requirements

Keep OS knowledge inside the platform capability layer rather than scattering Android checks throughout Flutter widgets.

Suggested responsibilities:

Flutter onboarding UI
        ↓
permission/setup coordinator
        ↓
AppLockGateway / shared system capability gateway
        ↓
Android implementation
        ↓
PackageManager + Settings intents + AppOps/permission state

The coordinator should own:

  • request attempt count / last external-settings transition;
  • resume reconciliation;
  • current capability state;
  • selected guide variant;
  • retry/recovery behavior;
  • diagnostics safe enough for bug reports.

The native side should own only platform facts/actions, not product navigation.


10. Diagnostics

For troubleshooting, expose safe local diagnostics such as:

Android version / API
manufacturer / model
Habiter version
install-source category (store/downloaded/local/unknown where available)
Usage Access granted: yes/no
Overlay granted: yes/no
selected guide variant
last settings action opened
last reconcile result

Do not include browsing history, app usage records, habit content, tokens, or other unrelated private data in permission diagnostics.

This should integrate cleanly with the Developer Mode/debug-bundle work from #24 later.


11. Tests / QA matrix

Add automated tests for the state machine and targeted Android integration/manual QA for system UI behavior.

Flutter/unit/widget

  • permission already granted -> immediately continue;
  • request -> return granted -> automatically continue;
  • request -> return denied -> recovery UI appears;
  • retry after recovery -> grant -> continue;
  • repeated denial does not create an infinite settings loop;
  • Maybe later persists the correct deferred state;
  • guide selection falls back to generic Android;
  • manufacturer/version mapping is deterministic;
  • overlay uses the same recovery contract;
  • app restart while in recovery restores a coherent state.

Android/native

  • ACTION_USAGE_ACCESS_SETTINGS path works where supported;
  • App info intent opens the Habiter application details page;
  • missing settings handlers fail safely;
  • install-source lookup handles unsupported/unknown cases;
  • permission snapshot accurately reflects Usage Access and overlay state.

Real-device / release QA

At minimum test a release APK installed from a downloaded/local file on:

Pixel / AOSP-like Android
Samsung One UI

Then expand the compatibility matrix for:

Xiaomi HyperOS / MIUI
OnePlus / Oppo
Nothing OS
Motorola

The QA case must use the same direct/sideload distribution path users actually receive, not only flutter run or a Play-installed build.


Acceptance criteria

  • A sideloaded Habiter APK can complete Usage Access setup without the user having to discover Restricted Settings independently.
  • Returning from Android Settings always triggers a permission re-check.
  • Granting Usage Access advances App Block onboarding automatically.
  • Returning without access shows a contextual recovery state instead of repeating the same one-shot CTA.
  • Android 13+ Restricted Settings are explained when relevant, with explicit trust/security wording.
  • A supported action opens Habiter's App info page; the app does not attempt to bypass Android's confirmation requirement.
  • The guide explains More / three-dot menu -> Allow restricted settings -> return -> grant Usage Access for variants where that path is valid.
  • Install-source information is used only as a hint and unknown states have a safe fallback.
  • Usage Access and overlay share the same permission/setup reconciliation architecture.
  • Device-specific guides exist for Pixel/AOSP, Samsung, Xiaomi, OnePlus/Oppo, Nothing, Motorola, plus generic Android.
  • Guide variants include version/test metadata and a generic fallback rather than assuming OEM UI is static.
  • Repository docs contain the maintained Android permission/setup guides.
  • Desktop platforms are not shown Android-specific permission instructions.
  • No guide tells users to disable Play Protect, globally weaken Android security, or enable unrelated settings.
  • Tests cover granted, denied, restricted/recovery, retry, resume, fallback, and revoked-permission paths.
  • At least Pixel/AOSP and Samsung direct-APK flows are validated before considering the issue complete.
  • All user-facing copy is localized and accessible.

Definition of done

A user who installs Habiter directly from the official Habiter/GitHub distribution can enter App Block onboarding, understand why Usage Access is required, be guided through Android's extra Restricted Settings confirmation when necessary, return to Habiter, and continue automatically once the permission is actually granted.

The same permission flow remains understandable on common OEM Android variants and has a maintained generic fallback instead of relying on users to search the web for their device's Settings path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions