8000 GitHub - hrithiksaini99/holdfast: A focus timer with teeth — an original Android app that covers distracting apps, with recurring schedules, a Shorts/Reels feed guard, and weekly reports. Kotlin + Jetpack Compose. · GitHub
[go: up one dir, main page]

Skip to content
hrithiksaini99Public

About

A focus timer with teeth — an original Android app that covers distracting apps, with recurring schedules, a Shorts/Reels feed guard, and weekly reports. Kotlin + Jetpack Compose.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Holdfast

A focus timer with teeth. Holdfast covers the apps you choose until the clock runs out. Pick a length, build a blocklist, and — when you want no wiggle room — turn on Strict mode, which removes the early-exit button for the whole session.

Holdfast is an original Android app written from scratch in Kotlin and Jetpack Compose. Package: app.holdfast. Version: 1.1.0 (debug build, versionCode 2). It is not affiliated with Regain or any other app; the name, code, copy, and visual design are original.

Home

What it does

  • Onboarding that explains the Accessibility permission, lets you pick apps, and checks notifications before the first session.
  • A blocklist built from installed launcher apps, with search. Settings, launchers, and system/permission UI are deliberately excluded so you can never lock yourself out of the phone.
  • Presets of 15, 25, 45, and 60 minutes, plus a custom slider from 1 minute to 3 hours.
  • Scheduled sessions: pick days, a start time, a length and whether the session is strict. Holdfast arms a single alarm for the soonest occurrence, re-arms it after reboot or a timezone change, and can start the session with the app closed. Exact alarms are used when Android allows them; otherwise the UI warns that a start may drift by a few minutes.
  • Feed guard (BETA): keep YouTube, Instagram, or Facebook usable but cover the Shorts/Reels player while a session runs. Detection looks only at the player's view id, so a redesign causes a missed block, never a false block of the normal app.
  • Strict mode: no early-exit control in the app or on the blocker for the duration of the session.
  • A full-screen blocker over each blocked app, with the app's icon, a live countdown, a progress bar, and a short line of encouragement.
  • A foreground timer service with a persistent countdown notification, plus an AlarmManager backstop so the session ends even if the UI is gone.
  • Completion notifications and a local history of completed and early-ended sessions, with daily totals, a seven-day chart, and a streak.
  • A weekly report at the top of History: focused time, session count and finish rate for the last seven days, compared with the seven days before, plus the best day of the week.
  • Resume after process death or reinstall: an active session is restored from disk, and a session that expired while the app was dead is banked as complete.
  • No network permission, no account, no analytics. Everything stays in app-private SharedPreferences.

How the blocking works

  1. AppWatchAccessibilityService receives TYPE_WINDOW_STATE_CHANGED / TYPE_WINDOWS_CHANGED events and reads only the foreground package name. For feed guard it additionally reads view identifiers to recognise the Shorts/Reels player. It never reads screen text, captions, keystrokes, or messages.
  2. If the active session's blocklist contains that package, the service adds a full-screen TYPE_ACCESSIBILITY_OVERLAY window (BlockerOverlay). This needs no "draw over other apps" grant.
  3. For feed-guarded apps the service also listens to TYPE_WINDOW_CONTENT_CHANGED, debounced to 400 ms, and checks for the Shorts/Reels pager's view id. A match raises the same overlay in feed mode, with a "Leave Shorts/Reels" button and a Back handler that leaves the feed. The rest of the app stays usable.
  4. The overlay consumes Back and keeps the screen awake. In Strict mode it has no end control at all.
  5. FocusService runs as a specialUse foreground service and owns the countdown notification. An AlarmManager alarm is scheduled at the session's end as a safety net.
  6. On expiry, FocusController closes the session, tears down the overlay, stops the service, posts the completion notification, and appends a SessionRecord to history.
  7. FocusStore is the single in-process source of truth. Writes go to SharedPreferences synchronously before the new state is published, so the UI, foreground service, and accessibility service cannot disagree.

The overlay is intentionally skipped for com.android.systemui, android, and permission-controller packages, so the notification shade, power menu, and runtime permission dialogs remain usable. Events from Holdfast's own overlay window are ignored unless they come from MainActivity, which prevents a show/hide feedback loop.

Screens

Onboarding Blocklist Blocked app
Welcome App picker Blocker over YouTube
Active session History Completion notification
Session History Notification
Schedules Feed guard Weekly report
Schedules Feed guard Weekly report

More screenshots, including the back-button trap and automatic expiry, are in demo/. The full test record is in TEST-REPORT.md.

Build

Requirements: JDK 17 or newer, Android SDK Platform 35, and a local.properties with sdk.dir pointing at the SDK.

export JAVA_HOME=/opt/homebrew/opt/openjdk@21   # or your JDK 17+ path
./gradlew assembleDebug

The APK lands at app/build/outputs/apk/debug/app-debug.apk (about 17 MB in debug).

Tests and lint:

./gradlew testDebugUnitTest lintDebug

Last result: 14/14 unit tests pass; 0 lint errors, 41 warnings (warnings are mostly SharedPreferences.commit() being intentional, dependency update notices, and hard-coded overlay strings).

Install and enable

adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n app.holdfast/.MainActivity

Walk through onboarding, then enable the watcher:

Settings → Accessibility → Downloaded apps → Holdfast → Use Holdfast.

On an emulator or test device this can be scripted:

adb shell settings put secure enabled_accessibility_services \
  app.holdfast/app.holdfast.service.AppWatchAccessibilityService
adb shell settings put secure accessibility_enabled 1

Reinstalling the APK unbinds the accessibility service; re-enable it after every install. An active session survives reinstall because it is stored on disk.

Honest limits

  • Accessibility is mandatory. Without it the timer still runs, but nothing is covered. The home screen warns when the watcher is off.
  • Strict mode is a commitment, not a prison. Force-stopping Holdfast, clearing its data, uninstalling it, disabling Accessibility, or rebooting before the boot receiver runs can get around any third-party blocker. Holdfast says so in onboarding rather than pretending otherwise.
  • One overlay covers the whole screen. System UI and permission dialogs remain reachable by design. A determined user can still use the notification shade.
  • Feed guard is BETA and fail-open. It matches published view ids for YouTube Shorts, Instagram Reels, and Facebook Reels. If an app renames its player, the guard misses the feed; it will not block the normal app. Live Shorts detection could not be exercised on the test emulator because its YouTube client is stuck on an update screen, so detection is covered by unit tests and the negative on-device test only.
  • Schedules depend on Android's alarm policy. With exact alarms allowed, starts landed on the second in testing. Without it Holdfast uses inexact alarms and tells the user starts may be a few minutes late.
  • Tested against YouTube and Chrome on an Android 15 emulator. Other apps, launchers, and OEM skins may behave differently, especially around first-run dialogs or split-screen.
  • Debug-signed. This is a working prototype, not a Play Store release.
  • Unit tests cover the pure logic, not the device. 14 JVM tests cover schedule arithmetic and feed-marker matching. The blocking path is verified by the manual device pass in TEST-REPORT.md; there are no instrumentation tests.

Project layout

app/src/main/java/app/holdfast/
  MainActivity.kt              Compose entry point
  HoldfastApp.kt               Application init + session resume
  data/AppRepository.kt        Installed-app discovery and icons
  data/FocusStore.kt           Persisted session, blocklist, feed guard, schedules, history
  data/Schedule.kt             Schedule model + pure date planner
  service/AppWatchAccessibilityService.kt   Foreground + feed detection
  service/BlockerOverlay.kt    Full-screen accessibility overlay (block + feed modes)
  service/FeedDetector.kt      Shorts/Reels view-id matcher
  service/FocusService.kt      Foreground countdown service
  service/FocusController.kt   Session lifecycle and alarms
  service/ScheduleScheduler.kt Single-alarm schedule arming
  service/ScheduleReceiver.kt  Scheduled start + re-arm
  service/Notifications.kt     Ongoing + completion + missed-schedule notifications
  service/BootReceiver.kt      Resume and re-arm after reboot / time change / update
  service/SessionActionReceiver.kt  End/expire broadcasts
  ui/                          Onboarding, home, apps, history, theme
app/src/test/java/app/holdfast/
  data/SchedulePlannerTest.kt  8 schedule tests
  service/FeedMarkersTest.kt   6 feed-marker tests
app/src/main/res/
  layout/view_block_overlay.xml
  xml/accessibility_service_config.xml
demo/                          Test screenshots

Why it is shaped this way

Holdfast deliberately avoids Usage Access and SYSTEM_ALERT_WINDOW. Usage Access is batch-oriented and laggy for an immediate block; a draw-over-other-apps window requires a separate scary permission and can be disabled independently. The accessibility root window gives an immediate, event-driven answer to "which app is in front?", and the accessibility overlay layer is the correct place to cover it. The trade-off is the Accessibility permission itself, which onboarding explains plainly.

About

A focus timer with teeth — an original Android app that covers distracting apps, with recurring schedules, a Shorts/Reels feed guard, and weekly reports. Kotlin + Jetpack Compose.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0