Humorous productivity/health app that intercepts food delivery app launches on Android and shows a full-screen deterrent flow (video + SFX + cooldown).
- Watch selected target apps (Foodpanda, UberEats, etc.) via
AccessibilityService - Intercept launch in near real-time
- Open
WarningActivitywith separate Flutter entrypoint (warningMain) - Play the impact SFX on the alarm stream with transient audio focus
- Offer two actions: dismiss now or start 15-minute bypass cooldown
- Track interception count and display lightweight analytics
- Flutter (UI and configuration screens)
- Kotlin Android services/activities (watchdog + intercept path)
- SharedPreferences bridge (Flutter writes, Kotlin reads directly)
lib/main.dartmain app shell, onboarding/home routing, and thewarningMainentrypoint (must stay here — the embedding resolves activity entrypoints against the root library only)lib/screens/onboarding, home, app picker, warning, analyticslib/services/permissions, targets, warning channelandroid/app/src/main/kotlin/dev/kashifkhan/jhappto/native Android classesandroid/app/src/main/res/xml/accessibility_service_config.xmlservice config
- Flutter SDK
- Android SDK + NDK
27.0.12077973 - Real Android device required — Accessibility is unreliable on emulators
flutter pub get
flutter run --debugBoth are required. The app cannot function without either.
- Accessibility — detects which app moved to the foreground. Only package
names are read; screen content is never accessed
(
canRetrieveWindowContent="false"). - Display over other apps — an
AccessibilityServiceis not exempt from background-activity-launch restrictions.SYSTEM_ALERT_WINDOWis the exemption that letsWarningActivitystart over another app. Without it the intercept is detected and then silently dropped.
QUERY_ALL_PACKAGES is not permitted for this use case under Play policy, so
the app declares an explicit <queries> list instead — currently 57 packages
covering global delivery platforms and regional/fast-food ordering apps across
Pakistan, India, US, UK/EU, SEA, Middle East, LATAM, ANZ, Canada and East Asia.
Adding a new food app means updating both:
android/app/src/main/AndroidManifest.xml(<queries>)MainActivity.KNOWN_FOOD_APPS
test/food_apps_sync_test.dart fails the build if the two drift. Verify any
new package resolves before adding it — a wrong id fails silently:
curl -s -o /dev/null -w "%{http_code}\n" \
"https://play.google.com/store/apps/details?id=<package>"Interception itself is unaffected by this list — event.packageName is not
visibility-filtered. The list only determines what the picker can show.
- Open app and complete onboarding
- Enable Accessibility for jhappto Food Guard
- Allow Display over other apps
- In Guarded Apps, toggle target food apps ON
- Leave jhappto
- Open guarded food app
- Warning screen should appear quickly
If nothing appears, check whether the launch was blocked:
adb logcat -s ActivityTaskManager JhapptoWatchdogPlaceholders are committed so the app runs before final art exists. Replace in place — no code changes needed:
assets/media/slap_video.mp4— portrait (9:16), under 5MBassets/audio/slap_sfx.mp3— under 500KB, starts on the impact
The warning screen degrades gracefully if either is missing or corrupt, and its action buttons always appear within ~3 seconds regardless.
Create android/key.properties (git-ignored):
storeFile=/absolute/path/to/upload-keystore.jks
storePassword=...
keyAlias=upload
keyPassword=...Then:
flutter build appbundle --releaseWithout that file the build falls back to debug signing, which Play rejects.
Release builds are minified — proguard-rules.pro keeps the accessibility
service and both activities, since stripping them breaks the app silently.
targetSdk 36is required for new submissions from 2026-08-31- AccessibilityService use requires a Permission Declaration Form plus a prominent in-app disclosure that is not bundled with other disclosures and does not live only in the privacy policy
- The deterrent is framed as a user-configured, opt-in self-commitment tool: the user picks their own apps, one tap dismisses, nothing runs autonomously
- Android-only by design (iOS does not expose the needed APIs)
- Back is trapped on the warning screen; Home and Recents always escape
- Intercept not firing: confirm Accessibility ON and overlay allowed
- Warning not showing: confirm
WarningActivity.getDartEntrypointFunctionName()returnswarningMain - App missing from picker: add it to both the
<queries>block andKNOWN_FOOD_APPS
Private/internal use unless a license file is added.