ScreenSync MCP guide

Testing Android Foreground Services with Claude Code and MCP

Learn how to test Android foreground services, Android 14/15 type restrictions, and background timeouts using Claude Code and ScreenSync MCP on real devices.

Testing Android Foreground Services with Claude Code and MCP

Modern mobile operating systems have progressively dismantled unbounded background execution. Starting in Android 8.0 Oreo and culminating in strict architectural enforcements across Android 14 (API level 34) and Android 15 (API level 35), the Android runtime mandates that any long-running task requiring guaranteed CPU execution must run as an explicit foreground service. Yet for engineering teams, verifying foreground services across physical devices remains one of the most notoriously brittle frontiers in mobile quality assurance. A service that behaves impeccably when launched from an active developer session will abruptly crash with a fatal ForegroundServiceStartNotAllowedException when triggered from a background worker, or throw an unforgiving SecurityException if its manifest lacks specific foreground service type declarations.

By integrating Claude Code—Anthropic’s agentic command-line software engineering tool—with ScreenSync MCP, mobile developers can construct fully autonomous, real-hardware verification loops. Rather than relying on headless unit mocks or fragile Appium scripts, developers can deploy an AI agent that directly interfaces with the Model Context Protocol (MCP). The agent orchestrates user taps to initiate flows, inspects live screen frames and notification drawers, queries the native Android UI hierarchy, and monitors low-level Android Debug Bridge (ADB) logcat output in real time.

Key Takeaways & Architecture Overview

  • Mandatory Service Typing: Android 14 and 15 require explicit declaration of foregroundServiceType in the manifest and runtime; omitting types or calling unauthorized types causes immediate app crashes.
  • Autonomous Real-Device Verification: Claude Code connects directly to the ScreenSync MCP desktop hub via stdio, orchestrating test routines without manual scripting.
  • Zero-Root MediaProjection Capture: ScreenSync MCP uses Android’s native MediaProjection API to stream screen frames to Claude at ~94 ms median LAN latency, requiring no root and no ADB for visual capture.
  • Precision Control via ADB: Taps, swipes, text entry, and system shell telemetry (dumpsys activity services) are dispatched through ADB over USB or wireless debugging.
  • Deterministic Exception Trapping: AI agents autonomously trigger background states, verify notification persistence, enforce shortService three-minute timeouts, and parse logcat to isolate regressions before production deployment.

Architectural Evolution of Android Foreground Services in API 34 and API 35

Android foreground services in API 34 and API 35 require explicit type declarations in the manifest, targeted runtime permissions, and user-initiated execution contexts. The operating system actively terminates non-compliant services, throwing ForegroundServiceStartNotAllowedException or SecurityException when apps attempt background invocation without meeting system exemptions or active UI interaction criteria.

For nearly a decade, Android developers treated Service.startForeground() as an all-purpose escape hatch to keep network synchronization, file downloads, audio playback, and location tracking alive when an app moved off-screen. Beginning in Android 14, Google replaced this permissive model with mandatory specialization. Apps targeting API level 34 or higher must declare at least one specific android:foregroundServiceType attribute for every foreground service inside AndroidManifest.xml. Declaring a generic service without a type triggers an instantaneous IllegalArgumentException at launch.

Furthermore, each individual foreground service type requires its own corresponding system permission. For example, syncing offline data requires android.permission.FOREGROUND_SERVICE_DATA_SYNC, while location tracking requires both android.permission.FOREGROUND_SERVICE_LOCATION and an established runtime location grant (ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION). If an application attempts to call startForeground() specifying a type flag that has not been granted by the user or declared in the manifest, the Android runtime throws a fatal SecurityException.

In Android 15 (API level 35), system restrictions tighten even further. The operating system limits background service starts to narrow, deterministic windows. Apps can no longer spawn foreground services from arbitrary broadcast receivers—such as BOOT_COMPLETED or network change callbacks—unless the application already holds an active top activity, has a temporary binding exemption, or possesses an explicit system power management exemption. Verifying these complex state transitions demands testing on authentic hardware environments where the OS task manager and power management daemons operate without simulation artifacts.


Common Foreground Service Failure Modes and Exception Traps

Foreground service failure modes on modern Android stem from three critical execution hurdles: background invocation bans resulting in ForegroundServiceStartNotAllowedException, undeclared type permissions triggering SecurityException crashes, and execution exceeding strict time limits such as the three-minute threshold for shortService that forces system termination and Application Not Responding states.

When auditing production mobile applications, engineering teams encounter recurring categories of foreground service failures that evade standard unit tests:

  1. ForegroundServiceStartNotAllowedException on Background Invocations: Introduced in Android 12 and strictly enforced in Android 14 and 15, this runtime exception occurs whenever an application attempts to call Context.startForegroundService() while the app resides in a background state. Common culprits include alarms scheduled via AlarmManager without exact alarm permissions, deferred push notifications, and background location geofence transitions.
  2. Missing POST_NOTIFICATIONS Runtime Permission: Since Android 13 (API level 33), posting notifications requires explicit runtime consent. If a foreground service starts successfully but the user has denied the notification permission, the service runs in a degraded state. On certain OEM builds (such as Samsung One UI and Xiaomi HyperOS), starting a foreground service without an active notification channel can result in immediate silent process termination.
  3. shortService 3-Minute Timeout Termination: Android 14 introduced android:foregroundServiceType="shortService" for bounded, critical operations that cannot be deferred to WorkManager. However, shortService carries a hard, system-enforced three-minute (180 second) timeout. If the service fails to call stopSelf() within this window, the operating system invokes Service.onTimeout() and immediately kills the process with an ANR (Application Not Responding) if work continues.
  4. Notification Channel Misconfigurations: Calling startForeground() with a notification that lacks a valid, registered NotificationChannel throws an unrecoverable system exception. Even when a channel exists, assigning it IMPORTANCE_NONE on modern Android versions will cause the system to reject the foreground elevation.
  5. Memory Pressure and OEM Aggressive Task Killers: Even when all manifest declarations are correct, heavily customized OEM skins aggressively demote foreground services whose parent process consumes excessive heap memory during background execution. Simulating realistic device loads is essential to evaluate whether a service recovers gracefully under system pressure.

Comprehensive Comparison: Foreground Service Types, Permissions, and System Enforcements

Android foreground service types require specific manifest permissions and adhere to distinct lifecycle constraints. While dataSync handles bounded network operations with system execution budgets, location and mediaPlayback maintain active hardware access, and shortService enforces an unextendable three-minute execution ceiling before triggering mandatory termination to preserve battery health.

To navigate the compliance landscape across API level 34 and API level 35, mobile engineering teams must audit each service against Android's strict enforcement matrix:

Foreground Service Type Required Manifest Permission Enforced API Level Execution Timeout Limit Allowed Background Invocations Common Failure Mode
dataSync FOREGROUND_SERVICE_DATA_SYNC Android 14+ (API 34) System execution budget (~6 hours in 24h) Restricted; requires active UI or high-priority FCM ForegroundServiceStartNotAllowedException on sync retry
location FOREGROUND_SERVICE_LOCATION + ACCESS_FINE_LOCATION Android 14+ (API 34) None (Active until user stops) Forbidden unless location permission is granted ALWAYS SecurityException: Starting FGS with type location
mediaPlayback FOREGROUND_SERVICE_MEDIA_PLAYBACK Android 14+ (API 34) None (Active during active playback) Allowed only if media session is active Notification dismissal crashes audio pipeline
shortService FOREGROUND_SERVICE_SHORT_SERVICE Android 14+ (API 34) Strict 3 minutes (180 seconds) Allowed from any valid foreground or immediate context ForegroundServiceTimeoutException / ANR process kill
mediaProjection FOREGROUND_SERVICE_MEDIA_PROJECTION Android 14+ (API 34) None (Requires active user projection prompt) Forbidden; requires interactive system dialog approval Missing MediaProjection token causes crash
camera FOREGROUND_SERVICE_CAMERA + CAMERA Android 14+ (API 34) None (Terminates when camera release occurs) Forbidden; must be initiated while UI activity is visible Privacy indicator violation / System kill
microphone FOREGROUND_SERVICE_MICROPHONE + RECORD_AUDIO Android 14+ (API 34) None (Terminates when audio record stops) Forbidden; requires foreground activity initiation Mic access revoked silently in background
specialUse FOREGROUND_SERVICE_SPECIAL_USE Android 14+ (API 34) Subject to Google Play policy review Highly restricted; requires custom developer rationale Rejection during Google Play developer console review

The ScreenSync MCP Testing Architecture: Bridging Claude Code to Real Android Runtimes

ScreenSync MCP bridges Claude Code to physical Android devices by decoupling zero-root MediaProjection screen capture from ADB-driven command execution. Running as a local stdio Model Context Protocol server on Node.js 18+, the desktop hub delivers sub-100 millisecond visual frames alongside real-time logcat inspection and native UI hierarchy parsing directly to AI coding agents.

The core difficulty in automating mobile agentic testing has always been latency and fidelity. Emulators frequently fail to reproduce OEM background task termination algorithms, while standard mobile automation frameworks require complex WebDriver setups, brittle element locators, and intrusive test instrumentation.

ScreenSync MCP solves this challenge through a clean dual-channel architecture created by EpsolDev:

+-----------------------------------------------------------------------------------+
|                              CLAUDE CODE CLI / IDE                                |
|         (Agentic reasoning, automated test orchestration, logcat analysis)        |
+-----------------------------------------+-----------------------------------------+
                                          |
                                          | stdio (JSON-RPC)
                                          v
+-----------------------------------------------------------------------------------+
|                        SCREENSYNC MCP DESKTOP HUB                                 |
|               (Node.js 18+, localhost:3000, 226 MCP Tools Total)                  |
|    - 29 Phone & Desktop Tools (12 control_*, 3 os_*, 14 capture/telemetry)        |
|    - 197 Web Browser Tools (Chrome/Edge Extension MV3 integration)                |
+--------------------+---------------------------------------------+----------------+
                     |                                             |
     LAN WiFi / mDNS | (~94 ms median mirror)       USB / Wireless | ADB Daemon
                     v                                             v
+-----------------------------------------+   +-------------------------------------+
|        ANDROID HARDWARE DEVICE          |   |          ADB COMMAND BRIDGE         |
|  - Free app on Google Play              |   |  - Touch input & text injection     |
|    (com.screensync.mcp, Android 7.0+)   |   |  - Package manager & intent launch  |
|  - Native MediaProjection API capture   |   |  - dumpsys activity services        |
|  - No Root, No ADB required for video   |   |  - Real-time logcat error filtering |
+-----------------------------------------+   +-------------------------------------+
  1. Zero-Root Visual Capture Channel: The ScreenSync MCP Android mobile application runs on Android 7.0 or later and is available for free on Google Play (com.screensync.mcp). When launched, it utilizes Android's native MediaProjection API to capture the real device display. This video streaming requires no device rooting and no ADB connection, streaming frames across the local area network to the desktop hub with approximately 94 ms median latency in real-world benchmarks.
  2. Deterministic Control & Telemetry Channel: For active test manipulation—such as tapping buttons, typing input, launching activities, and inspecting system states—the desktop hub communicates with the device via ADB over USB or wireless debugging. ScreenSync MCP exposes 29 dedicated phone and desktop tools (including 12 control_* tools, 3 os_* tools, and 14 capture and telemetry tools like get_latest_screenshot, get_ui_hierarchy, and get_logcat), alongside 197 browser automation tools.
  3. Local Privacy by Design: Video frames and telemetry data travel directly between the mobile phone and the developer's workstation hub over the local network. Frames never route through EpsolDev cloud servers. When Claude Code requests a screenshot or UI hierarchy snapshot to verify a test assertion, only the specific requested snapshot is transferred to Anthropic's model context.

Comparative Matrix: ScreenSync MCP Real-Device Telemetry vs Emulators and Synthetic Harnesses

Real-device MCP telemetry surpasses headless emulators and synthetic test harnesses by executing foreground services against authentic OEM battery management, realistic notification shading, and genuine hardware sensor subsystems. ScreenSync MCP pairs low-latency visual verification with deep ADB logcat analysis, exposing background execution exceptions that simulated environments routinely miss.

Evaluating foreground services requires balancing execution fidelity, configuration complexity, and agentic interoperability:

Evaluation Criteria ScreenSync MCP on Real Android Hardware Cloud Device Testing Farms Headless Emulator / Robolectric Legacy UiAutomator2 / Appium Scripts
Real OEM OS Killers Authentic: Evaluates real battery optimizations on Samsung, Pixel, and Xiaomi devices Partial: Devices are frequently reset or kept awake via charging flags None: Synthetic environment lacks OEM background task termination Authentic: Runs on hardware, but scripts cannot adapt dynamically to unexpected crashes
AI Agent Integration Native MCP stdio: Single CLI command binds directly to Claude Code and Cursor Proprietary REST APIs: Heavy latency and non-standard JSON schemas Complex: Requires mock stubs for system daemons and notification managers Brittle: Requires rigid selector maps and external server processes
Visual Frame Latency Sub-100 ms (~94 ms median): High-speed local LAN mirror 1500 ms – 3000 ms: Network hops to remote cloud data centers Instantaneous: Synthetic canvas without authentic compositor rendering 1000 ms – 2500 ms: Polling-based screenshot grabs via ADB exec-out
Notification Verification True Visual & Semantic Inspection: Reads notification shade via get_ui_hierarchy and screenshot Partial: Requires parsing raw accessibility nodes without visual confirmation Simulated: NotificationManager shadow objects do not reflect system shade UI Manual: XPath locators break when OEM layouts alter system shade structures
Root & ADB Requirements Capture: Zero Root & Zero ADB; Control uses standard ADB Mandatory: Managed device profiles require vendor ADB tunnels N/A: Runs as local JVM process Mandatory ADB: Requires persistent USB debugging and Appium server daemon
Tool Ecosystem 226 Tools: 29 mobile/desktop tools + 197 web tools Narrow mobile test runner APIs Local assertion libraries only Element-interaction automation commands only

Step-by-Step Implementation: Building a Resilient Android 15 Foreground Service

Implementing a resilient Android 15 foreground service requires declaring explicit foreground service types in the manifest, establishing a high-priority notification channel, requesting POST_NOTIFICATIONS at runtime, and invoking startForeground with appropriate type bitmasks immediately within the service lifecycle to prevent immediate system-level process death.

To examine how Claude Code verifies foreground services, consider a robust, production-grade Kotlin implementation of a data synchronization service designed for Android 14 and 15 compliance.

1. Declaring the Service in AndroidManifest.xml

Every foreground service must declare its permissions and foreground service type explicitly in the application manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Baseline foreground service permission required since Android 9 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <!-- Specific type permission required for Android 14+ data sync -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

    <!-- Runtime notification permission required since Android 13 -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <!-- Network access to perform the synchronization -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.ForegroundSyncDemo">

        <service
            android:name=".sync.DataSyncForegroundService"
            android:enabled="true"
            android:exported="false"
            android:foregroundServiceType="dataSync" />

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

2. The Kotlin Service Implementation

The service creates a dedicated notification channel, triggers startForeground() within milliseconds of creation, and provides clean cancellation handling:

package com.example.foregroundsyncdemo.sync

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import com.example.foregroundsyncdemo.MainActivity
import com.example.foregroundsyncdemo.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class DataSyncForegroundService : Service() {

    private val serviceScope = CoroutineScope(Dispatchers.IO + Job())
    private var syncJob: Job? = null

    companion object {
        private const val TAG = "DataSyncService"
        private const val CHANNEL_ID = "sync_channel_high_priority"
        private const val NOTIFICATION_ID = 1001

        const val ACTION_START_SYNC = "com.example.ACTION_START_SYNC"
        const val ACTION_STOP_SYNC = "com.example.ACTION_STOP_SYNC"

        fun startService(context: Context) {
            val intent = Intent(context, DataSyncForegroundService::class.java).apply {
                action = ACTION_START_SYNC
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                context.startForegroundService(intent)
            } else {
                context.startService(intent)
            }
        }

        fun stopService(context: Context) {
            val intent = Intent(context, DataSyncForegroundService::class.java).apply {
                action = ACTION_STOP_SYNC
            }
            context.startService(intent)
        }
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "DataSyncForegroundService onCreate() executed")
        createNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_STOP_SYNC -> {
                Log.i(TAG, "Received ACTION_STOP_SYNC. Terminating service.")
                stopSync()
                stopForeground(STOP_FOREGROUND_REMOVE)
                stopSelf()
                return START_NOT_STICKY
            }
            ACTION_START_SYNC -> {
                Log.i(TAG, "Starting sync execution in foreground.")
                promoteToForeground()
                executeDataSync()
                return START_REDELIVER_INTENT
            }
            else -> {
                Log.w(TAG, "Unknown action received: ${intent?.action}")
                stopSelf()
                return START_NOT_STICKY
            }
        }
    }

    private fun promoteToForeground() {
        val notification = buildSyncNotification("Preparing local records for server sync...", 0)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // Android 14+ requires explicit service type flags in startForeground
            startForeground(
                NOTIFICATION_ID,
                notification,
                ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
            )
        } else {
            startForeground(NOTIFICATION_ID, notification)
        }
    }

    private fun executeDataSync() {
        syncJob?.cancel()
        syncJob = serviceScope.launch {
            try {
                for (progress in 1..100 step 10) {
                    delay(1500) // Simulate chunked network transaction
                    Log.d(TAG, "Syncing batch chunk: $progress% complete")
                    updateNotificationProgress(progress)
                }
                Log.i(TAG, "Data synchronization finished successfully.")
                delay(500)
                stopForeground(STOP_FOREGROUND_REMOVE)
                stopSelf()
            } catch (e: Exception) {
                Log.e(TAG, "Synchronization encountered unexpected failure: ${e.message}", e)
                stopForeground(STOP_FOREGROUND_REMOVE)
                stopSelf()
            }
        }
    }

    private fun updateNotificationProgress(progress: Int) {
        val notification = buildSyncNotification("Syncing database chunks ($progress%)...", progress)
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.notify(NOTIFICATION_ID, notification)
    }

    private fun buildSyncNotification(contentText: String, progress: Int): Notification {
        val launchIntent = Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            this,
            0,
            launchIntent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Cloud Data Synchronization")
            .setContentText(contentText)
            .setSmallIcon(android.R.drawable.stat_notify_sync)
            .setOngoing(true)
            .setOnlyAlertOnce(true)
            .setProgress(100, progress, progress == 0)
            .setContentIntent(pendingIntent)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setCategory(NotificationCompat.CATEGORY_SERVICE)
            .build()
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                "Background Data Synchronization",
                NotificationManager.IMPORTANCE_LOW
            ).apply {
                description = "Displays ongoing synchronization progress for offline records"
                setShowBadge(false)
            }
            val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            manager.createNotificationChannel(channel)
        }
    }

    private fun stopSync() {
        syncJob?.cancel()
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "DataSyncForegroundService onDestroy() invoked")
        serviceScope.cancel()
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

Configuring Claude Code with ScreenSync MCP for Autonomous Service Auditing

Developers configure Claude Code for autonomous Android service auditing by adding the ScreenSync desktop hub as a stdio Model Context Protocol server. Using a single terminal command, Claude Code initializes device communication, gains access to twenty-nine mobile tools, and coordinates multi-step test workflows combining UI clicks, logcat evaluation, and OS telemetry.

Connecting Claude Code to the physical test device requires completing three straightforward steps using the official ScreenSync MCP setup guide:

1. Launching the ScreenSync MCP Desktop Hub

The desktop hub runs on Node.js 18+ across Windows, macOS, and Linux. Download the hub release archive, extract it to a directory on your development machine, and start the local service:

# On Windows
start-hub.bat

# On macOS / Linux
chmod +x start-hub.sh
./start-hub.sh

The hub initializes a local HTTP server on port 3000 to manage WebSocket streaming and provides an MCP server interface over standard input/output (stdio).

2. Registering ScreenSync MCP in Claude Code

In your terminal, register ScreenSync MCP as an active server inside Claude Code:

claude mcp add screensync node "/path/to/screensync-hub/screensync-hub.js"

If you are using Claude Desktop instead, register the server inside your claude_desktop_config.json configuration file (accessible via Settings > Developer):

{
  "mcpServers": {
    "screensync": {
      "command": "node",
      "args": ["C:/Tools/screensync-hub/screensync-hub.js"],
      "env": {
        "SCREEN_SYNC_TOKEN": "your_secure_bearer_token_here"
      }
    }
  }
}

3. Pairing the Android Hardware Device

  1. Install the free ScreenSync MCP client from Google Play (com.screensync.mcp) on an Android 7.0+ device.
  2. Ensure your workstation and phone are connected to the same local area network.
  3. Open the ScreenSync MCP app and scan the QR code displayed in the hub's local dashboard, or let the app locate the hub automatically via local mDNS discovery.
  4. Tap "Start Screen Sync" and grant the one-time MediaProjection capture consent.
  5. Enable USB debugging or Wireless Debugging in Android Developer Options so the hub can dispatch ADB control and telemetry commands.

Autonomous Test Recipes: Orchestrating Foreground Service Audits with Claude Code

Autonomous test recipes orchestrate foreground service audits by prompting Claude Code to simulate real-world user workflows on physical devices. Claude Code triggers user interactions via control_tap, verifies notification cards through get_ui_hierarchy, inspects system states with dumpsys commands, and monitors logcat to confirm zero background execution crashes.

Once paired, Claude Code has full visibility into your Android hardware runtime. Here are four essential test recipes for auditing foreground services:

Recipe 1: Verifying User-Initiated Launch and Notification Persistence

To verify that the service starts smoothly, generates the proper notification, and updates its progress without UI flicker, issue the following instruction to Claude Code:

Claude Code Prompt:
"Audit the data synchronization flow in our app com.example.foregroundsyncdemo. Using control_tap, click the 'Start Sync' button on the main activity. Then call get_ui_hierarchy to ensure the progress indicator appears. Swipe down the notification shade using control_swipe, capture a screenshot via get_latest_screenshot to confirm that the persistent notification card displays 'Cloud Data Synchronization', and call get_logcat to confirm that DataSyncForegroundService transitioned to the foreground without any warning."

During execution, Claude Code executes this multi-tool workflow autonomously:

  1. Calls control_tap with coordinates targeted at the sync button.
  2. Calls get_ui_hierarchy to confirm that the UI state updated to "Sync in progress".
  3. Dispatches control_swipe starting from (540, 10) down to (540, 1200) to expand the notification shade.
  4. Calls get_latest_screenshot to verify that the ongoing notification card is visible and styled properly.
  5. Calls get_logcat with filter tag:DataSyncService to verify that startForeground(..., FOREGROUND_SERVICE_TYPE_DATA_SYNC) succeeded.

Recipe 2: Testing Android 14/15 Background Start Restrictions & Exception Traps

To test how your app behaves when a service start is triggered while the application is in the background, prompt Claude Code to simulate external triggers:

Claude Code Prompt:
"Test background launch restrictions on com.example.foregroundsyncdemo. Launch the app, then press the device home button via ADB to move the app into the background. Wait 10 seconds. Trigger a background sync intent via adb shell am start-foreground-service -a com.example.ACTION_START_SYNC com.example.foregroundsyncdemo/.sync.DataSyncForegroundService. Read get_logcat immediately to verify whether ForegroundServiceStartNotAllowedException was thrown, and check if our app caught the exception or crashed."

Claude Code executes the command, reads logcat, and provides a root-cause breakdown:

[Claude Code Telemetry Analysis]
Exception Trapped: android.app.ForegroundServiceStartNotAllowedException
Process: com.example.foregroundsyncdemo, PID: 18420
Reason: Service.startForeground() was invoked from process state 'TOP_SLEEPING / BACKGROUND'
Compliance Status: FAILED - Application attempted to invoke startForegroundService without a valid foreground activity token or foreground execution exemption.
Recommendation: Migrate deferred synchronization triggers to androidx.work.WorkManager.

Recipe 3: Auditing shortService 3-Minute Timeout Enforcement

If you are using shortService, Claude Code can run a continuous timer audit to ensure your service halts before the Android system executes an ANR process kill:

Claude Code Prompt:
"Audit our short service worker com.example.app/.sync.QuickUploadService. Trigger the upload flow, then poll adb shell dumpsys activity services com.example.app every 30 seconds for 3.5 minutes. Verify that stopSelf() is called before the 180-second boundary. Report if Service.onTimeout() is triggered in get_logcat."

Claude tracks the elapsed time against the Android system dumpsys service record. If the service exceeds 180 seconds, Claude immediately flags the ForegroundServiceTimeoutException and pinpoints the blocking network call.

Recipe 4: Evaluating Foreground Services Under Doze Mode and App Standby

A common source of production regressions is service degradation during deep battery saving states. Test how your service holds up when Android enters Doze mode:

# Force the physical device into deep Doze mode
adb shell dumpsys deviceidle force-idle

# Check active wake locks and socket states
adb shell dumpsys power | grep -A 5 "Wake Locks"

Prompt Claude Code to observe whether network sockets remain active or whether your service gracefully handles packet dropouts without crashing the foreground thread. For deeper insights into battery optimizations, see our comprehensive guide on testing Android battery drain and Doze mode.


Production Triage: Diagnosing Service Crashes Using ADB Dumpsys and ScreenSync MCP

Diagnosing production foreground service crashes requires inspecting OS-level service registries using ADB dumpsys activity services. Claude Code parses active service records, verifies foreground status flags, inspects notification associations, and cross-references process priority states with logcat timestamps to pinpoint manifest permission mismatches and illegal background transitions.

When a foreground service fails silently or experiences unexpected kills, relying exclusively on high-level UI inspection is insufficient. Developers must interrogate the Android ActivityManager directly.

Through ScreenSync MCP, Claude Code can inspect low-level service records by executing dumpsys activity services <package-name>:

$ adb shell dumpsys activity services com.example.foregroundsyncdemo

ACTIVITY MANAGER SERVICES (dumpsys activity services)
  User 0 active services:
  * ServiceRecord{4b9a12e u0 com.example.foregroundsyncdemo/.sync.DataSyncForegroundService}
    intent={act=com.example.ACTION_START_SYNC cmp=com.example.foregroundsyncdemo/.sync.DataSyncForegroundService}
    packageName=com.example.foregroundsyncdemo
    processName=com.example.foregroundsyncdemo
    baseDir=/data/app/~~u9W3b==/com.example.foregroundsyncdemo-1/base.apk
    dataDir=/data/user/0/com.example.foregroundsyncdemo
    app=ProcessRecord{83b271f 18420:com.example.foregroundsyncdemo/u0a241}
    isForeground=true foregroundId=1001 foregroundNoti=Notification(channel=sync_channel_high_priority pri=-1)
    foregroundServiceType=0x00000001 [dataSync]
    createTime=-42s118ms lastActivity=-12s402ms
    startRequested=true delayedStop=false stopIfKilled=false callStart=true

Key Dumpsys Telemetry Fields to Verify:

  • isForeground=true: Confirms that the operating system has officially elevated the service process out of background scheduling limits.
  • foregroundId=1001 and foregroundNoti: Verifies that a valid notification is actively attached. If this shows null while isForeground=true, Android 14 will terminate the host process within seconds.
  • foregroundServiceType=0x00000001 [dataSync]: Verifies that the runtime bitmask matches the declared manifest type. If this displays none or an unexpected type hex code, runtime permissions must be reviewed.
  • app=ProcessRecord{...}: Reveals the Out-Of-Memory (OOM) score adjustment. A compliant foreground service typically holds an adj score of 200 (FOREGROUND_SERVICE_ADJ), shielding it from being killed when the user switches to other applications.

If your service requires handling user approvals for sensitive capabilities, combine this inspection workflow with our tutorial on testing Android runtime permissions with Claude Code. For tasks that can be deferred rather than executed immediately in the foreground, review our deep dive into testing Android WorkManager with Claude Code and MCP.


Enterprise Best Practices for Mobile CI/CD and Autonomous Regression Suites

Enterprise mobile testing teams integrate ScreenSync MCP and Claude Code into continuous integration pipelines to catch foreground service regressions before production releases. Automated suites execute daily regression runs across physical test benches, verifying that updated target SDK migrations maintain compliance with emerging Android platform restrictions.

To maintain robust quality across complex mobile releases, enterprise engineering teams adhere to four architectural standards:

  1. Physical Device Racks over Headless Emulators: While emulators are fast, they fail to replicate proprietary OEM power management rules (such as Samsung's Device Care or Xiaomi's Battery Saver) that terminate foreground services unpredictably. Maintain physical test benches running official OEM firmware.
  2. Automated Manifest & Target SDK Audits: Whenever upgrading targetSdkVersion to 34 or 35, execute an automated Claude Code audit prompt that checks every <service> tag in AndroidManifest.xml against the required foreground service types and corresponding runtime permissions.
  3. Graceful WorkManager Fallbacks: Architecture should follow Google’s modern guidelines: use foreground services exclusively for user-perceptible, immediate tasks (such as turn-by-turn navigation, active media recording, or user-initiated file transfers). Defer all opportunistic or retry-heavy tasks to WorkManager. Claude Code can audit your codebase to ensure fallback paths trigger seamlessly when a foreground start is rejected.
  4. Autonomous Notification Shade Auditing: Make visual verification part of your CI pipeline. Using ScreenSync MCP’s get_latest_screenshot and get_ui_hierarchy, automated suites confirm that ongoing notifications display up-to-date progress strings, accurate action buttons (e.g., "Pause", "Cancel"), and compliant category metadata.

By bridging Claude Code's agentic reasoning with ScreenSync MCP's high-speed real-device control, mobile development teams turn the most fragile corner of Android OS compliance into a deterministic, fully automated engineering pipeline.

FAQ

Questions, answered

Why does calling startForegroundService from the background crash on Android 14 and 15?

Android strictly forbids background foreground service starts to prevent battery abuse. If an app calls startForegroundService while in the background without an active UI window or valid system exemption, the operating system immediately throws ForegroundServiceStartNotAllowedException to terminate the unauthorized background process.

What is the maximum execution time allowed for shortService in Android 14+?

The shortService foreground service type enforces an unextendable system timeout of three minutes (180 seconds). If an application fails to invoke stopSelf before this window elapses, Android triggers Service.onTimeout and forcefully terminates the host process with an Application Not Responding (ANR) error.

Do I need root access or ADB to capture Android screens with ScreenSync MCP?

No root access and no ADB connection are required for screen capture. ScreenSync MCP utilizes Android's native MediaProjection API with one-time user consent to stream low-latency display frames over the local network. ADB is used strictly for input control and system telemetry commands.

How does ScreenSync MCP communicate with Claude Code?

ScreenSync MCP operates a desktop hub built on Node.js 18+ that exposes Model Context Protocol tools over standard input/output (stdio). Claude Code registers the hub via a simple CLI command, allowing the AI agent to invoke 29 mobile tools and 197 browser tools.

What happens if an app starts a foreground service without declaring foregroundServiceType?

On apps targeting Android 14 (API level 34) or higher, starting a foreground service without declaring a specific foregroundServiceType in AndroidManifest.xml and in startForeground results in an immediate IllegalArgumentException or SecurityException crash triggered by the Android runtime.

How can Claude Code verify that a foreground service notification actually appeared on screen?

Claude Code dispatches a swipe gesture to expand the notification shade, calls get_ui_hierarchy to inspect the notification view tree semantically, and captures a frame using get_latest_screenshot to visually confirm that the notification channel title, icon, and progress bar are rendered properly.

Can Claude Code test foreground services during device sleep and Doze mode?

Yes. Claude Code uses ADB shell commands through ScreenSync MCP to force the connected device into deep Doze mode via dumpsys deviceidle force-idle, subsequently monitoring logcat and dumpsys power to verify whether the foreground service maintains necessary wake locks and socket connections.

Is iOS supported by ScreenSync MCP for background service testing?

No. ScreenSync MCP is purpose-built exclusively for Android devices (running Android 7.0 or higher), alongside its desktop hub and Chrome/Edge browser extension. It does not provide iOS device support, as iOS architecture utilizes distinct background execution daemons and sandbox policies.

Written by

ScreenSync MCP team

The ScreenSync MCP team at EpsolDev builds the Android app, the desktop MCP hub and the browser extension that let AI agents see and control a real phone. These guides come out of that work.