A bulletproof 20-20-20 rule timer for Android that actually works.
Blink is a persistent eye-care daemon that reminds you to rest your eyes every 20 minutes. Unlike other timer apps, it uses exact alarms and a foreground service to survive Android's aggressive battery optimizations — your timer fires on time, every time, even during Doze mode.
When it's time, a beautiful fullscreen overlay appears on top of whatever you're doing (no app switching, no interruptions) and counts down 20 seconds for you to look at something 20 feet away.
| Feature | Description |
|---|---|
| Exact Alarms | Uses AlarmManager.setExactAndAllowWhileIdle — fires during deep sleep & Doze |
| Foreground Service | Persistent specialUse FGS with live countdown notification |
| True Overlay | TYPE_APPLICATION_OVERLAY floats on top of all apps without replacing them |
| Smart Screen Detection | Pauses on screen off, resets after >20s lock, resumes on short locks |
| Boot Receiver | Optional auto-start when device reboots |
| Feature | Description |
|---|---|
| 3 Overlay Styles | Expressive (animated rings, ripple waves, orbital dots), Calm (smooth arc, gentle glow), Minimal (clean progress circle) |
| Dynamic M3 Theming | Colors pulled from your wallpaper via dynamicColorScheme() |
| Alert Modes | Choose between: Overlay only, Notification only, or Both |
| Adjustable Timers | Work: 1–60 min, Rest: 10–60 sec |
| Overlay Opacity | 30–100% transparency |
| Dismissable Mode | Optional close button (red FAB) during rest overlay |
| Landscape Support | Responsive layout adapts to orientation |
| Feature | Description |
|---|---|
| Shizuku Integration | Optional — auto-whitelists app from battery optimization via cmd deviceidle whitelist |
| Battery Optimization | Built-in toggle to request unrestricted battery access |
| START_STICKY | Service restarts itself if killed by the OS |
| Watchdog Alarm | 5-minute exact alarm verifies service liveness, restarts if dead |
| Direct Boot | Starts on LOCKED_BOOT_COMPLETED before device unlock |
| OEM Guides | Per-manufacturer battery settings instructions (Xiaomi, Samsung, OPPO, Vivo, etc.) |
| Build | Size | Description |
|---|---|---|
blink-v1.1.0-release.apk |
~1.8 MB | Signed, R8-minified release build |
blink-v1.1.0-debug.apk |
~10 MB | Debug build with logging |
See CHANGELOG.md for release notes.
- Android 14+ (API 34)
- Display over other apps permission (for the overlay)
- Notification permission
- Exact alarm permission
┌─────────────────────────────────────────────────┐
│ MainActivity (Jetpack Compose / M3) │
│ ├── Timer Ring (segmented arc, 60fps) │
│ ├── Start/Stop FAB │
│ └── Settings FAB → SettingsScreen │
├─────────────────────────────────────────────────┤
│ EyeCareService (Foreground Service) │
│ ├── AlarmManager (exact, idle-safe) │
│ ├── BroadcastReceiver (SCREEN_ON/OFF) │
│ ├── Countdown coroutine (1s tick) │
│ └── Persistent notification (live countdown) │
├─────────────────────────────────────────────────┤
│ RestOverlayService (TYPE_APPLICATION_OVERLAY) │
│ ├── ComposeView in WindowManager │
│ ├── 3 style composables (Expressive/Calm/Min) │
│ ├── 60fps raw progress (no animateFloatAsState)│
│ └── Vibration at start + end │
├─────────────────────────────────────────────────┤
│ ShizukuUtils → cmd deviceidle whitelist │
│ BootReceiver → auto-start on reboot │
│ AlarmReceiver → wakes EyeCareService │
│ PrefsManager → SharedPreferences wrapper │
└─────────────────────────────────────────────────┘
- No WorkManager — too imprecise for 20-minute intervals.
AlarmManager.setExactAndAllowWhileIdleis the only reliable option. - True overlay, not Activity —
TYPE_APPLICATION_OVERLAYstays on top without replacing the current app. Activities would force-switch context. - ComposeView in Service — requires setting all three tree owners (
LifecycleOwner,SavedStateRegistryOwner,ViewModelStoreOwner) manually. - Raw 16ms countdown —
animateFloatAsStateon an already-updatingmutableStatecauses jitter. Directdelay(16)loop gives smooth ~60fps arc animation. - R8 on debug builds —
material-icons-extendedis ~30MB unshrinked. R8 tree-shakes unused icons down to <2MB.
# Debug
./gradlew assembleDebug
# Release (requires signing key)
./gradlew assembleRelease- Language: Kotlin 2.1
- UI: Jetpack Compose + Material 3 (Dynamic Color)
- Min SDK: 34 (Android 14)
- Build: Gradle 8.9, AGP 8.7.3, Compose BOM 2024.12.01
- Dependencies: Shizuku 13.1.5, Coroutines 1.8.1
GNU General Public License v3.