Updated 2026-10-13: This article records the historical approach of observing notifications through accessibility events before Android 4.3. That approach is outdated for a general notification center. Android 4.3 (API 18) introduced
NotificationListenerService; section 4 adds a minimal listener for Android 7.0 (API 24) and later. The legacy examples explain the earlier implementation and do not provide a complete notification list.
1. Accessibility Service in Practice
The previous article introduced accessibility services and their basic configuration. This article follows the original implementation of a simple notification center. Android’s system notification UI displays notifications from apps and system components, such as missed calls, screenshots, weather, and news updates. Before Android 4.3, ordinary third-party apps did not have a public API to query the active notification list. Accessibility events offered a limited way to observe notifications; Android 4.3 added the dedicated NotificationListenerService.getActiveNotifications() API.
The following sections preserve the accessibility-based implementation. Layout resources came from the original project, so these excerpts are not a complete Android project. New notification centers should use the dedicated API in section 4.
2. Building a Third-Party Notification Center
2.1 Extending AccessibilityService
Following the previous article, create a service extending AccessibilityService. Check the event and payload types before forwarding the notification to this application’s interface:
1 | import android.accessibilityservice.AccessibilityService; |
The callbacks have distinct purposes:
onServiceConnected()runs when the system connects the service, not when an Activity binds to it. This implementation handles only notifications, so it subscribes only toTYPE_NOTIFICATION_STATE_CHANGED.onInterrupt()asks the service to interrupt accessibility feedback; it is not a disconnection callback.onAccessibilityEvent()receives matching events. Check the type ofgetParcelableData()before casting it toNotification. See the AccessibilityEvent reference.
Once I had the Notification, I sent it to the Activity through a broadcast. The updated example restricts delivery to the app’s package and uses a signature permission on both sender and receiver. Package targeting limits where the broadcast goes; the receiver’s permission limits who can send data to it.
I also ran into the broadcast payload-size limit with large notifications, including screenshots and missed calls. A full Notification can contain large RemoteViews, images, or extras and trigger TransactionTooLargeException. The original post suggested keeping contentView temporarily in Application, but that doesn’t handle cross-process access, process restarts, or other large fields. For a current implementation, keep the fields you need in the app’s data layer and let the UI read snapshots instead of repeatedly broadcasting whole notification objects.
2.2 Registering the Service in Manifest
This example assumes an applicationId of com.example.notificationcenter. Change the custom permission prefix if using another package. Permission declarations belong under <manifest>, while the service belongs under <application>:
1 | <!-- Direct children of <manifest>. --> |
BIND_ACCESSIBILITY_SERVICE restricts service binding to the system. The metadata refers to res/xml/accessibilityserviceconfig.xml; XML service configuration is available from Android 4.0 (API 14):
1 |
|
This selects the notification events handled by the service.
2.3 Receiving and Displaying Notifications
The Activity excerpt below reads the notification and attempts to display its legacy custom RemoteViews. The activity_main layout must contain a TextView named notify_test_textview and a LinearLayout named root_layout. Compile against SDK 33 or higher and declare this Activity in the manifest.
The receiver is registered in onStart() and unregistered in onStop(), so it stops receiving notifications when the Activity stops. A signature permission protects both ends. Android 13 (API 33) and later also use RECEIVER_NOT_EXPORTED. See the context-registered receiver lifecycle.
1 | import android.app.Activity; |
The user must enable the service in Settings → Accessibility. The manifest declaration alone does not grant access.
RemoteViews is a description of a remote layout, not a View that can be passed directly to addView(). Call apply() to obtain a View first. Notification.contentView was deprecated in API 24 and may be absent for modern template notifications, so this cannot reproduce every notification. The excerpt preserves the original display approach with failure handling.
A complete notification center still takes some work: updates, removals, ordering, user interaction, and process restarts all need handling. I couldn’t share the full implementation because it was a company project. If you’re interested, feel free to get in touch privately.
3. Summary and Limitations
This post took me a while to finish. It also gave me a chance to wrap up the work from that project. These were the problems left in the implementation at the time:
- Accessibility events do not supply the complete current notification list or reconstruct notifications that have already been dismissed.
- I kept the data only in memory, so it was lost when the service was killed. I tried saving whole notification objects with an object database, db4o, but couldn’t get it working. An app can persist the text, package names, timestamps, and other fields it needs in its own database; whole
NotificationorRemoteViewsobjects aren’t a stable persistence format. - Not every system change produces an observable notification event. USB state, for example, should be observed through the relevant system API rather than inferred entirely from notifications.
4. Notification Listening After Android 4.3
NotificationListenerService has supplied posted/removed callbacks and active-notification queries since API 18. The following example uses both onListenerConnected() from API 21 and onListenerDisconnected() from API 24, so its minimum runtime version is Android 7.0. From API 24, these callbacks run on the main thread.
Declare the dedicated service inside <application>. This approach does not need the accessibility service or internal broadcast shown earlier:
1 | <service |
The user must also grant notification access in system settings. This is separate from accessibility authorization and Android 13’s POST_NOTIFICATIONS permission to post notifications.
This listener retains only notification keys to demonstrate synchronization on connection, updates, and removals. It does not read or log notification bodies:
1 | import android.service.notification.NotificationListenerService; |
Call getActiveNotifications() only after connection. It synchronizes notifications that are still active and visible to this listener, not a history of dismissed notifications. After disconnection, an empty local set represents unavailable state, not proof that the device has no notifications. A real UI should observe data-layer snapshots that include connection status and move persistence work out of callbacks.
From Android 15 (API 35), untrusted notification listeners cannot read unredacted content from notifications in which an OTP is detected. Notification access therefore does not guarantee access to every field. See Android 15 notification-content protections.
About Me && Blog
Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”
- Blogger Intro
- Blog Content Navigation: A guide for my blog content.
- Curated Excellent Blog Articles - Android Performance Optimization Must-Knows
- Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.
One walks faster alone, but a group walks further together.
