How to Test Video Calls on Android (Complete Guide)

Video calling is no longer a niche feature; it is a core interaction for telehealth, remote work, education, and social apps. On Android the complexity multiplies because the platform exposes a hetero

January 19, 2026 · 19 min read · How-To Guides

Why Video Call Testing Demands Special Attention on Android

Video calling is no longer a niche feature; it is a core interaction for telehealth, remote work, education, and social apps. On Android the complexity multiplies because the platform exposes a heterogeneous set of hardware capabilities, permission models, and background‑process behaviors. A defect that survives unit tests can surface as a dropped call, garbled audio, or a privacy leak when a real user experiences fluctuating network conditions, receives an incoming SMS, or switches to battery‑saver mode.

Testing video calls therefore requires a blend of functional verification, media‑path validation, and system‑level stress testing. The following guide walks through a complete methodology: from a concrete test matrix to manual steps, automated scripts, and finally how autonomous, persona‑driven exploration can surface issues that scripted tests never consider.

---

Common Failure Modes Seen in Production

Understanding what typically goes wrong helps prioritize test effort. The table below aggregates failure categories observed across multiple Android video‑call apps in the wild.

CategoryTypical SymptomRoot Cause (Android‑specific)Detection Technique
Permission handlingCall fails to start, black screenMissing CAMERA, RECORD_AUDIO, or READ_PHONE_STATE at runtime; permission denied after a system updateRuntime permission dialog checks, adb shell pm grant
Audio‑video sync driftLip‑sync error > 150 msImproper use of AudioTrack/AudioRecord timestamps, or reliance on MediaCodec without configuring KEY_OUTPUT_DELAYFrame‑timestamp comparison, MediaRecorder API probes
Network volatilityFreeze, pixelation, call dropWi‑Fi to cellular handoff, captive portal, VPN, or aggressive Doze mode throttlingNetwork simulation (tc, netem), ConnectivityManager callbacks
Background interferenceAudio muted, video paused when another app grabs mic/cameraAnother foreground service holds audio focus (AudioManager.AUDIOFOCUS_GAIN_TRANSIENT) or camera is opened by a different processAudioManager focus listeners, camera state polling
UI glitches under rotation/multi‑windowControls disappear, layout overlapsFailure to handle Configuration changes, or using fixed‑dimension SurfaceView without adapting to new window sizeonConfigurationChanged verification, UI Automator screenshots
Accessibility barriersTalkBack cannot announce call state, missing labelsCustom views without contentDescription, or reliance on touch‑only gesturesAccessibility scanner, TalkBack navigation
Security/privacy leaksCall metadata exposed in logs, screen capture allowedOver‑verbose Log.d, missing FLAG_SECURE on window, or insufficient encryption of signalingLogcat inspection, adb shell dumpsys window windows flag check
Resource exhaustionANR, OOM kill during long callHeavy bitmap processing, failure to release MediaCodec buffers, or unbounded growth of signaling queuesadb shell dumpsys meminfo, Systrace for main‑thread stalls

Each of these rows informs a specific test case; the next section consolidates them into a usable matrix.

---

Comprehensive Test Matrix

The matrix below organizes test scenarios by dimension (functional, error, edge, accessibility, security) and indicates the recommended verification method. Use it as a checklist when building test suites.

DimensionTest IDScenarioPreconditionsStepsExpected OutcomeAutomation Hint
Happy PathHP‑1Two‑party call initiation and teardownApp installed, granted CAMERA & AUDIO, device on stable Wi‑Fi1. User A taps “Start Call” → 2. User B accepts → 3. Verify video/audio streams → 4. Either party ends callBoth ends see each other's video, hear audio, call ends cleanly, resources releasedEspresso + IdlingResource for signaling; MediaProjection to capture frames
Happy PathHP‑2Call with screen share enabledSame as HP‑1, plus screen‑share permission granted1. Start call → 2. Activate screen share → 3. Share a scrolling list → 4. Stop shareRemote participant sees exact screen content, no lag > 200 ms, local UI unchangedUse VirtualDisplay via MediaProjection; compare bitmap hash
Error PathEP‑1Missing camera permission at runtimePermission denied via adb shell pm revoke1. Launch call flow → 2. Observe permission rationaleApp shows rationale dialog, does not crash, call button disabledUI Automator to verify dialog text, Espresso to assert button state
Error PathEP‑2Audio focus loss due to music appBackground music app playing, requests AUDIOFOCUS_GAIN1. Start call → 2. Launch music app → 3. Observe audio behaviorCall audio lowers (ducking) or pauses per app policy, recovers after focus returnsAudioManager focus listener; verify volume via MediaRecorder
Error PathEP‑3Signaling server returns 500Mock server configured to error on /join1. Attempt call → 2. Handle errorApp shows retry toast, does not leak socket, cleans up MediaCodecMockWebServer + Espresso idling for network idle
Edge CaseEC‑1Network latency spike (300 ms) + jitterUse tc qdisc add dev wlan0 root netem delay 300ms 100ms distribution normal1. Start call → 2. Monitor for freeze → 3. Verify recovery after latency returns to normalVideo may freeze briefly (< 2 s), audio may buffer, call recovers without crashadb shell dumpsys media.audio_flinger for buffer stats
Edge CaseEC‑2Incoming voice call interrupts video callDevice has carrier SIM, enable call waiting1. Start video call → 2. Receive incoming voice call → 3. Accept/reject → 4. Return to video callVideo call pauses/resumes correctly, mic/camera released/grabbed appropriately, no deadlockTelephonyManager state broadcast receiver test
Edge CaseEC‑3Battery saver / extreme low powerEnable Battery Saver, set battery level to 5 % via adb shell dumpsys battery set level 51. Start call → 2. Observe for throttlingFrame rate may drop, but call remains stable; no ANRSystrace to capture CPU freq scaling
Edge CaseEC‑4Picture‑in‑picture (PiP) mode activatedApp supports PiP, user presses home during call1. Start call → 2. Swipe up to home → 3. Verify PiP windowVideo continues in small window, controls accessible, audio unchangedUI Automator to assert PiP window presence, MediaProjection to capture
AccessibilityAC‑1TalkBack navigation of call controlsTalkBack enabled, focus starts on call screen1. Swipe left/right → 2. Hear description of each buttonEvery interactive element has a meaningful contentDescription, state changes announcedAccessibility Test Framework (ATF) or androidx.test.espresso.accessibility
AccessibilityAC‑2High contrast / font scalingSet Settings → Accessibility → Font size → Largest, enable high contrast1. Verify layout does not clip text, buttons remain tappableAll text scales, touch targets ≥ 48 dpUI Automator screenshot comparison
Security/PrivacySE‑1Screen capture preventionCall screen marked with FLAG_SECURE1. Attempt screenshot via power+vol down → 2. Check resultScreenshot is black, no video frame leakedadb shell screencap validation
Security/PrivacySE‑2Signaling encryption verificationEnable network capture (Wireshark) on device1. Place call → 2. Inspect packetsSignaling payload (SDP, ICE) is TLS‑encrypted, no plaintext credentialsadb shell tcpdump or HttpLoggingInterceptor level BODY
Security/PrivacySE‑3Permission re‑grant after device rebootReboot device, check persisted permissions1. Reboot → 2. Launch app → 3. Attempt callPermissions remain granted, no re‑prompt unless explicitly revokedadb shell pm list permissions -g -d

*Tip:* When constructing automated suites, group tests by dimension and run them in parallel on a device farm to keep total execution time manageable.

---

Manual Testing – Step‑by‑Step Walkthrough

Even with automation, a disciplined manual pass catches nuances that scripts gloss over. Follow this procedure on a physical device (or a well‑configured emulator) before committing to CI.

  1. Device Preparation
  1. Baseline Happy Path
  1. Permission Flow
  1. Audio Focus Ducking Test
  1. Network Stress
  1. Interrupt Handling
  1. Orientation & Multi‑Window
  1. Picture‑in‑Picture
  1. Accessibility Check
  1. Security & Privacy Spot‑Check

If any step fails, log the exact device model, Android version, and the precise state (e.g., network condition, battery level). This information is invaluable for reproducing the issue later in an automated test or bug report.

---

Automated Approaches – Tooling and Sample Code

1. UI‑Level Testing with Espresso + Idling Resources

Espresso excels at verifying UI state and synchronizing with asynchronous operations (signaling, media preparation). The following snippet demonstrates a happy‑path test that waits for the remote video surface to appear.


@RunWith(AndroidJUnit4::class)
class VideoCallTest {

    private lateinit var activityScenario: ActivityScenario<MainActivity>

    @Before
    fun setUp() {
        activityScenario = ActivityScenario.launch(MainActivity::class.java)
        // Grant permissions programmatically for the test
        ShellUtils.grantPermission(
            InstrumentationRegistry.getInstrumentation().targetContext,
            Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO
        )
    }

    @Test
    fun happyPathCallEstablishesVideo() {
        // Tap start call button
        onView(withId(R.id.btn_start_call)).perform(click())

        // Idling resource that waits until signaling reports CALL_CONNECTED
        val callConnectedIdling = object : IdlingResource {
            override fun getName() = "CallConnectedIdling"
            private var callback: IdlingResource.ResourceCallback? = null
            override fun isIdleNow(): Boolean {
                val isConnected = CallManager.getInstance().isConnected()
                if (isConnected) callback?.onTransitionToIdle()
                return isConnected
            }
            override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
                this.callback = callback
            }
        }
        IdlingRegistry.getInstance().register(callConnectedIdling)

        // Wait for remote SurfaceView to appear (indicating video decode)
        onView(withId(R.id.remote_surface))
            .check(matches(isDisplayed()))
            .check(matches(withEffectiveVisibility(View.VISIBLE)))

        // End call and verify cleanup
        onView(withId(R.id.btn_end_call)).perform(click())
        onView(withId(R.id.btn_start_call)).check(matches(isDisplayed()))
        IdlingRegistry.getInstance().unregister(callConnectedIdling)
    }
}

Key points

2. Media Path Validation with Camera2 Test Source

To confirm that the app actually sends frames from the camera (and not a static image), replace the physical camera with a test source that generates a moving pattern. The Android CTS provides android.hardware.camera2.test which can be used via adb shell am instrument.


# Install the camera test source APK (part of platform-tools)
adb install -r /path/to/CameraTestSource.apk

# Grant the test app permission to use the test source
adb shell appops set com.example.videocall CAMERA ignore

# Launch the test source service (it creates a virtual camera)
adb shell am startservice -n com.android.camera2.testsource/.CameraTestSourceService

# Run your Espresso test; the virtual camera will feed a rotating color bar pattern

In your test, after confirming the remote surface is visible, grab a frame using ImageReader attached to the local preview Surface. Compute the average hue; if it matches the expected pattern (e.g., rotating through red‑green‑blue), you know the camera pipeline is active.

3. Audio Path Verification with AudioRecord and Playback

A simple way to ensure audio is being captured and rendered is to record a short snippet from the microphone, play it back through the speaker, and compare the waveform to a known test tone.


private fun captureAndValidateAudio(durationMs: Long = 2000): Boolean {
    val sampleRate = 44100
    val bufferSize = AudioRecord.getMinBufferSize(sampleRate,
        AudioFormat.CHANNEL_IN_MONO,
        AudioFormat.ENCODING_PCM_16BIT)
    val recorder = AudioRecord(
        MediaRecorder.AudioSource.MIC,
        sampleRate,
        AudioFormat.CHANNEL_IN_MONO,
        AudioFormat.ENCODING_PCM_16BIT,
        bufferSize
    )
    recorder.startRecording()
    val samples = ShortArray(bufferSize / 2)
    val start = SystemClock.uptimeMillis()
    val collected = mutableListOf<Short>()
    while (SystemClock.uptimeMillis() - start < durationMs) {
        val read = recorder.read(samples, 0, samples.size)
        if (read > 0) collected.addAll(samples.copyOfRange(0, read))
    }
    recorder.stop()
    recorder.release()

    // Simple energy test: ensure RMS > threshold (indicates non‑silence)
    val sum = collected.map { it.toDouble() * it }.sum()
    val rms = Math.sqrt(sum / collected.size)
    return rms > 100.0   // empirical threshold; adjust per device
}

Call this function before and after the call starts; a significant increase in RMS confirms the mic is live. For the speaker path, you can use AudioTrack to play a known tone and verify with the same recorder on a second device or using a loopback cable.

4. Network Condition Simulation

While tc works on rooted devices or emulators with elevated privileges, you can also use the Android NetworkCapabilities API to inject latency and loss via TrafficShaper (API 29+). Example:


val trafficShader = ConnectivityManager.getTrafficShaper()
val config = TrafficShaper.Config.Builder()
    .setDelayMs(300)
    .setLossPercentage(2)
    .build()
trafficShader.apply(config)

Wrap this in a @Before/@After JUnit rule to apply the condition only for the duration of a test.

5. Cross‑Device Synchronization with UiAutomator

For true two‑party validation, launch the same test on a second device and coordinate via a socket or ADB forwarding. A minimal example:

Device A (caller)


adb -A forward tcp:5555 tcp:5555
adb -A shell am instrument -w -e action start_call -e peer_port 5555 com.example.test/androidx.test.runner.AndroidJUnitRunner

Device B (callee)


adb -B forward tcp:5555 tcp:5555
adb -B shell am instrument -w -e action accept_call -e peer_port 5555 com.example.test/androidx.test.runner.AndroidJUnitRunner

Each test side opens a socket on the forwarded port, exchanges a simple JSON signal ({type: "offer"} or {type: "answer"}), and then proceeds with the UI steps. This approach eliminates reliance on a real signaling server for UI verification while still exercising the full client‑side call flow.

6. Automated Accessibility Scanning

Integrate the androidx.test.espresso.accessibility library:


@Test
fun accessibilityCheck() {
    AccessibilityChecks.enable()
    onView(withId(R.id.call_screen)).check(matches(isDisplayed()))
}

This will run a series of heuristics (touch target size, contrast, content descriptions) and fail the test if any violation is found, giving you immediate feedback in CI.

---

Autonomous, Persona‑Driven Exploration – Where Scripts Miss Bugs

Traditional automated tests follow predetermined paths; they excel at regressions but often ignore the myriad ways real users interact with an app. Autonomous exploration platforms (e.g., SUSA) simulate distinct personas—each with its own behavior model—to stress the application in ways a script writer might not anticipate.

How Personas Work

PersonaCore TraitsTypical Interaction Patterns
CuriousExplores every visible element, taps repeatedly, long pressesOpens settings, tries hidden gestures, rotates device frequently
ImpatientRapid taps, minimal waiting, aborts if UI does not respond within 1 sSpams call button, cancels mid‑dial, force‑closes app
NovicePrefers large, labeled buttons, avoids icons without text, reads tooltipsStarts with help screen, follows on‑boarding prompts, rarely uses shortcuts
AdversarialAttempts to break invariants: sends malformed input, rotates while dialog is open, denies permissions repeatedlyToggles camera off mid‑call, spam back button, forces network loss via airplane mode
ElderlySlower interaction, larger tap targets needed, prefers voice commandsUses accessibility scale, relies on TalkBack, avoids multi‑touch gestures
AccessibilityRelies on screen reader, high contrast, switch controlNavigates via directional pad, expects all actions to be announced
Power UserUses shortcuts, expects quick settings, multitasks heavilySplits screen, uses picture‑in‑picture, triggers quick tiles (flashlight, DND)
Battery‑Saver ConsciousFrequently checks battery, enables power‑saving modes mid‑taskStarts call, then toggles Battery Saver, observes impact

Each persona is driven by a stochastic state machine that decides the next action based on the current UI hierarchy, sensor readings (battery, network), and a configurable “aggressiveness” factor. The engine records every visited screen, every action taken, and any anomaly (crash, ANR, unhandled exception, permission denial, accessibility violation).

Why This Finds Issues Scripts Overlook

  1. Permission Race Conditions – A script may grant permissions once at startup; an adversarial persona might repeatedly deny and re‑grant the camera permission while a call is in progress, exposing a race where the app tries to reconnect the camera after a denial and crashes.
  1. Gesture Conflicts – The curious persona may discover a hidden long‑press on the video preview that triggers a debug overlay; if the overlay incorrectly captures touch events, the call controls become unresponsive—a scenario rarely captured in a linear test script.
  1. Multitasking Interference – A power‑user persona often puts the app in split‑screen while simultaneously launching a CPU‑heavy game. The resulting thermal throttling can cause the video encoder to drop frames, leading to noticeable drift that only appears under sustained load.
  1. Network Fluctuation Patterns – Rather than a fixed latency, the adversarial persona can enable airplane mode for 200 ms, disable it, then repeat, simulating a flaky Wi‑Fi hand‑off. This can reveal bugs in ICE restart logic where the app fails to renew candidates.
  1. Accessibility Overlaps – When TalkBack is enabled, some custom views lose their contentDescription after a configuration change. An accessibility‑focused persona that repeatedly rotates the device while the screen reader is active will catch this regression, whereas a script that tests rotation only once may miss it.
  1. Battery‑Saver Induced Frame Drops – The battery‑saver conscious persona will trigger Power Save mid‑call, causing the system to lower the CPU frequency. If the app does not adapt its encoder bitrate, the video may become blocky. Scripts that keep the device plugged in will not see this.

Integrating Persona Exploration into Your Workflow

While SUSA is one implementation of this idea, the core principle—varying user behavior models to expose hidden interaction bugs—can be adopted with open‑source tools like adb monkey combined with custom scripts, or with frameworks such as AndroidX Test’s ActivityScenario combined with random decision generators.

---

Edge Cases That Surface Only in Production

Even the most exhaustive test matrix can miss conditions that arise from the real‑world device ecosystem. Below is a curated list of production‑only triggers, why they matter, and how you can approximate them in a lab.

Production TriggerWhy It Breaks CallsLab Approximation
Carrier‑Specific VoLTE/ViLTE Hand‑offSwitching between LTE voice and video causes the system to tear down and rebuild the media stack; some apps forget to release the old AudioTrack.Use adb shell svc data disable and adb shell svc data enable rapidly while on a call, or use a network simulator that mimics LTE bearer changes.
VPN or Proxy with Split TunnelingTraffic may be routed through a tunnel that blocks UDP ports needed for media, forcing fallback to TCP and increasing latency.Configure the device’s VPN settings to point to a local openvpn instance that drops UDP 50000‑60000.
Captive Portal InterceptionPublic Wi‑Fi redirects HTTP(S) to a login page; if the app does not detect the portal, signaling times out.Run a local hotspot with hostapd that serves a redirect page for the first HTTP request, then allow traffic after manual auth.
Do Not Disturb (DND) Priority ModeIncoming call notifications may be suppressed, causing the user to miss an incoming video call request.Enable DND via adb shell cmd notification set_interruption_filter 2 and place a second call from another device.
Nearby Share / Quick Share InterferenceThese services also request microphone access for audio notes; simultaneous access can cause the audio route to switch unexpectedly.Launch Nearby Share audio note recording while a video call is active and observe audio glitches.
External USB Audio/Video AccessoriesPlugging in a USB webcam or headset mid‑call may trigger a route change; some apps do not handle AudioDeviceCallback.Use a USB‑OTG adapter with a webcam, call adb shell am broadcast -a android.media.ACTION_AUDIO_BECOMING_NOISY to simulate unplug, then plug again.
System UI Themes (Dark Mode, High Contrast)Custom views that hard‑code background colors become invisible or lose contrast.Enable forced dark mode via adb shell cmd ui mode night yes and run the accessibility check.
Background Location ServicesSome OEMs aggressively kill background services when location is in use; if your signaling relies on a background keep‑alive, the call may drop after a few minutes.Start a location request in another app, then observe if your signaling socket receives a keep‑alive timeout.
Thermal Throttling from GamingSustained GPU load raises temperature, causing the encoder to drop bitrate or switch to software fallback, increasing CPU usage and leading to jank.Run a GPU stress test (e.g., glmark2) in background while maintaining a call; monitor adb shell dumpsys thermalservice.
Multiple Simultaneous Calls (VoIP + Cellular)Receiving a cellular call while a VoIP call is active can cause audio routing conflicts; the system may give priority to the cellular stream, muting VoIP audio.Initiate a VoIP call, then trigger an incoming cellular call via adb shell telecom service call +1234567890.
SIM Switch Dual‑Active DevicesOn dual‑SIM phones, switching the default data SIM mid‑call can cause a momentary loss of IP connectivity.Use adb shell telephony sim select-slot 1 then 2 repeatedly during a call.
Enterprise Device Owners (MDM) PoliciesMDM may enforce disabled camera or microphone; the app must gracefully degrade rather than crash.Install a test DPC (Device Policy Controller) that sets setCameraDisabled(true) and launch the call.

To capture these, augment your test matrix with environmental variation steps: toggle a setting, plug/unplug a peripheral, or inject a system broadcast before or during the call. Automate the toggles via adb shell commands within a @Rule that resets the state after each test.

---

Concise Checklist for Engineers

✅ ItemDescription
PermissionsVerify runtime grant/denial flows for CAMERA, RECORD_AUDIO, PHONE_STATE; ensure rationale dialogs appear and app does not crash.
Happy‑Path MediaConfirm both local and remote video render within 2 s, audio levels move with speech, and call terminates cleanly (resources released).
Error HandlingTest missing permissions, signaling failures (500/timeout), network loss, and audio focus loss; assert graceful UI and no leaked objects.
Edge CasesSimulate latency/jitter, incoming voice/SMS calls, battery saver, orientation changes, split‑screen, PiP, and device rotation while TalkBack is on.
AccessibilityRun AccessibilityChecks.enable(); manually navigate with TalkBack; verify content descriptions, touch target size ≥ 48 dp, and contrast ratios ≥ 4.5:1.
SecurityEnsure window flag FLAG_SECURE is set; confirm signaling is TLS‑encrypted; verify no screen capture leakage; check permissions persist after reboot.
Network StressUse tc/netem or TrafficShaper to inject delay, jitter, loss; confirm call recovers without crash after conditions normalize.
Interruption HandlingValidate behavior when an incoming cellular call, SMS heads‑up, or notification shade appears during a call.
MultitaskingTest split‑screen, picture‑in‑picture, and background CPU/GPU load; ensure no deadlock or excessive frame drop.
CleanupAfter each test scenario, assert that MediaRecorder, AudioRecord, Camera, and SurfaceView objects are released; inspect adb shell dumpsys media.audio_flinger for orphaned tracks.
Logging & MetricsCapture Logcat for unexpected warnings, record frame‑timestamps via MediaCodec getOutputBuffers(), and log battery/temperature for correlation analysis.

Run this checklist on a representative matrix of devices (different SoCs, Android versions, OEM skins) before marking a feature as release‑

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free