A practical introduction to AccessibilityService, the powerful ⚡ and potentially dangerous ⚠️ API behind password managers 🔐 and UI automation tools 🤖.

When we hear “accessibility” in Android, our minds usually jump to contentDescription, touch targets, and making our own apps usable with screen readers like TalkBack. That’s a critical, essential part of our job. ✅
But there’s another side to the accessibility framework — a side that doesn’t just provide information, but consumes it.
This is the world of the AccessibilityService. It’s a special, privileged service that can see and interact with the UI of any app running on a device. It’s the engine behind password managers, automation tools, and assistive technologies.
In this guide, we’ll explore what an AccessibilityService is, why it’s one of the most powerful—and dangerous—APIs in the Android SDK, and how to build a simple one from scratch.
Speaker vs. Listener: The Core Distinction 🗣️ ➡️ 👂
First, let’s clear up a common point of confusion.
- Making your app accessible (using
AccessibilityDelegate,XML attributes, etc.) is like being the speaker. You are providing information about your UI to the Android system. - Building an
AccessibilityService is like being the listener 👂. You are consuming information from whatever app is currently on the screen to perform actions on the user’s behalf.
Most developers only ever need to be the speaker. Today, we’re putting on our headphones and learning how to listen.
The Power ⚡ and the Responsibility 🛡️
An AccessibilityService can, with user permission, read the entire view hierarchy of any screen. This power enables incredible functionality:
- Password Managers (LastPass, 1Password, Bitwarden) 🔑: They detect when you’re on a login screen, find the username/password fields, and offer to fill them for you.
- UI Automation (Tasker) ⚙️: Allows users to create scripts that can click buttons, enter text, and navigate through apps automatically.
- Context-Aware Assistants 🧠: Can read the screen to provide contextual information or actions.
- Assistive Tools ♿: A service could be built for users with motor impairments to simplify complex gestures into single-button clicks.
However, this power comes with a huge responsibility. A malicious AccessibilityService could scrape private data, and a buggy one could render a device unusable. Because of this, Google Play has strict policies for apps using this API, and you must be able to justify why your app needs this level of access. Always proceed with caution and prioritize user privacy and security.
Building Our First AccessibilityService 🛠️
Step 1: Create the Service Class
Let’s build a simple service that logs the name of the app and screen (Activity) that the user is currently viewing.
| // MyAccessibilityService.kt | |
| import android.accessibilityservice.AccessibilityService | |
| import android.view.accessibility.AccessibilityEvent | |
| import android.util.Log | |
| class MyAccessibilityService : AccessibilityService() { | |
| private val TAG = "MyAccessibilityService" | |
| override fun onAccessibilityEvent(event: AccessibilityEvent?) { | |
| Log.d(TAG, "onAccessibilityEvent: $event") | |
| } | |
| override fun onInterrupt() { | |
| Log.e(TAG, "onInterrupt: Service was interrupted.") | |
| } | |
| override fun onServiceConnected() { | |
| super.onServiceConnected() | |
| Log.d(TAG, "onServiceConnected: Service has been connected.") | |
| } | |
| } |
onAccessibilityEvent(event: AccessibilityEvent): This is the heart of the service. It’s a callback that fires whenever a UI event matching your configuration occurs (more on that next).onInterrupt(): Called when the system wants to interrupt the feedback your service is providing, usually in response to a user action like moving focus to a different control.onServiceConnected(): A convenient place to do any one-time setup for your service after the system has successfully connected to it.
Step 2: Create the Configuration File 📝
You must tell the system what your service is interested in. Listening to every single event would be a massive drain on battery and performance. We do this with an XML configuration file.
Create a new file in res/xml/accessibility_service_config.xml:
| <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" | |
| android:description="@string/accessibility_service_description" | |
| android:accessibilityEventTypes="typeWindowStateChanged" | |
| android:packageNames="com.google.android.youtube, com.google.android.gm" | |
| android:accessibilityFeedbackType="feedbackGeneric" | |
| android:notificationTimeout="100" | |
| android:canRetrieveWindowContent="true" /> |
Let’s break this down:
android:description: A user-facing description of what your service does. This is required.android:accessibilityEventTypes: The most important attribute. It specifies which event types you want to listen for. We’re usingtypeWindowStateChanged, which fires when the foreground window changes.android:packageNames: (Optional but recommended) A comma-separated list of packages you want to monitor. This is a crucial optimization. For our test, I’ve limited it to YouTube and Gmail. If you leave this out, you’ll monitor all apps.android:canRetrieveWindowContent: Setting this totruegives you permission to inspect the actual view hierarchy of the current screen. This is the “powerful” permission.
Step 3: Declare the Service in the Manifest 📣
Finally, register your service in AndroidManifest.xml.
| <application | |
| ... > | |
| ... | |
| <service | |
| android:name=".MyAccessibilityService" | |
| android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" | |
| android:exported="false"> | |
| <intent-filter> | |
| <action android:name="android.accessibilityservice.AccessibilityService" /> | |
| </intent-filter> | |
| <meta-data | |
| android:name="android.accessibilityservice" | |
| android:resource="@xml/accessibility_service_config" /> | |
| </service> | |
| </application> |
The two key parts are:
- The
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"ensures that only the system can bind to this service. - The
<meta-data>tag links the service to the configuration file we just created.
Step 4: Run and Manually Enable the Service 👉
This is the final, crucial step. You cannot start an AccessibilityService programmatically. The user must explicitly grant permission.
- Run your app on a device or emulator.
- Navigate to Settings -> Accessibility -> Installed apps (or Downloaded apps).
- Find your app in the list.
- Tap on it and toggle the switch to “On”. The system will show a warning and your service description.
Once you enable it, check Logcat. You should see your “Service has been connected” message.
Putting It All Together: A Working Example 🏁
Now, let’s update our service to log the window changes we configured it to listen for.
| // MyAccessibilityService.kt | |
| import android.accessibilityservice.AccessibilityService | |
| import android.view.accessibility.AccessibilityEvent | |
| import android.util.Log | |
| class MyAccessibilityService : AccessibilityService() { | |
| private val TAG = "MyAccessibilityService" | |
| override fun onAccessibilityEvent(event: AccessibilityEvent?) { | |
| // We are only listening for window state changes, but it's good practice to check | |
| if (event?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { | |
| val packageName = event.packageName?.toString() | |
| val className = event.className?.toString() | |
| Log.i(TAG, "Window Changed: \n Package: $packageName\n Class: $className") | |
| } | |
| } | |
| override fun onInterrupt() { | |
| Log.e(TAG, "onInterrupt: Service was interrupted.") | |
| } | |
| override fun onServiceConnected() { | |
| super.onServiceConnected() | |
| Log.d(TAG, "onServiceConnected: Service has been connected.") | |
| // You can set up your service's configuration here if you don't want to use XML | |
| // val info = AccessibilityServiceInfo() | |
| // ... | |
| // serviceInfo = info | |
| } | |
| } |
Now, with the service enabled, open the YouTube app and then the Gmail app. Watch Logcat (filtered by the “MyAccessibilityService” tag). You will see:
Window Changed: Package: com.google.android.youtube Class: com.google.android.apps.youtube.app.watchwhile.WatchWhileActivity … Window Changed: Package: com.google.android.gm Class: com.google.android.gm.GmailActivity
Success! Your service is now listening to and reporting UI events from other applications ✅.
Job Offers
Where to Go From Here 🚀
You’ve built a “listener.” You can now see what’s happening on the screen. The next logical step is to act.
This involves using the rootInActiveWindow property of your service to get the root AccessibilityNodeInfo of the view hierarchy. From there, you can:
- Find specific nodes using
findAccessibilityNodeInfosByText()orfindAccessibilityNodeInfosByViewId(). - Perform actions on those nodes, like
performAction(AccessibilityNodeInfo.ACTION_CLICK).
That’s a topic for another day, but with the foundation you’ve built here, you are well on your way to mastering one of Android’s most powerful APIs. Happy coding! 👨💻
A Note on Real-World Usage 🏢
You might be thinking that the example above is simple, and you’re right. But the principles are the foundation for incredibly powerful features.
In my own work on production applications at major tech companies, I’ve relied on AccessibilityService for critical tasks. One of the primary use cases is to monitor the state of another application to create a seamless, integrated experience between different apps. For example, knowing which app is in the foreground allows a companion app to intelligently change its own state or offer contextual help.
While I can’t dive into proprietary details, I can say that when implemented correctly, the AccessibilityService API is incredibly robust. The key to using it successfully in a production environment is a relentless focus on:
- Performance: Only listen for the exact events and packages you need. An inefficient service can degrade the performance of the entire device.
- Reliability: Account for the vast diversity of the Android ecosystem. Your service will run on countless devices and OS versions.
- Privacy: Always operate with the utmost respect for user data and transparency.
So, while we started with a simple logger, know that you’re learning a tool that powers sophisticated features in some of the most-used apps in the world.
✨ Stay in the Loop for More!
If you found this article helpful, you’re in for a treat.
I plan to publish more stories from my time at Google developing for the automotive space and from my experience at Meta working on Orion and the future of Augmented Reality.
To be the first to know when a new article drops, be sure to follow me on Medium and connect with me on LinkedIn.
Thank you for reading!
This article was previously published on proandroiddev.com.


