Design doc · for review

focus

A focus-oriented launcher for a Pixel 6. One button swaps the phone to a quieter home screen — a few apps, no notifications — and one gesture brings the normal launcher back.

eunomie/focus · 16 August 2026 · status: v1 built and running on the Pixel 6

01 — The brief

One locked requirement, everything else open.

Locked in

From the phone's normal launcher, hit a button and it swaps to a smaller, lighter, focus-oriented launcher: just minimal apps and notifications. And a clear way back to the classic launcher.

Everything below that line is a proposal. The important framing: this is a tool for one person on one phone. Both products I studied are commercial and carry a lot of weight that exists only because they must serve strangers — onboarding wizards, subscription tiers, six configurable profiles, analytics, Play Store policy compliance. None of that is load-bearing here, and cutting it is most of what makes this project small enough to actually finish.

02 — What the inspirations actually do

Fairphone open-sourced Moments. That turned guesswork into evidence.

Fairphone Moments

Shipped on the Fairphone (Gen. 6), Android 15. A lime-green physical switch on the side of the phone flips into a minimal mode. Five presets — Essentials (Camera, Browser, Maps, Messages, Phone), Deep Focus, Recharge, Journey, Quality Time — each capped at five apps, up to six saved. Notifications and calls are blocked by default. Fairphone's own line: "clicking in and out of apps doesn't take you back to your Android home screen."

The source is public — fairphone/fairphone-moments, Kotlin + Compose, EUPL-1.2, internal codename SpringLauncher. Reading it was the single most useful hour of this investigation:

What I found in the sourceWhy it matters here
Android.bp declares privileged: true, presigned: true. The manifest asks for WRITE_SECURE_SETTINGS, INTERACT_ACROSS_USERS_FULL, MODIFY_DAY_NIGHT_MODE, NETWORK_AIRPLANE_MODE, POWER_SAVER, each tagged tools:ignore="ProtectedPermissions". Moments is a privileged system app on an OS Fairphone controls. This is the headline finding. Most of its magic is not clever app code, it is platform ownership — and it is not available to us. See §03.
The switch handler does exactly two things: startActivity(NEW_TASK|CLEAR_TASK|NO_ANIMATION) to enter, and instance?.finish() to leave. The "launcher swap" is an activity lifecycle toggle. It only reads as a swap because the surrounding framework is theirs. Copying the technique without the privileges gets you nothing.
Notifications are handled with AutomaticZenRule + ZenPolicy (Do Not Disturb), via ACCESS_NOTIFICATION_POLICY. The NotificationInterceptorService and CallInterceptorReceiver exist in the tree but are commented out of the manifest: "Disabling these components for now until they are properly implemented." A funded team with system privileges tried notification interception and shipped DND instead. That is a strong, free lesson: start with DND, treat reading notifications as a later, optional step.
A profile stores: name, icon, two gradient colours, app list, allowed contacts, repeat-call, wallpaper, dark mode, blue-light filter, sound, battery saver, brightness. A good checklist of what a "mode" can contain. Most entries need privileged permissions we don't have — useful for knowing what to cut early rather than discover late.
Firebase Analytics on every switch event; a demo mode for retail units; a Play Store rejection worked around by dropping MANAGE_EXTERNAL_STORAGE; users reporting Play Protect flagging the app. Pure commercial-product overhead. All of it disappears when the audience is one person and distribution is a sideloaded APK.

Minimalist Phone

A third-party Android launcher (com.qqlabs.minimalistlauncher), and a philosophically different animal. It is not a mode you enter — it is a launcher you live in, permanently replacing your home screen. Its features cluster around restriction and measurement:

The wider genre (Before Launcher, Olauncher, Niagara) converges on the same grammar: swipe right for a searchable text app list, swipe left for filtered notifications, a small pinned favourites row, edge-scrub alphabet navigation.

The useful contrast

Fairphone gives you a mode you switch into and out of. Minimalist Phone gives you a permanent restriction plus willpower tooling. The brief here is explicitly the first shape. So: Moments' model, Minimalist Phone's typography, neither's business model.

03 — The constraint that shapes everything

On a stock Pixel, taking over the home screen is one tap. Giving it back is not symmetric.

An Android launcher is just an activity with this in its manifest:

<intent-filter><action android:name="android.intent.action.MAIN"/>
  <category android:name="android.intent.category.HOME"/>
  <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

Which of the installed candidates actually receives the HOME intent is decided by the role android.app.role.HOME. From AOSP's roles.xml, that role is exclusive="true", exclusivity="user", and — critically — it carries a requestTitle and requestDescription. A role with those is requestable: RoleManager.createRequestRoleIntent(ROLE_HOME) puts up a system dialog, the user taps once, and your app is the home app.

Hard constraint — surfaced deliberately

There is no public API for an app to give up a role it holds. Taking the home screen is one tap; handing it back has no equivalent call. Every design below is shaped by that asymmetry, and it is the main reason this can't simply mirror how Moments works.

Fairphone sidesteps it entirely by being part of the OS. We can't. So the realistic options are:

OptionHow the round trip worksVerdict
A — Focus holds HOME permanently, forwards to Pixel Launcher when off Zero taps. Focus is always the home app; when inactive it immediately launches NexusLauncherActivity so you see your normal launcher. Rejected
Pixel Launcher is bound to Quickstep as the role holder. Driving it while it doesn't hold the role invites flicker, broken home/recents animations, and a configuration Google never tests. Too fragile for a daily driver.
B — Focus takes HOME on entry, releases it on exit In: one system dialog, one tap. Out: the system Home-app picker via Settings.ACTION_HOME_SETTINGS. Recommended
Works with the platform instead of against it. No privileged permissions, no root, no custom ROM. Costs a tap each way.
C — Focus is the only launcher; no classic mode at all Build a full-featured launcher and a minimal one in the same app. Rejected
Contradicts the brief, and means writing a real launcher — grids, folders, pages, widgets. Ten times the project.

The spike that could make exit free

The HOME role's <required-components> is "an activity with MAIN + HOME", and AOSP's roles.xml carries a comment pointing at HomeRoleBehavior.getFallbackHolder(). That suggests a possibility worth one evening: if Focus disables its own HOME activity with setComponentEnabledSetting, it stops satisfying the required component, and the role may fall back automatically to Pixel Launcher.

If it works, exiting focus mode becomes zero-tap and fully automatic. If it doesn't, the Settings picker is the shipped path and nothing else in the design changes. I have not verified this on a device — it is flagged as a spike, not assumed.

One upside of being personal

Because this is sideloaded rather than shipped through Play, a one-time adb shell pm grant … WRITE_SECURE_SETTINGS is legitimately available. That unlocks device-wide effects like greyscale that Fairphone needed system-app status for. It stays strictly opt-in — nothing core depends on it.

04 — Designing the swap

Three surfaces in, one deliberate gesture out.

Normal Pixel Launcher holds HOME DND off · all apps TRIGGER Quick Settings tile Quick Tap (back of phone) Home-screen shortcut any one of the three SYSTEM DIALOG "Make Focus your Home app?" one tap · ROLE_HOME Focus Focus holds HOME zen rule active · 5 apps hold "exit focus" 1s → release role → Pixel Launcher returns entering is deliberate · leaving is easy but never accidental
The default ↔ focus ↔ default round trip.

Getting in: three surfaces, no launcher customisation

The brief says "hit a button". On a Pixel that button can live in three places, and I'd build all three because each is a handful of lines:

Getting out: findable, deliberate, never locked

The exit control sits permanently at the bottom of the focus home screen. It is a press-and-hold for ~1 second, not a tap — enough that it never fires by accident in a pocket, not so much that it becomes a puzzle. The Quick Settings tile toggles back out too.

A deliberate non-feature: no lockout. No "you must wait 25 minutes", no penalty screen, no streak to break. Minimalist Phone's session-limit machinery is aimed at people fighting their own phone; this is a mode you chose to enter, and being unable to leave your own phone during a real interruption is a genuine failure mode, not a feature.

05 — Mockups

The five screens that make up the whole interaction.

9:41
9:41
Sun, 16 Aug · 24° Sunny
Gmail
Maps
Photos
Keep
Slack
Files
Music
Focus
shortcut

01Default launcher Pixel Launcher, untouched. Focus adds one icon and takes nothing away — if the project is abandoned, deleting the app leaves no trace.

9:41
◉Wi-Fi
✦Bluetooth
◐Focus
◑Do Not Disturb
☾Dark theme
✈Airplane
tap → focus on
Quick Tap on the back of the phone does the same

02The trigger A Quick Settings tile — reachable from inside any app, not just the home screen. The same tile turns focus back off.

◐
Focus on
5 apps · notifications paused

03The transition A short blurred confirmation, borrowed from Moments. It exists to make the mode change unmistakable — the worst launcher bug is not knowing which mode you're in.

9:41
9:41
Sunday, 16 August
3 waiting
HOLD TO EXIT

04Focus home Clock, five app names, one count of what's waiting, one way out. No drawer, no search, no widgets, no swipe to anything. Text instead of icons — nothing here is designed to catch your eye.

9:41
SUN 16 AUG
9:41
drawn by the system, not by us
wallpaper · no notifications · always-on

04bLock screen Not a replaced lock screen — Android has no API for that. This is a wallpaper rendered from the same Compose code as screen 04, laid out so the system's own clock fills the top. Glance at the phone and you know you're in focus mode without unlocking it. Deliberately no button shapes: a control that can't be tapped only teaches your thumb to try.

9:41
9:41
Sunday, 16 August
3 waiting
KEEP HOLDING…

05The way back Press and hold fills the pill over ~1s, then the classic launcher returns. Deliberate enough not to misfire, never a lockout.

9:41
Allowed apps
5 of 5 chosen
Phone
Messages
Camera
Maps
Podcasts
Calendar
Gmail
Slack

06Configuration The entire settings surface. One list, a cap, nothing else — the cap is the feature. Reached by long-pressing the clock.

06 — Feature scope

Small enough to finish, in that order.

Core — v1

FeatureNotes
Focus home screenClock, allowed-app list, exit control. Text-first, monochrome, no icons.
Enter / exitQuick Settings tile + Quick Tap + home shortcut in; hold-to-exit out. The locked-in requirement.
HOME role handlingTake on entry, release on exit — plus the fallback spike from §03.
Do Not DisturbOne AutomaticZenRule tied to focus state. Following Fairphone's shipped choice, not their abandoned one.
Allowed-app configurationOne screen, one list, a cap of 5.
Transition confirmationThe blurred overlay. Cheap, and it removes all ambiguity about which mode you're in.
Notification count line Q4"3 waiting" — a count only, no senders, no previews, not tappable. Counts allowed apps only. Needs NotificationListenerService.
Device-wide greyscale Q5On entering focus, off on leaving. One-time adb grant; degrades cleanly to colour without it. See the restore-safety note below.
Starred contacts break through newZenPolicy.allowCalls(PEOPLE_TYPE_STARRED) plus repeat callers. Uses the favourites already curated in Contacts — no second list, and no READ_CONTACTS. A focus mode you can't be reached in is one that stays switched off.
Lock screen + always-on newLock and home wallpaper rendered from the same Compose code as the focus screen; always-on display on; lock-screen notifications hidden. All reverted on exit. See ADR 6.

Nice to have — v2, only if v1 gets used

laterSession timer — focus for 25/50 minutes, then auto-exit. Additive, not a lockout.
laterA second profile — only once one profile has proved itself. Fairphone ships six; one person needs one.

Explicitly out of scope

noApp blocking, session limits, delay screens — the Minimalist Phone half. Wrong model: focus is a place you go, not a warden.
noScreen-time dashboards, streaks, usage nags — measuring the problem is not solving it, and Android Digital Wellbeing already exists.
noAnalytics, accounts, cloud sync — one user, one device.
noPlay Store distribution — sideloading dodges the exact policy surface that cost Fairphone a rejection and a Play Protect flag.
noGeneral launcher features — folders, pages, icon packs, widgets, search, app drawer. Every one is an escape hatch back to distraction.
noOnboarding wizard — the author installed it. A README is the onboarding.
noOther devices, other Android versions, work profiles, iOS — target is one Pixel 6.

07 — What I take, what I don't

Neither product is being cloned. Here is the ledger.

FromTakenDeliberately different
Fairphone Moments The core model — a mode you switch into and out of, not a permanent state.

A small hard cap of apps per mode (five is a good number and they landed on it too).

The blurred transition confirmation.

DND via AutomaticZenRule rather than notification interception — their own source shows them retreating to exactly this.
No privileged system app; we work inside what a sideloaded APK can do, which forces the role-handoff design in §03.

One profile, not six. Presets for strangers; one person knows their own list.

No analytics, no Firebase, no Play Services — their README warns the app is degraded without them, which is a dependency worth not having.

A Pixel-native trigger (Quick Tap + QS tile) instead of hardware we don't have.
Minimalist Phone The visual language: text instead of icons, monochrome, generous spacing, nothing that competes for attention.

The idea of an explicit allowlist rather than a blocklist — decide what's in, not what's out.
No blocking, no session limits, no delay screens, no streaks, no screen-time reporting. Its model is a permanent restriction plus willpower tooling; the brief here is a reversible mode.

No swipe-right app drawer. In their design it's a convenience; in a focus mode it's an escape hatch back to everything, which defeats the point.

No subscription, obviously — but also none of the structure a subscription forces (tiers, upsells, retention hooks).

08 — Technical direction

Kotlin, Compose, one Gradle module, no dependencies worth arguing about.

Language and framework

Kotlin with Jetpack Compose. This wasn't specified in the brief, so it is being decided and recorded rather than assumed — the full reasoning is in ADR 2. In short: Kotlin is the only first-class Android language and every API this app touches is documented Kotlin-first; Compose suits a handful of bespoke text screens and its @Preview system means the mockups above and the real screens can converge in one place. Fairphone built the same kind of app in the same stack, which is a useful sanity check.

Rejected: Java (more ceremony, no Compose ergonomics); Flutter/React Native (a cross-platform layer is pure cost for one Android device, and every interesting API here would need a hand-written platform channel); KMP (nothing to share with).

The pieces

ENTRY POINTS FocusTileService FocusHomeActivity ToggleActivity SettingsActivity Compose throughout DOMAIN — the whole app is this one state machine FocusModeController HomeRoleManager ZenRuleController AllowedAppsRepository PLATFORM RoleManager LauncherApps NotificationManager DataStore Settings.ACTION_HOME…
Three layers. FocusModeController is the only thing that decides anything.

Permissions, and how few there are

Permission / grantWhy, and how it's obtained
ACCESS_NOTIFICATION_POLICYCreate and toggle the DND zen rule. User grants once in Settings.
android.app.role.HOMEThe role itself. One system dialog per entry, or once if the spike works out.
(none needed)Listing installed apps uses LauncherApps.getActivityList(), which needs no permission — QUERY_ALL_PACKAGES, which Fairphone declares, is not required for this.
BIND_NOTIFICATION_LISTENER_SERVICEThe "3 waiting" count (Q4). User grants Notification Access once in Settings. DND suppresses alerting, not posting, so the listener still sees what arrives while focus is on.
SET_WALLPAPERLock and home wallpaper during focus mode. A normal permission — no prompt, no grant.
READ_MEDIA_IMAGESReading the current wallpaper so the original can be restored. Granted once over adb, same as WRITE_SECURE_SETTINGS. Without it, focus mode leaves the wallpaper alone rather than changing something it can't undo.
WRITE_SECURE_SETTINGSGreyscale, always-on display, and hiding lock-screen notifications. Granted once over adb, never requested at runtime:
adb -d shell pm grant <pkg> android.permission.WRITE_SECURE_SETTINGS
Then Settings.Secure: accessibility_display_daltonizer{,_enabled}, doze_always_on, lock_screen_show_notifications — all verified writable on the device.
Failure mode worth designing for

Focus mode now changes four pieces of global device state: greyscale, the always-on display, lock-screen notifications, and the wallpaper. None of them are app state. If Focus is killed, crashes, or is force-stopped mid-session, the phone is left grey, burning battery on an always-on display, hiding its lock-screen notifications, and wearing the wrong wallpaper — with no obvious way back.

So restoration is a real component, not a line in onDestroy: persist the intended state, and reconcile it on app start and on BOOT_COMPLETED. This is the single largest piece of engineering the device-state effects buy, and it is worth naming before it is discovered.

Known tax

While Focus holds the HOME role, Pixel Launcher's gesture-navigation polish and At a Glance are gone — the swipe-up-to-home animation gets generic. This is unavoidable for any third-party launcher on a Pixel, it only applies while focus mode is active, and quantifying how much it grates is one of the things the first spike is for.

09 — Build tooling

Dagger v1.0.0-beta.9. A working skeleton now, the real pipeline when there's code to build.

The skeleton is in the repo and runs today — dagger check passes focus:check against the pinned engine, and dagger call focus tree proves the workspace mounts. That is the whole point of it: prove the mechanism, don't build a pipeline for an app that doesn't exist yet.

TODAY — built and verified .dagger/modules/focus (dang) check · tree — engine pinned v1.0.0-beta.9 PLANNED — once the app exists android-sdk (dang) JDK + SDK + licences gradle (dang) wrapper + cache volumes focus (dang) composes the above SURFACE THE MODULE EXPOSES dagger check unit tests · lint · detekt dagger call focus build debug + release APK … focus sign keystore as a Dagger secret … focus release versioned APK → GitHub Releases
Modules in dang; the Java SDK is the per-module fallback when dang stops being comfortable.

Splitting android-sdk from gradle earns its keep: the SDK container is slow to build and highly cacheable, the Gradle runner is generic enough to reuse unchanged, and focus stays thin. Two things that will need care when they land — non-interactive Android SDK licence acceptance (belongs in android-sdk, cached once) and release signing (keystore as a Dagger secret, never a file in the repo; debug builds need neither). Full detail in docs/build-tooling.md and ADR 3.

10 — Decisions

All eight answered on 16 August, then two more added after the spike ran.

Net effect on scope

Q4 and Q5 pulled the notification count line and device-wide greyscale out of "maybe later" and into v1. Everything else confirmed the recommendation, which means the shape of the app doesn't change — it just gets two more things in the first release.

Q1Which apps, and how many?

Fairphone caps at five and it feels right — enough for a phone to still be a phone, few enough that the list never needs scrolling or scanning.

Decided — 5 appsCap at 5. Starting list: Phone, Messages, Camera, Maps, Podcasts, editable in the settings screen. The cap itself is not editable — that's the point of it.
Q2Is there an escape hatch to all apps from inside focus mode?

The honest tension: a personal phone occasionally needs the banking app right now. But every minimal launcher that adds a full app list ends up being used as a normal launcher.

Decided — no drawerIn Yves's words: "if I need an app I'm not in focus mode anymore." That's the cleanest possible statement of the rule, and it settles a lot of smaller questions downstream — there is exactly one way out, and needing an app is a reason to take it.
Q3Should there be a schedule or automatic trigger?

Fairphone has none — it's the physical switch or nothing. Minimalist Phone has blocking schedules, and they're part of what makes it feel like a warden.

Decided — manual onlyNo schedules, no automatic triggers, in v1 or later. The voluntary "focus for 25 minutes then exit" timer stays on the v2 list, since it keeps the initiative with you rather than taking it away.
Q4How much notification filtering?

Three levels: (a) DND only — nothing gets through, nothing is shown; (b) DND plus a count line, "3 waiting", no senders or previews; (c) a filtered list rendered in the launcher, which needs NotificationListenerService and is where Fairphone's own attempt stalled.

Decided — (b), and it's in v1DND plus a bare count. Two details I've settled rather than send back: it counts only allowed apps (a count that includes Slack is noise, and by definition you don't care about the rest), and the line is not tappable — the moment it expands, it's the notification shade again. Both are one-line changes if living with it says otherwise. (c) stays rejected.
Q5Greyscale the whole phone during focus mode?

Probably the single highest-impact effect per line of code — colour is most of what makes app icons pull at you. Needs a one-time adb shell pm grant … WRITE_SECURE_SETTINGS, which is available precisely because this is sideloaded onto your own device.

Decided — yes, in v1One adb command at install time; the app degrades cleanly to colour if the grant is missing, so it never becomes a hard dependency. The keys are confirmed working in shipped apps — see §08, along with the restore-on-crash safety net this makes necessary.
Q6How much friction on the way out?

Options: instant tap, hold ~1s, or a confirmation dialog. Fairphone's physical switch is instant and unmissable; a tap on a touchscreen is neither.

Decided — hold ~1sWith the filling progress bar, as mocked up in screen 05. Explicitly no lockout, ever.
Q7One focus mode, or several?

Fairphone ships five presets and allows six saved. That is a product serving strangers who don't know what they want yet.

Decided — oneA single focus mode. Storage still keys settings under a profile id so a second one is an additive change later, but nothing in the UI hints that modes exist.
Q8Is losing Pixel Launcher's gesture polish acceptable?

Unavoidable for any third-party launcher, and only while focus mode is active — but it's the kind of thing that's fine in theory and irritating in practice.

Decided — accept, and try itMeasured in the first spike, before any real UI is written. If it grates, the answer is shorter and more deliberate focus sessions, not a different architecture.
Q9Should starred contacts ring through focus mode?

Raised by Yves: "if my wife is calling me it can be for a very good reason." The concern is exactly right — a focus mode you can't be reached in is one you never leave switched on, and you'll keep checking the phone anyway.

Decided — yes, in v1ZenPolicy.allowCalls(PEOPLE_TYPE_STARRED) plus allowRepeatCallers(true). Three things make it cheap: it reuses the favourites already in Contacts so there's no second list to maintain, it needs no READ_CONTACTS because the platform evaluates starred status itself, and repeat callers cover the emergency from someone you never thought to star. This is how Android's own Bedtime mode behaves.
Q10How far can the lock screen and always-on display be pushed?

The goal: glance at the phone, see focus mode, don't even try to unlock — a device that always behaves the same way, where the lock is a commodity rather than a gate.

Decided — most of it, minus the part nobody can doAlways-on display, hidden lock-screen notifications and both wallpapers are all verified working on the device. Replacing the lock screen itself is not possible — Android has no API, and the `showWhenLocked` workaround is fragile and security-degrading. That's the same wall that made Fairphone ship Moments as a privileged system app. The wallpaper carries the recognition instead, and as a bonus it removes the swipe-up-to-home flicker, because what the transition reveals already looks like the focus screen.

11 — Roadmap

Validate the idea before paying for the plumbing.

Spike · done ✓

Does the platform cooperate? Yes.

  • Take ROLE_HOME, live with it for a day
  • Test whether disabling the HOME component hands the role back automatically
  • Feel the gesture-navigation tax (Q8)
  • Bind Quick Tap, check it works
  • Confirm the greyscale keys on this Pixel, and that restore-on-crash works
  • Confirm a listener still sees notifications while DND is active

All of it verified on the device. The automatic return works, so exit is zero-tap. The gesture tax is real but minor. Full results in ADR 4.

v1 · built ✓

The locked-in requirement, well.

  • Focus home screen
  • QS tile · Quick Tap · shortcut
  • Role handoff both ways
  • DND zen rule
  • Allowed-apps screen (cap 5)
  • Transition overlay
  • Notification count line
  • Greyscale, always-on, lock wallpaper
  • Starred contacts break through
  • Device-state restore component

Then use it for a month before writing another line.

v2 · only what's missed

Driven by use, not by plan.

  • Session timer
  • A second profile
  • Allowed contacts

Every item here is a guess until v1 has been lived with. Ordering to be decided then — and "none of them" is a perfectly good outcome.


Sources