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.
Top comments (0)