# Pausr Technical Specification (Android v1) **Author:** Founder (me) **Status:** Draft **Related:** [[Pausr Product Log]] **Mantra:** *Pause the noise, not your life.* ## 1. Overview This spec covers the v1 build of **Pausr**: one-tap pausing of single apps, app families, or user-defined suites, surfaced as resizable home screen widgets and a Quick Settings tile. The core technical problem is that Android gives no public way for a third party app to suspend another app. `PackageManager.setPackagesSuspended` is gated behind device ownership or the privileged `SUSPEND_APPS` permission, which is how Digital Wellbeing does it. v1 therefore enforces a pause by **detection and interruption**: a foreground watcher reads the current app from `UsageStatsManager`, and when a paused package comes to front it launches a full-screen interstitial over it. The user always has a way through. The product bet is that the friction of the shield is enough. Everything is on-device. No accounts, no network, no analytics service in v1. ## 2. Key Components - **Platform:** Android (Kotlin), minSdk 26 (Oreo), targetSdk current. minSdk 26 is the floor for `requestPinAppWidget` and the Quick Settings `TileService` surface. - **UI:** Jetpack Compose for the app, Jetpack Glance for widgets. - **Database:** Room (on-device SQLite). - **Background:** One foreground service (`PausrWatcherService`, type `specialUse` with a declared justification) that owns the polling loop while any target is paused and the screen is on. - **Widgets:** One `GlanceAppWidget` class, one widget per pause target. `SizeMode.Responsive` with layouts for 1x1, 2x1, and 2x2 cells. Pinned from inside the app with `AppWidgetManager.requestPinAppWidget`. - **Quick Settings tile:** `TileService` that toggles the user's default suite. - **Interstitial:** A single-top `Activity` with `showWhenLocked` and `turnScreenOn` off, launched from the watcher, themed full-screen. ## 3. Pause Mechanism: the Ladder | Rung | Mechanism | Works for | Verdict | | --- | --- | --- | --- | | 1 | `DevicePolicyManager.setPackagesSuspended` | Device or profile owner only | Out. Requires enrolling the phone or a work profile; personal apps stay untouched. | | 2 | Accessibility service (`TYPE_WINDOW_STATE_CHANGED`) | Any app, event-driven, lowest latency | Reserve. Play policy restricts accessibility APIs to accessibility use; needs a declaration and a justification, and a rejection blocks the listing. | | 3 | `UsageStatsManager` poll + interstitial | Any app, polling, needs special access | **v1.** The path every shipping third party blocker takes. Latency is the risk, measured in the first spike. | | 4 | Deep link to the OS pause screen (`Settings.ACTION_APP_USAGE_SETTINGS`) | Any app, no enforcement by Pausr | Fallback for the tile if the watcher is denied: the tile still saves the settings safari. | Rung 3 flow: `queryEvents` over the last few seconds, take the latest `ACTIVITY_RESUMED` (`MOVE_TO_FOREGROUND` on older APIs), compare against the paused set, launch the shield. Poll interval adapts: 300ms while a paused app is installed and the screen is on, stop entirely on screen off or when nothing is paused. ## 4. Data Model (Room) ``` PauseTarget id Long (PK) name String "Doomscroll bundle" kind Enum SINGLE | FAMILY | SUITE state Enum LIVE | PAUSED pausedUntil Long? epoch millis, null = until manually resumed widgetId Int? the pinned AppWidget id, null if not pinned createdAt Long TargetPackage targetId Long (FK -> PauseTarget) packageName String "com.instagram.android" PK (targetId, packageName) Schedule id Long (PK) targetId Long (FK) daysMask Int bit per weekday startMinute Int minutes from midnight endMinute Int enabled Boolean BreachEvent id Long (PK) targetId Long (FK) packageName String at Long outcome Enum KEPT | FIVE_MIN | UNPAUSED_ALL ``` `BreachEvent` is the product's whole feedback loop: which pauses hold, which ones get overridden, and how fast. It stays on device and feeds the "opened it 3 times today" line on the shield. **Families:** a curated `families.json` shipped in assets (Meta, ByteDance, Google, Snap, Microsoft) keyed by package name. Unknown publishers group by signing certificate hash from `PackageInfo.signingInfo`, which is a heuristic and is labeled as one in the picker. ## 5. User Flows ### Flow 1: Create a Target and Pin It 1. In-app `+` opens the picker: installed apps (from `PackageManager` with `QUERY_ALL_PACKAGES` declared and justified, since the picker is the product) grouped by family. 2. User picks apps, names the target, optionally sets a schedule. 3. App calls `requestPinAppWidget` with the target id in the widget extras. The launcher shows its own placement sheet. 4. Glance renders the 1x1 layout by default; user long-presses to resize; `SizeMode.Responsive` swaps layouts at the launcher's breakpoints. ### Flow 2: Tap to Pause 1. Widget `onClick` runs an `actionRunCallback` that flips `PauseTarget.state`, sets `pausedUntil`, and calls `updateAll` on the widget. 2. If the watcher is not running, start it as a foreground service with a low-priority persistent notification ("Pausr is watching 6 apps"). 3. Tile mirrors the default suite's state. ### Flow 3: Enforce 1. Watcher polls `UsageStatsManager.queryEvents`. 2. Foreground package is in a paused target: launch `ShieldActivity` (`FLAG_ACTIVITY_NEW_TASK`) over it, record a `BreachEvent` with outcome pending. 3. Shield offers three exits: **Keep it paused** (return home), **Open for 5 minutes** (grace window stored on the target, watcher ignores that package until it lapses), **Unpause the whole bundle**. Outcome written to the event. 4. Repeated launches within the grace window are suppressed. ### Flow 4: Resume 1. Manual: tap the widget or row again. Scheduled: a `WorkManager` job at `pausedUntil` or the schedule's end minute flips state and refreshes widgets. 2. If no target remains paused, the watcher stops itself and drops the notification. ## 6. UI ![[pausr-ui-mock.png]] Left: the home screen with three widget sizes on the native grid, the suite mid-resize. Middle: the in-app list, one row per widget. Right: the shield. Palette in the mock is the site's; the app ships Material 3 dynamic color with a copper fallback. ## 7. Permissions and Policy | Permission | Why | UX | | --- | --- | --- | | `PACKAGE_USAGE_STATS` | Read the foreground app | Special access screen; onboarding deep-links with `Settings.ACTION_USAGE_ACCESS_SETTINGS` | | `SYSTEM_ALERT_WINDOW` | Launch the shield over another app on Android 10+ | Special access screen; explained as "so the pause can meet the app" | | `POST_NOTIFICATIONS` | The watcher's foreground notification | Runtime prompt | | `FOREGROUND_SERVICE_SPECIAL_USE` | Android 14+ service typing | Manifest property with a plain-language justification | | `QUERY_ALL_PACKAGES` | The app picker | Play declaration; the picker is the core feature, which is an accepted case | No accessibility service, no device admin, no VPN. ## 8. Dependencies - Jetpack Compose, Glance, Room, WorkManager, DataStore (settings). - No network libraries. No analytics SDK. ## 9. Open Questions and Risks - **Latency.** If the poll misses the first frame, the user sees the feed for a beat before the shield lands. Spike on a Pixel at 250ms, 500ms, and 1s. If 300ms is not good enough, the accessibility rung comes back on the table with the policy work that implies. - **OEM background limits.** Samsung and Xiaomi kill foreground services aggressively. Test the watcher on one of each before promising anything. - **Android 15 and later.** Foreground service start restrictions from the background; the shield launch path must originate from the service, not a broadcast. - **Shield escape hatches.** Recent apps and split screen can bypass the shield. Acceptable in v1; the shield is documented as a lid. - **Family map drift.** Package names change. The curated map needs a version and a release cadence. ## 10. Milestones 1. **Spike (1 week):** watcher latency, shield launch on Android 14 and 15, Glance responsive widget on Pixel launcher. 2. **v0.1 (2 weeks):** single app targets, widget pin, shield with the three exits, Room schema. 3. **v0.2 (2 weeks):** families and suites, schedules, Quick Settings tile. 4. **v0.3:** sideloadable debug builds through CI, first tester on the tester loop from [[Vibe Reader Product Log|Vibe Reader]].