On this page
Validating Android push notifications, system notification shade cards, and heads-up alert banners on physical hardware represents one of the most frustrating gaps in modern mobile continuous integration. While push messages serve as the backbone for critical customer touchpoints—ranging from transaction confirmations and time-sensitive multi-factor authentication tokens to promotional alerts and background data syncs—verifying their end-to-end behavior has historically forced engineering teams into slow, fragile manual testing cycles. A malformed payload attribute, a misconfigured notification channel importance level, an unhandled PendingIntent bundle extra, or an ungranted Android 13+ POST_NOTIFICATIONS runtime permission can silently swallow delivery without raising an overt crash. By connecting terminal-based AI coding assistants such as Claude Code to physical Android smartphones through the Model Context Protocol (MCP) via ScreenSync MCP, developers can automate synthetic broadcast injection, inspect the system-level accessibility tree of the notification shade, interact with inline action buttons, and verify deep link navigation completely autonomously.
Key Takeaways
- System-level accessibility boundary bypass: ScreenSync MCP empowers Claude Code to cross application sandboxes and inspect the Android system notification shade (
com.android.systemui) directly viaget_ui_hierarchyand physical input injection. - Deterministic mock broadcast testing: Developers can simulate Firebase Cloud Messaging (FCM) deliveries locally via Android Debug Bridge (ADB) broadcast intents, eliminating dependencies on external cloud messaging backends or staging API networks.
- Non-invasive display perception: High-performance screen streaming operates over local networks using Android's native MediaProjection API (Android 7.0+, requiring zero root access and zero ADB overhead), maintaining approximately 94 ms median latency in tests.
- Decoupled control plane: Input actions such as expanding the notification shade, tapping notification action chips, and verifying dismiss swipes run through ADB over USB or wireless debugging.
- Automated Android 13+ permission audits: Claude Code inspects and toggles
POST_NOTIFICATIONSruntime states via ADB, confirming that notification channels degrade gracefully when permissions are revoked. - Unified cross-surface testing suite: ScreenSync MCP bridges 226 total MCP tools (29 mobile and desktop tools alongside 197 browser tools), enabling end-to-end verification of push alerts triggered from web dashboards and received on mobile hardware.
Why Testing Android Push Notifications Fails in Traditional CI/CD Pipelines
Testing Android push notifications in traditional CI/CD pipelines fails because standard instrumentation frameworks like Espresso are sandboxed within the application process. Sandboxed test runners cannot interact with system UI components, pull down the notification shade, or grant runtime permissions, forcing developers to rely on slow, manual device audits.
Mobile quality assurance suites have historically treated push notification testing as an untestable edge case. When engineering teams attempt to automate notification checks using standard toolchains, they immediately encounter three systemic architectural boundaries:
- The Application Process Sandbox Boundary: In-app UI testing frameworks such as Espresso and Robolectric execute strictly within the host app's instrumentation context. Once a push notification is dispatched by the Android OS, it renders inside the system server process under package
com.android.systemui. Because Espresso cannot bind to views outside its own target package, it cannot pull down the notification shade, inspect the text of a heads-up notification (HUN), or tap an action button like "Mark as Read" or "Confirm Payment". - Cloud Device Farm Network and Token Isolation: Relying on live Firebase Cloud Messaging (FCM) relays inside cloud-hosted device farms introduces severe flakiness. Every ephemeral test runner generates a unique device registration token that must be registered with staging backends. Network firewalls, token propagation delays, and unpredictable Google Play Services synchronization latencies frequently result in missed delivery windows and false-negative build failures.
- Android 13+ Runtime Permission Volatility: Starting in Android 13 (API level 33), applications must explicitly request the
android.permission.POST_NOTIFICATIONSruntime permission. Sandboxed test scripts often fail to account for first-launch permission dialog states, causing automated test suites to execute without active notification channels and reporting misleading passes while silent drops occur in production. - Synthetic Emulator Inaccuracies: Desktop emulators often lack authentic hardware surface compositor behavior and run stripped-down system images that handle foreground service banners and notification grouping differently than production hardware running custom OEM skins (such as Samsung One UI, Google Pixel, or Xiaomi HyperOS).
The emergence of autonomous AI coding assistants like Claude Code transforms this verification pipeline. Equipped with direct system-level tool execution through ScreenSync MCP, Claude Code can dispatch simulated broadcasts, pull down the real hardware notification shade, verify layout nodes against expected payload strings, and triage root-cause bundle errors directly in the device's logcat stream.
| Testing Dimension | In-Process Espresso / JUnit | Cloud Device Farms (Appium) | ScreenSync MCP + Claude Code (Real Hardware) |
|---|---|---|---|
| Execution Boundary | Sandboxed inside app process | External WebDriver protocol | Full system-level OS interaction via MCP tools |
| System UI Inspection | Impossible (com.android.systemui blocked) |
Brittle XPath selectors; high latency | Native structural inspection via get_ui_hierarchy |
| Push Delivery Method | Mocked in-memory stubs only | Cloud FCM network dependent | Local ADB broadcast injection or live FCM |
| Visual Mirror Latency | No live video feedback | 800 ms to 3,000 ms cloud lag | ~94 ms median local LAN MediaProjection |
| Notification Shade Control | Unsupported | Unreliable swipe emulation | Deterministic ADB gestures and status bar commands |
| Logcat Diagnostics | Filtered to app PID only | Post-test text log artifact | Real-time streaming triage via get_logcat |
| Cross-Platform Handoff | Isolated to mobile APK | Fragmented mobile/web runners | Unified 226 tools (29 mobile/desktop + 197 browser) |
| Test Maintenance Cost | High custom mock maintenance | High selector maintenance | Intent-driven natural language prompts |
Anatomy of Android Push Notification Verification: FCM, Intents, and Channels
Android push notification verification evaluates three interconnected OS layers: runtime notification permissions introduced in Android 13, local broadcast receiver intent resolution, and system notification channel routing. Autonomous verification requires asserting both structural accessibility attributes in com.android.systemui and visual banner rendering states on physical devices.
To establish comprehensive test assertions, developers and AI agents must understand the exact lifecycle an incoming message navigates through the Android operating system:
| Notification Stage | Operating System Mechanism | Responsible Component | Failure Manifestation |
|---|---|---|---|
| 1. Ingestion | Network push or ADB broadcast | FirebaseMessagingService |
Missing payload keys, JSON deserialization error |
| 2. Channel Triage | Notification channel assignment | NotificationManager |
Notification muted, dropped, or sent to wrong channel |
| 3. Permission Check | POST_NOTIFICATIONS verification |
Android OS Security Core | Silent drop without user feedback on Android 13+ |
| 4. Surface Rendering | Heads-up banner or lock screen | com.android.systemui |
Bad icon tinting, truncated summary, unstyled action chip |
| 5. Interaction Handoff | PendingIntent execution |
Target Activity / Jetpack Compose |
Blank destination screen, corrupted task backstack |
1. Handling Android 13+ Runtime Permissions
Prior to Android 13, notification permissions were granted automatically at installation time. Modern Android releases treat notifications as dangerous permissions. In production code, your application must declare the permission in AndroidManifest.xml and request it dynamically: For a comprehensive guide to automating system permission dialogs and denials, see testing Android runtime permissions with Claude Code and MCP.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.screensync.pushdemo">
<!-- Mandatory runtime permission for Android 13 (API 33) and above -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.ScreenSyncDemo">
<!-- Custom Firebase Messaging Service Receiver -->
<service
android:name=".services.AppFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.OrderDetailsActivity"
android:exported="true"
android:parentActivityName=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="www.screensyncmcp.tech"
android:pathPrefix="/orders" />
</intent-filter>
</activity>
</application>
</manifest>
2. Robust Notification Channel Setup in Kotlin
Beginning with Android 8.0 (API level 26), all notifications must be assigned to a specific NotificationChannel. If a channel is deleted, blocked by the user, or given IMPORTANCE_NONE, notifications assigned to it will never render. Here is a production-grade Kotlin implementation demonstrating channel creation, data parsing, and deep-link PendingIntent construction:
package com.example.screensync.pushdemo.services
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.app.NotificationCompat
import com.example.screensync.pushdemo.R
import com.example.screensync.pushdemo.ui.OrderDetailsActivity
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
class AppFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
// Extract custom key-value payload attributes
val title = remoteMessage.data["title"] ?: remoteMessage.notification?.title ?: "Order Update"
val message = remoteMessage.data["message"] ?: remoteMessage.notification?.body ?: "Your shipment has arrived."
val orderId = remoteMessage.data["orderId"] ?: "ORD-9921"
val deepLinkUrl = remoteMessage.data["link"] ?: "https://www.screensyncmcp.tech/orders/$orderId"
createNotificationChannel()
displayNotification(title, message, orderId, deepLinkUrl)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID_ORDERS,
"Order & Delivery Updates",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Real-time alerts for customer shipment tracking"
enableVibration(true)
setShowBadge(true)
}
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun displayNotification(title: String, message: String, orderId: String, deepLinkUrl: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(deepLinkUrl), this, OrderDetailsActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra("EXTRA_ORDER_ID", orderId)
}
val pendingIntent = PendingIntent.getActivity(
this,
orderId.hashCode(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Action button intent to confirm receipt
val confirmIntent = Intent("com.example.screensync.ACTION_CONFIRM_DELIVERY").apply {
putExtra("EXTRA_ORDER_ID", orderId)
}
val confirmPendingIntent = PendingIntent.getBroadcast(
this,
orderId.hashCode() + 1,
confirmIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID_ORDERS)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(message)
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_check, "Confirm Delivery", confirmPendingIntent)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(orderId.hashCode(), notificationBuilder.build())
}
companion object {
const val CHANNEL_ID_ORDERS = "channel_orders_high_priority"
}
}
Dual-Plane Architecture: How ScreenSync MCP Connects Claude Code to System UI
ScreenSync MCP connects Claude Code to Android system UI through a dual-plane architecture separating visual observation from device control. The Android app streams display frames over local networks using native MediaProjection without ADB overhead, while the desktop hub injects broadcast intents and touch gestures via ADB over USB or wireless debugging.
Understanding the internal division of responsibilities within ScreenSync MCP explains why it reliably automates system UI components that block other mobile test frameworks. The system operates across three decoupled layers:
[ Android Physical Device ]
|-- (Hardware MediaProjection / LAN) ----> [ ScreenSync Hub (Node.js 18+) ] <--(stdio MCP)--> [ Claude Code CLI ]
| |
+<-------- (ADB Daemon / USB or Wi-Fi) --------------+
1. Observation Plane: Zero-Root MediaProjection Capture
Display capture on physical Android hardware is governed by Android's native MediaProjection API (supported on Android 7.0 or newer). During the initial session startup, the user grants interactive consent on the device screen. Once approved, the ScreenSync MCP mobile application captures display buffers directly from the hardware surface compositor, compresses them into efficient local video frames, and transmits them across the local Wi-Fi network directly to the desktop hub on port 3000.
Because this capture mechanism runs natively without rooting and operates completely independently of ADB, it never saturates the USB bridge. In our benchmark tests across local area networks, this architecture achieves a median visual mirror latency of approximately 94 milliseconds. When Claude Code invokes the get_latest_screenshot MCP tool, the desktop hub grabs the active hardware frame instantly without triggering costly screen-scraping subprocesses.
2. Execution Plane: Direct ADB Control and System Broadcasts
While visual perception flows smoothly across the local network, actionable commands execute deterministically through ADB. When Claude Code interacts with the device, it uses the desktop hub's 12 mobile control tools—such as control_tap, control_swipe, control_type, control_key, and control_open_url. For push testing, the hub translates AI commands into native Android system calls, including expanding the notification shade, injecting simulated broadcasts via am broadcast, and checking system logs via get_logcat.
3. Structural Parsing: The System Accessibility Tree
ScreenSync MCP provides the get_ui_hierarchy tool, which dumps the active Android accessibility node hierarchy across the entire screen. Unlike in-app test frameworks that are restricted to the host application's view hierarchy, get_ui_hierarchy captures the complete window manager state. When the notification shade is pulled down, get_ui_hierarchy extracts every notification item under com.android.systemui, revealing:
- The container resource ID:
com.android.systemui:id/notification_stack_scroller - Notification header text:
android:id/title - Body text:
android:id/textorandroid:id/big_text - System timestamps:
android:id/time - Interactive action chips:
android:id/actionsand button child nodes
This allows Claude Code to verify notification rendering deterministically by querying layout attributes, eliminating the coordinate drift associated with pure computer vision approaches. For deeper background on why structural inspection outperforms raw visual guessing, read our analysis on how AI agents use Android UI trees not screenshots.
| System Component | Transport Protocol | Network Scope | Primary Function in Push QA |
|---|---|---|---|
| Android Mobile App | MediaProjection / HTTP | Local Subnet (LAN) | Real-time display streaming at ~94 ms median latency |
| ScreenSync Hub | Node.js stdio JSON-RPC | Workstation Localhost | MCP server exposing 226 tools, ADB command translator |
| ADB Control Bridge | USB or Wireless TCP/IP | Local Host-to-Device | Injects broadcast intents, pulls shade, fires keycodes |
| Claude Code Client | Local CLI Subprocess | Developer Terminal | Evaluates assertions, triggers tools, triages defects |
Step-by-Step Setup: Configuring Hardware, Desktop Hub, and Claude Code
Setting up ScreenSync MCP with Claude Code requires four sequential configuration steps: launching the Node.js 18+ desktop hub, pairing the physical Android 7.0+ device via mDNS or QR code, authorizing ADB debugging over USB or Wi-Fi, and registering the local stdio MCP server in Claude Code.
Step 1: Workstation and Hardware Prerequisites
Ensure your developer machine and mobile test device satisfy the following requirements:
- Workstation Operating System: Windows 10/11, macOS, or Linux running Node.js 18 or higher.
- Android Physical Device: A physical smartphone or tablet running Android 7.0 (API level 24) or later connected to the same local Wi-Fi network as your workstation.
- Android SDK Platform Tools:
adbconfigured in your systemPATH. Verify by runningadb versionin your terminal. If you experience connection issues, consult our troubleshooting guide on how to fix ADB no devices or emulators found fast. - Target Mobile Application: Debug APK installed on the phone containing your notification service and intent filters.
Step 2: Launch the Desktop Hub
Download the desktop hub archive from the official site. Extract the files to a local directory on your machine and launch the process using the platform-specific launcher:
# On Windows (PowerShell or Command Prompt)
cd C:\tools\screensync-hub
.\start-hub.bat
# On macOS or Linux (Terminal)
cd ~/tools/screensync-hub
chmod +x start-hub.sh
./start-hub.sh
Upon launching, the desktop hub binds locally to HTTP port 3000 to listen for incoming MediaProjection frames and initializes a JSON-RPC Model Context Protocol server over stdio. The terminal output will display your local authentication token (SCREEN_SYNC_TOKEN) and an ASCII pairing QR code.
Step 3: Register ScreenSync MCP in Claude Code
In your workstation terminal, register the local hub with Claude Code:
claude mcp add screensync node "/path/to/screensync-hub/screensync-hub.js"
If you are using Claude Desktop instead of Claude Code, configure claude_desktop_config.json (accessible via Settings > Developer > Edit Config):
{
"mcpServers": {
"screensync": {
"command": "node",
"args": ["/path/to/screensync-hub/screensync-hub.js"],
"env": {
"SCREEN_SYNC_TOKEN": "your_hub_token_here"
}
}
}
}
(Note: ChatGPT's web application requires remote HTTPS endpoints and does not support local stdio MCP servers. ScreenSync MCP is built specifically for local coding agents including Claude Code, Claude Desktop, Cursor, and VS Code).
Step 4: Install the Android App and Authorize ADB
- Install the free ScreenSync MCP application (
com.screensync.mcp) from Google Play. - Open the app on your Android device. It will automatically detect the desktop hub via mDNS discovery over your local Wi-Fi, or you can scan the pairing QR code from the hub terminal.
- Tap Start Projection on your device and accept Android's native MediaProjection confirmation dialog.
- Enable Developer Options on the device (Settings > About Phone > tap Build Number 7 times), then enable USB Debugging or Wireless Debugging. If setting up wireless debugging without cables, follow our guide on how to pair Android wireless debugging to an MCP server without USB.
- Verify that your device is recognized by running
adb devicesin your workstation terminal. For full setup instructions, see our ScreenSync MCP setup documentation.
Practical Verification Workflows: Broadcast Injections, Shade Interaction, and Handoffs
Practical push notification verification workflows with Claude Code execute three core testing patterns: triggering mock FCM data broadcasts via ADB, expanding the notification shade to assert action buttons, and clicking notification deep links to validate target composable navigation on physical Android hardware.
Let us explore three real-world automation workflows where Claude Code drives ScreenSync MCP tools to validate end-to-end notification handling.
Workflow 1: Local Mock FCM Broadcast Injection without Backend Servers
Instead of triggering push messages from external staging dashboards, Claude Code can simulate incoming FCM data packets locally by injecting broadcast intents directly into the app's receiver via ADB.
Developer Prompt:
"Claude, grant notification permission to com.example.screensync.pushdemo, inject a mock high-priority order update broadcast with orderId 'ORD-5542', verify that the notification arrives, and check logcat for processing errors."
Step 1: Pre-grant runtime permission
Claude Code ensures that Android 13+ permission dialogs do not block delivery:
adb shell pm grant com.example.screensync.pushdemo android.permission.POST_NOTIFICATIONS
Step 2: Inject the broadcast intent
Claude Code fires a simulated FCM delivery intent through ADB:
adb shell am broadcast \
-a com.google.android.c2dm.intent.RECEIVE \
-n com.example.screensync.pushdemo/com.google.firebase.iid.FirebaseInstanceIdReceiver \
--es title "Order Dispatched" \
--es message "Package ORD-5542 is out for delivery." \
--es orderId "ORD-5542" \
--es link "https://www.screensyncmcp.tech/orders/ORD-5542"
The command returns:
Broadcasting: Intent { act=com.google.android.c2dm.intent.RECEIVE flg=0x400000 pkg=com.example.screensync.pushdemo cmp=com.example.screensync.pushdemo/com.google.firebase.iid.FirebaseInstanceIdReceiver (has extras) }
Broadcast completed: result=0
Step 3: Triage delivery telemetry via logcat
Claude Code calls the get_logcat MCP tool with filter arguments:
{
"name": "get_logcat",
"arguments": {
"filter": "AppFirebaseMessagingService:D *:S",
"lines": 30
}
}
The returned log entries confirm successful payload parsing:
10-27 10:14:02.112 14820 14855 D AppFirebaseMessagingService: onMessageReceived: orderId=ORD-5542 title=Order Dispatched
10-27 10:14:02.128 14820 14855 D AppFirebaseMessagingService: Notification posted to channel: channel_orders_high_priority with ID: 1982731
Workflow 2: Interacting with the Notification Shade and Action Buttons
When notifications arrive while another app is active or the screen is on the home launcher, they frequently collapse into the system notification shade. Testing requires pulling down the shade and interacting with individual notification cards.
Developer Prompt:
"Claude, pull down the Android notification shade, verify that the 'Order Dispatched' notification is visible with orderId 'ORD-5542', click the 'Confirm Delivery' action button, and capture a screenshot to prove the confirmation dialog rendered."
Step 1: Expand the notification shade
Claude Code expands the notification drawer using an ADB status bar command or a top-edge swipe:
adb shell cmd statusbar expand-notifications
Alternatively, Claude Code can invoke the control_swipe tool to pull down from coordinate x: 540, y: 10 to x: 540, y: 1200.
Step 2: Inspect accessibility hierarchy for notification nodes
Claude Code queries the structural layout using get_ui_hierarchy:
{
"name": "get_ui_hierarchy",
"arguments": {}
}
The returned XML tree includes the active system UI nodes:
<node package="com.android.systemui" class="android.widget.FrameLayout" resource-id="com.android.systemui:id/notification_stack_scroller">
<node class="android.view.ViewGroup" resource-id="com.android.systemui:id/expandableNotificationRow">
<node text="Order Dispatched" resource-id="android:id/title" bounds="[120,410][960,460]" />
<node text="Package ORD-5542 is out for delivery." resource-id="android:id/text" bounds="[120,465][960,515]" />
<node text="Confirm Delivery" resource-id="android:id/action0" bounds="[140,540][480,620]" clickable="true" />
</node>
</node>
Step 3: Trigger the action button tap
Claude Code identifies the exact bounding box of the "Confirm Delivery" button ([140,540][480,620]), calculates the center coordinate (x: 310, y: 580), and calls control_tap:
{
"name": "control_tap",
"arguments": {
"x": 310,
"y": 580
}
}
Step 4: Visual confirmation
Claude Code captures the updated display using get_latest_screenshot, verifying that a confirmation toast or snackbar appeared on the screen.
Workflow 3: End-to-End Notification Tap to Deep Link Verification
When a user taps the body of a notification, Android triggers the embedded PendingIntent, which should launch the application and route directly to the designated destination activity. For detailed strategies on verifying Intent filters and URI schemes, review our companion guide on testing Android deep links and app links with Claude Code and MCP.
Developer Prompt:
"Claude, tap the body of the 'Order Dispatched' notification, verify that OrderDetailsActivity launches displaying order 'ORD-5542', press the hardware back button, and verify that the application returns to MainActivity rather than quitting."
- Tap Notification Card: Claude Code taps coordinate
x: 540, y: 460(the main body of the notification card). Android dispatches thePendingIntentand collapses the notification shade. - Assert Target Screen: Claude Code calls
get_ui_hierarchyto confirm thatOrderDetailsActivityhas loaded with text node"Order Details: ORD-5542". - Verify Backstack Navigation: Claude Code calls
control_keywith keycode4(KEYCODE_BACK). - Validate Parent View: Claude Code verifies via
get_ui_hierarchythat the active screen isMainActivity(resource-id="com.example.screensync.pushdemo:id/dashboard_root"). If the app exits to the home launcher, Claude Code flags a backstack construction defect in thePendingIntent.
Debugging Notification Edge Cases and Failure Modes
Debugging mobile notification failures requires triaging permission rejections, payload serialization mismatches, and notification channel silences. Claude Code accelerates triage by combining real-time logcat streaming with accessibility tree dumps to identify whether drops occurred during OS delivery, background service execution, or UI rendering.
When notifications fail in production, developers often waste hours guessing whether the breakdown occurred at the server payload level, the Android OS dispatch stage, or the client rendering layer. Claude Code helps you pinpoint the root cause immediately across four common failure scenarios:
1. The Android 13+ Silent Permission Trap
If an app fails to check ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS), the Android OS silently drops notifications without throwing an exception.
- Triage in Claude Code: Inspect logcat for
NotificationService: Notification not allowed for package com.example.... Claude Code can immediately runadb shell dumpsys notification --package com.example.screensync.pushdemoto inspect the package's notification grant state.
2. Notification Channel Blocked or Importance Downgraded
If a user or OEM battery optimizer disables a notification channel, calling notificationManager.notify() completes with no visual display.
- Triage in Claude Code: Execute
adb shell cmd notification get-channels com.example.screensync.pushdemo. Claude Code inspects the output to confirm whetherimportanceis set to0(IMPORTANCE_NONE) or if the channel has been muted.
3. Missing FLAG_IMMUTABLE on Android 12+ (API 31+)
Starting in Android 12, creating a PendingIntent without specifying either FLAG_IMMUTABLE or FLAG_MUTABLE causes an immediate fatal crash: java.lang.IllegalArgumentException: Targeted S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified.
- Triage in Claude Code: Claude Code runs
get_logcatwith a crash filter (AndroidRuntime:E *:S), captures the stack trace, identifies the line in your Kotlin service wherePendingIntent.getActivity()was invoked, and offers an instant inline code fix.
4. Background Service Data Payload Restrictions
When an app is in the background and an FCM message containing both notification and data keys is received, the Android system tray automatically handles the notification, and the data payload is only passed to the launcher activity if the user taps the card. If your app expects onMessageReceived() to run in the background, you must send data-only messages.
- Triage in Claude Code: Claude Code tests both message types using
am broadcast, checking whetheronMessageReceivedfires when the app is in the foreground versus background.
Implementation Checklist for Production Teams
Production engineering teams implement autonomous notification testing by establishing automated permission grants, maintaining standardized broadcast intent fixtures, and enforcing dual-layer accessibility and screenshot assertions. This checklist ensures reliable regression testing across continuous integration and local developer environments.
Use this operational checklist to integrate ScreenSync MCP and Claude Code into your mobile testing workflow:
- [ ] Verify that workstation runs Node.js 18 or higher with platform-tools (
adb) inPATH. - [ ] Download and unpack the ScreenSync MCP desktop hub.
- [ ] Install the free ScreenSync MCP app (v2.5.7) from Google Play on an Android 7.0+ device.
- [ ] Connect phone and workstation to the same local Wi-Fi subnet; enable USB or Wireless Debugging.
- [ ] Launch the desktop hub (
start-hub.batorstart-hub.sh) and grant MediaProjection on the phone. - [ ] Register the MCP server in Claude Code using
claude mcp add screensync node "/path/to/screensync-hub.js". - [ ] Prepare mock broadcast intent fixtures containing test payload attributes (
title,message,orderId). - [ ] Add automated pre-test permission grants (
adb shell pm grant ... POST_NOTIFICATIONS). - [ ] Implement dual-layer assertions combining
get_ui_hierarchystructural checks withget_latest_screenshotvisual verification. - [ ] Validate notification shade expansion and action button taps across physical hardware devices.
| Component | Minimum Version | Pricing (USD) | Primary Responsibility |
|---|---|---|---|
| ScreenSync MCP Android App | 2.5.7 | Free on Google Play | Zero-root MediaProjection streaming |
| ScreenSync MCP Desktop Hub | 1.14.4 | Free download | Stdio MCP bridge and ADB command runner |
| ScreenSync MCP Browser Extension | 1.14.4 | Free download | MV3 browser automation and web triggers |
| Node.js Workstation Runtime | 18.0.0+ | Free open source | Hosts local HTTP (port 3000) and stdio pipe |
| Android Operating System | 7.0+ (API 24+) | Native OS | Target runtime environment for push tests |
Conclusion
Testing Android push notifications and system notification shade interactions no longer requires slow, manual device handling or brittle cloud device farm configurations. By pairing Claude Code with ScreenSync MCP's local dual-plane architecture, mobile engineering teams can automate synthetic broadcast dispatches, inspect system-level UI hierarchies, verify notification action clicks, and validate deep-link backstacks directly on real physical Android hardware.
ScreenSync MCP provides the ScreenSync MCP Android app, ScreenSync MCP hub, and ScreenSync MCP browser extension for development teams worldwide. Explore our technical overview on ScreenSync MCP, review supported tools on our Android MCP servers reference, and Get it on Google Play to start automating real mobile devices today.
Questions, answered
Why can't traditional Espresso tests verify the Android notification shade?
Espresso executes strictly within the target application's process sandbox and cannot bind to views outside its own package. System notifications render inside the system server under package com.android.systemui, preventing Espresso from pulling down the notification shade, reading notification cards, or clicking action buttons.
Does ScreenSync MCP require root access or ADB to capture mobile notifications?
No, screen capture uses Android's native MediaProjection API after interactive user consent, requiring neither root access nor ADB. However, device control actions—such as pulling down the notification shade, injecting simulated broadcasts, or tapping action buttons—require an authorized ADB link over USB or wireless debugging.
How can Claude Code simulate push notifications without connecting to a real FCM server?
Claude Code fires mock FCM delivery intents locally using the ADB am broadcast command directed at the application's Firebase receiver. This approach simulates incoming payloads instantly without hitting external cloud servers, preventing test flakiness and avoiding accidental notifications to real users.
How does ScreenSync MCP read notification text and buttons inside the system shade?
ScreenSync MCP provides the get_ui_hierarchy tool, which queries Android's system-level accessibility node tree. When the notification shade is expanded, get_ui_hierarchy extracts all active UI elements under com.android.systemui, including notification titles, summary texts, timestamps, and interactive action button bounds. This structural data gives agents deterministic element locators without relying on brittle pixel coordinate calculations.
What happens if an application does not have POST_NOTIFICATIONS permission on Android 13+?
Starting in Android 13 (API level 33), the operating system silently drops notifications if the POST_NOTIFICATIONS runtime permission has not been granted. Claude Code can audit and pre-grant this permission during test runs using adb shell pm grant to ensure uninterrupted delivery.
Can I test notification deep links and backstack navigation using ScreenSync MCP?
Yes, Claude Code can tap the notification card, verify that the target Activity or Jetpack Compose destination opens with the expected parameters, and trigger the back keycode (KEYCODE_BACK) using control_key to ensure the app navigates back to the parent screen rather than exiting.
How many tools does ScreenSync MCP expose for mobile and web automation?
ScreenSync MCP provides 226 MCP tools in total. This includes 29 phone and desktop tools (12 control, 3 OS, and 14 capture and telemetry tools such as get_latest_screenshot, get_ui_hierarchy, and get_logcat) alongside 197 browser tools for web-driven workflows. This dual-surface environment allows coding agents to orchestrate complex end-to-end user journeys bridging mobile apps and web dashboards.
Can I use ScreenSync MCP with the web-based ChatGPT interface?
No, ChatGPT's web interface requires remote cloud-hosted HTTPS MCP servers, whereas ScreenSync MCP is designed for local stdio transports to ensure privacy and low latency. ScreenSync MCP is built specifically for local AI coding clients including Claude Code, Claude Desktop, Cursor, and VS Code.




