If you need to know which app the user is looking at right now — for a focus timer, a parental-control tool, an anti-distraction app — you hit the same wall at step one: how do you get the current foreground app without root?
I shipped a tool that does exactly this, and this post is the part I wish someone had written for me.
The obvious options, and why they all fail
| Approach | Verdict |
|---|---|
ActivityManager.getRunningTasks() | Deprecated since Android 5.0. Returns only your own task. |
getRunningAppProcesses() | Returns only your own process (Android 8+). |
UsageStatsManager ("Usage access") | Works, but latency is up to ~10 minutes. Useless for real-time. |
AccessibilityService | TYPE_WINDOW_STATE_CHANGED fires the instant the window changes. ✅ |
AccessibilityService is the only real-time, no-root option.
The price: the user must enable the service manually (Settings → Accessibility). There is no legitimate way to toggle it programmatically, and you shouldn't try — that's a Play-policy landmine.
Minimal working implementation
class WarnService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent?) { if (event?.eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) return val pkg = event.packageName?.toString() ?: return if (pkg == packageName) return // ignore our own windows checkAndWarn(pkg) } override fun onInterrupt() {} } The manifest — every attribute matters here:
<service android:name=".WarnService" android:exported="true" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_config" /> </service> And res/xml/accessibility_config.xml:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeWindowStateChanged" android:accessibilityFeedbackType="feedbackGeneric" android:notificationTimeout="100" /> Four pitfalls that actually bit me
Use
TYPE_WINDOW_STATE_CHANGED, notTYPE_WINDOW_CONTENT_CHANGED. The latter fires on every content update — your battery graph will look like a ski slope.Guard against self-triggering. When your own warning overlay appears, that's also a window-state change. Filter out your own package name or you get an infinite loop.
Throttle. A 30-second cooldown after each warning. Without it, a user who is genuinely distracted gets spammed and uninstalls.
Don't try to block. Blocking an app launch requires root. "Detect + notify" is the correct no-root design — and it keeps you on the right side of Play policy.
What I built with it
WiFi Beacon (wifiwarn): you configure a target WiFi SSID + a target app. When the phone is connected to that WiFi and the app opens, it fires a sound, a notification, and a full-screen overlay.
Use cases: a parent policing games on the home WiFi; or self-discipline when you're supposed to be working on the office WiFi.
The whole thing runs locally — no cloud, no data collection, Android 8–15.
Source: github.com/weavrikx/wifiwarn — stars and issues are welcome.
I'm Weavri, an indie Android developer. I test on real hardware (Android 14) and ship reproducible builds.
