An experimental Android screen-habit tracker that measures active phone time and time away without relying on a remote backend.
KAN is the repository name for an app currently labeled I IN on Android. I built it to explore how an always-on, device-local tracker can make two behaviors visible: time spent actively using the phone and uninterrupted time spent away from it.
The app combines a foreground service, screen-state broadcasts, persistent local state, a floating system overlay, and a lock-screen challenge flow. Its dashboard turns those signals into a daily budget, streak, absence records, session history, and a multi-day chart.
- Tracks active phone time, daily time away, budget progress, streaks, and absence records.
- Shows history for completed days, phone sessions, and lock-screen challenge attempts.
- Provides an optional draggable overlay with bar, dot, and ring visualizations.
- Displays an optional lock-screen timer and timed stay-away challenges.
- Persists settings and tracking state across app restarts, device boots, and app updates.
- Includes first-run onboarding, notification and overlay permission flows, and Compose previews.
- Event-driven background tracking:
ScreenTimeServicereacts to screen-on, screen-off, user-present, and unlock events. A one-second coroutine ticker records active time only while the device is interactive. - Defensive elapsed-time accounting: repository calculations combine wall-clock time with Android's monotonic elapsed-realtime clock. Restart recovery is capped at 60 seconds to avoid crediting an unobserved locked interval after the service is killed.
- Day-boundary handling: daily rollover archives budget results and splits an ongoing absence at midnight, preserving the session while assigning time to the correct date.
- Layered local persistence: aggregate state and settings use
SharedPreferences; potentially larger phone-session and challenge logs use bounded append-only files. A migration path imports earlier preference-backed logs. - Service-owned overlay:
OverlayControllerbuilds native Android views throughWindowManager, separates taps from drags, snaps the timer to a screen edge, persists its position, and updates its visual state against the daily budget. - Reactive UI boundary: the repository exposes an immutable
StateFlow<KanSnapshot>. Activities and Compose screens observe the same snapshot rather than maintaining separate tracking state. - Lifecycle recovery: a boot receiver restarts tracking after boot or package replacement, but only after onboarding has been completed.
- Build automation: GitHub Actions provisions JDK 17 and the Android SDK, builds a clean debug APK, and publishes it as a workflow artifact. The workflow currently builds only; automated test and lint gates are not implemented.
flowchart LR
OS[Android screen, unlock, boot events] --> Service[ScreenTimeService]
Boot[BootCompletedReceiver] --> Service
Service <--> Repo[ScreenTimeRepository]
Repo --> Prefs[SharedPreferences\naggregates and settings]
Repo --> Logs[Bounded append-only logs\nsessions and challenges]
Repo --> Flow[StateFlow of KanSnapshot]
Flow --> UI[Compose app and lock-screen UI]
Service --> Overlay[WindowManager overlay]
Service --> Notify[Foreground and record notifications]
UI --> Repo
ScreenTimeRepository is the state boundary. The service writes time and lifecycle events to it; UI surfaces observe its snapshot and send settings or challenge actions back. Overlay and notification presentation remain service-owned so they can continue when the main activity is closed.
- Kotlin 2.0.21 and Java 17
- Android SDK 35 (
minSdk26,targetSdk35) - Jetpack Compose with Material 3
- Android foreground services, broadcast receivers, notifications, and
WindowManageroverlays - Kotlin coroutines and
StateFlow - Gradle 8.9 with the checked-in wrapper
- GitHub Actions for debug APK builds
- JDK 17
- Android SDK Platform 35
- Android Studio with Android SDK support, or command-line Android tooling
- An Android 8.0 / API 26 or newer device or emulator for installation
Clone the repository, open it in Android Studio, and allow Gradle to sync. For command-line builds, configure the Android SDK through your environment or a local, uncommitted local.properties, then run from the repository root:
./gradlew :app:assembleDebugOn Windows PowerShell, use:
.\gradlew.bat :app:assembleDebugThe debug APK is written to app/build/outputs/apk/debug/app-debug.apk. The checked-in GitHub Actions workflow also runs this build on pushes to main or master and can be started manually; its debug-apk artifact contains the APK.
The repository defines standard verification tasks:
./gradlew test
./gradlew connectedAndroidTest
./gradlew :app:lintReleaseconnectedAndroidTest requires a device or emulator. No unit or instrumentation test source sets are currently present, and the workflow does not run these verification tasks yet.
Screenshot coming soon.
For an in-repository UI tour, open the Compose previews in app/src/main/java/com/kan/app/ui/. Preview fixtures cover onboarding, the dashboard, history/settings, the app shell, and lock-timer states.
Experimental. The core tracking, persistence, overlay, lock-screen, history, onboarding, and debug-build workflow are implemented. Production work—including automated tests, release verification, privacy documentation, Play Console validation, and broader device testing—is tracked in PRODUCTION_ROADMAP.md.
- Background time tracking is primarily a lifecycle and clock-consistency problem: process death, reboot, screen transitions, and midnight rollover all need explicit recovery rules.
- A monotonic clock is safer for elapsed durations, while wall time is still required for calendar grouping; reconciling both avoids common reboot and clock-change errors.
- Keeping one repository-owned snapshot simplifies coordination between a foreground service, activities, Compose screens, overlays, and notifications.
- High-frequency event logs and small configuration values have different storage needs; separating append-only logs from preference-backed aggregates keeps each persistence path simple.
- Android's overlay, notification, full-screen intent, and boot behaviors require permission-aware fallbacks and real-device validation before any production-readiness claim is justified.