A minimal Android app (Kotlin + XML layouts, no Compose) that reads device usage statistics and lets you build a custom screen time total by including/excluding individual apps.
Accurate foreground time— reconstructed fromUsageEvents
(MOVE_TO_FOREGROUND
/MOVE_TO_BACKGROUND
/ACTIVITY_STOPPED
) rather than the pre-bucketedqueryUsageStats()
totals.Wellbeing-style charts— a donut chart of your included apps with the filtered total in the center (Today/Custom), and a weekly bar chart with hour gridlines and a tappable day selector (This Week). Both are small customView
s — no chart library.Date ranges— Today, This Week (Monday-based), or a custom start/end date picked from calendar dialogs.** Filterable app list**— every app with usage in the range is listed with its icon, name, and time, sorted descending. A checkbox on each row includes/excludes it from the big total, which updates live.Exclusion list— unchecking an app hides it from the list and leaves it out of the total. A dedicated** Excluded**screen lists everything you have excluded so you can restore apps individually or all at once, and a "Show excluded apps" toggle brings them back inline.Persistent filters— inclusion choices and the "show system apps" / "show excluded apps" toggles are stored inSharedPreferences
.System app toggle— system apps and the launcher are hidden by default to reduce clutter.** Usage access flow**— ifPACKAGE_USAGE_STATS
isn't granted, the app shows an explanation and aGrant Access button that deep-links toSettings.ACTION_USAGE_ACCESS_SETTINGS
.
./gradlew testDebugUnitTest
— everything runs on the JVM in about a minute; no device or emulator involved. GitHub Actions runs this job on every push, and the APK is only built if it passes.
| Layer | What it covers |
|---|---|
| Pure logic | ForegroundSessionReplay (session reconstruction from usage events), WeekNavigator (day/week stepping), AppListFilter (system/excluded filtering and totals), DateRange (midnight and week boundaries) |
| Robolectric | FilterStore against real SharedPreferences , and a smoke test that launches every activity to catch inflation or view-binding failures the compiler cannot see |
The pattern is deliberate: logic that used to live inside the activities is extracted into plain Kotlin objects, so the interesting behaviour is testable without an emulator and the activities stay thin.
Requirements: JDK 17+ and an Android SDK (compileSdk 35). Setting up from scratch on WSL2? See BUILDING.md for the exact commands.
./gradlew assembleDebug
Or grab the debug APK from the Releases page — it is built
by the GitHub Actions workflow in .github/workflows/build.yml
.
adb install app/build/outputs/apk/debug/app-debug.apk
Then open the app and tap Grant Access to enable Usage Access.
Because the app is sideloaded, Android gates Usage Access behind "restricted settings" and the first attempt is denied. The full flow:
| 1. Tap Grant Access | 2. First attempt is denied | 3. Allow restricted settings | 4. Turn the toggle on |
|---|
Step by step:
- Open TrueScreenTime and tap Grant Access— it deep-links to the Usage Access settings, but the toggle is blocked at first ("App was denied access"). - Go to Settings → Apps → TrueScreenTime, open the⋮ overflow menu in the top-right, and tapAllow restricted settings(you may be asked to authenticate). - Return to the app, tap Grant Access again, and enablePermit access to app usage data.
The grant is remembered until the app is uninstalled. The app never leaves the device with this data — everything stays local.
- minSdk 24 (Android 7.0), targetSdk/compileSdk 35 (Android 15)
- Dependencies: AndroidX core/appcompat/recyclerview/lifecycle, Material Components, Kotlin coroutines — nothing else.