How to Test Screen Sharing on Android (Complete Guide)
Screen sharing is a core feature for collaboration apps, remote support tools, live‑streaming platforms, and any software that lets a user broadcast what is on their display. On Android the feature re
Why Screen Sharing Matters on Android
Screen sharing is a core feature for collaboration apps, remote support tools, live‑streaming platforms, and any software that lets a user broadcast what is on their display. On Android the feature relies on the MediaProjection API, which grants such as video‑conferencing, tele‑health, or field‑service, a broken share can halt a meeting, expose private data, or cause a crash that leads to poor ratings and churn. Because the feature touches several system layers—UI rendering, permission model, media codecs, and network transport—defects are often subtle and only surface under specific device states or user interactions. Understanding where things can go wrong helps you build a test strategy that catches regressions before they reach users.
Common Failure Modes in Production
When screen sharing goes awry in the wild, the symptoms usually fall into one of these buckets:
| Failure Category | Typical Symptom | Root Cause (Android‑specific) |
|---|---|---|
| Permission denial | Share button does nothing or shows a toast “Permission denied” | Missing android.permission.SYSTEM_ALERT_WINDOW or android.permission.FOREGROUND_SERVICE; MediaProjection not granted via result callback |
| Black or frozen video | Remote participants see a static frame or a black screen | Surface not released, SurfaceTexture not updated, or hardware encoder stuck due to insufficient buffer allocation |
| Audio‑video drift | Audio continues while video freezes or lags | Mismatched timestamps between MediaCodec output and audio track; improper use of ImageReader vs AudioRecord |
| Overlay interference | Share UI appears but is obscured by system dialogs or other apps | Missing TYPE_APPLICATION_OVERLAY flag, or another app with higher window‑type priority covering the preview |
| Security leakage | Sensitive content appears in the share despite FLAG_SECURE | App forgets to call setSecure(true) on the window or uses a non‑secure Surface for capture |
| Crash / ANR | App stops responding when sharing starts or stops | Long‑running work on the main thread (e.g., blocking ImageReader.acquireLatestImage()), or failure to release the MediaProjection token on onStop |
| Resource leak | Device gets hot, battery drains quickly after a share session | MediaCodec or ImageReader not released, leading to orphaned buffers and continuous GPU usage |
| Compatibility break | Share works on Pixel but fails on Samsung/OnePlus | OEM‑specific restrictions on MediaProjection, custom power‑saving policies, or altered WindowManager behavior |
Each of these categories can be reproduced in a test lab if you know which knobs to turn. The next section lays out a matrix that captures the dimensions you need to exercise.
Test Matrix for Screen Sharing
The following table maps test dimensions (rows) against scenarios (columns). Mark a cell with ✓ when the scenario should be exercised for that dimension, and leave it blank when it is not applicable. Use this matrix to generate both manual test scripts and automated test cases.
| Dimension \ Scenario | Happy‑Path Share Start | Share Stop Mid‑Session | Permission Denied | Black Screen Injection | Overlap with System Alert | Low‑Bandwidth Network | Picture‑in‑Picture (PiP) Mode | Multi‑Window (Split‑Screen) | Accessibility Service Active | Device Administrator Policy | FLAG_SECURE Window | Battery‑Saver Mode |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Functional correctness | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Performance & latency | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||||||
| Security & privacy | ✓ | ✓ | ✓ | |||||||||
| Accessibility (WCAG) | ✓ | ✓ | ✓ | |||||||||
| Stability (crash/ANR) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Resource management | ✓ | ✓ | ✓ | ✓ | ✓ | |||||||
| Compatibility (OEM) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Regression (post‑update) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
How to read the matrix
- A ✓ in the *Functional correctness* row means you must verify that the share behaves as expected (video renders, audio syncs, UI updates).
- The *Performance & latency* row invites you to measure frame‑rate, end‑to‑end latency, and CPU usage under the listed scenario.
- *Security & privacy* focuses on ensuring that no protected content leaks and that overlays cannot be hijacked.
- *Accessibility* checks that talkback, switch control, or font scaling still work while sharing.
- *Stability* looks for uncaught exceptions or ANRs.
- *Resource management* watches for leaks in MediaCodec, ImageReader, or Surface objects.
- *Compatibility* forces you to run the same matrix on at least three distinct device families (Google, Samsung, Xiaomi) and two Android API levels (e.g., 30 and 33).
- *Regression* is the same set of scenarios executed after each code change to detect drift.
You can turn each checked cell into a concrete test case. The next two sections show how to execute them manually and then how to automate the repetitive parts.
Manual Testing Approach
Setup
- Device preparation – Use a physical device (API 21+). Disable battery optimizations for the test app (
Settings > Apps > [YourApp] > Battery > Unrestricted). Enable Developer options →Stay awakewhile charging to prevent the screen from turning off mid‑test. - Instrumentation – Install
adbversion ≥ 1.0.40. Grant the app theandroid.permission.SYSTEM_ALERT_WINDOWviaadb shell pm grant your.package.name android.permission.SYSTEM_ALERT_WINDOW. - Log capture – Start
adb logcat -v threadtime > screenshare.log &to collect timestamps, exceptions, and MediaProjection callbacks. - Remote viewer – Set up a simple receiver (e.g.,
ffplay -framerate 30 -i udp://@:5000?overrun_nonfatal=1) to view the streamed frames, or use the companion app’s preview if it exists. - Baseline – Run a happy‑path share for 30 seconds and note the average frame‑rate (target ≥ 24 fps) and end‑to‑end latency (measure with a timestamp overlay).
Step‑by‑Step Test Cases
| # | Scenario | Actions | Expected Observation |
|---|---|---|---|
| 1 | Happy‑path start | Tap “Share Screen” → grant MediaProjection via system dialog → confirm preview appears | Preview shows live UI, no black frames, audio continues if applicable |
| 2 | Share stop mid‑session | While sharing, press Back or tap “Stop Share” | Preview stops, MediaProjection token released, no leftover Surface in logcat |
| 3 | Permission denied | Deny the system MediaProjection dialog | App shows a toast/error, no preview, onResult returns RESULT_CANCELED |
| 4 | Black screen injection | Use adb shell service call media_projection 1 i32 0 to simulate a null result | App handles null data gracefully, does not crash, shows error UI |
| 5 | Overlap with system alert | Trigger a system alert (e.g., incoming call) while sharing | Share preview is paused or hidden; after call ends, sharing resumes without glitch |
| 6 | Low‑bandwidth network | Connect device to a Wi‑Fi network throttled to 500 kbps (via tc on a router) | Video quality degrades gracefully, no freeze, audio stays in sync |
| 7 | Picture‑in‑Picture | While sharing, swipe up to home → app enters PiP | Sharing continues in the small PiP window, UI scales correctly |
| 8 | Multi‑window (split‑screen) | Drag the app to top/bottom half, open another app in the other half | Share continues, preview respects the allocated window size |
| 9 | Accessibility service active | Enable TalkBack, start sharing | TalkBack still reads UI elements; share preview does not interfere with focus order |
| 10 | Device administrator policy | Set a policy that disables screen capture (DevicePolicyManager.setScreenCaptureDisabled(true)) | Share request is immediately denied, appropriate error shown |
| 11 | FLAG_SECURE window | Open a Dialog with getWindow().setSecure(true) before starting share | Content inside the dialog appears black in the shared stream, but rest of UI is visible |
| 12 | Battery‑saver mode | Enable Battery Saver, start share | Share works but may drop frame‑rate; verify that the app respects the power‑save hint (lower encoder bitrate) |
During each step, watch logcat for:
MediaProjectionCallback#onStop(should fire when sharing ends)ImageReader#onImageAvailabletimestamps (look for gaps > 33 ms)- Any
ExceptionorANRtags.
Record the outcome in a spreadsheet; any deviation from the expected observation flags a defect.
Observations and Logging
- Frame‑rate calculation – Extract timestamps from the
MediaCodecoutput buffers (bufferInfo.presentationTimeUs). Compute delta between consecutive frames; outliers > 50 ms indicate stutter. - Audio‑video sync – If you embed a 1 kHz tone in the audio track, compare its zero‑crossings with a visual flash in the video (you can flash a bright rectangle every second). Drift > 40 ms is noticeable.
- Resource leaks – After each test, run
adb shell dumpsys media.projection; the active projection count should be zero. Also checkadb shell dumpsys meminfofor growing native memory.
Manual testing is invaluable for catching UX‑specific glitches (e.g., a button that becomes unreachable when the share preview overlays it). However, repeating the matrix on every build is tedious. The next section shows how to automate the repeatable parts while retaining the ability to explore edge cases that scripts often miss.
Automated Testing Strategies
Using Espresso/UIAutomator for Interaction Flows
Espresso excels at verifying UI state before and after a share action. UIAutomator can cross‑app boundaries, which is useful for granting the MediaProjection permission via the system dialog.
// ShareScreenTest.kt
@RunWith(AndroidJUnit4::class)
class ShareScreenTest {
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)
@Test
fun happyPathShareStartAndStop() {
// Click share button
onView(withId(R.id.btn_share)).perform(click())
// Grant permission via UIAutomator (system dialog)
val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
val allowButton = uiDevice.findObject(By.text("Allow"))
assertTrue("Allow button not found", allowButton.exists())
allowButton.click()
// Verify preview SurfaceView appears
onView(withId(R.id.preview_surface)).check(matches(isDisplayed()))
// Simulate stopping share
onView(withId(R.id.btn_stop)).perform(click())
onView(withId(R.id.preview_surface)).check(matches(not(isDisplayed())))
// Ensure MediaProjection callback fired
val activity = activityRule.activity
assertFalse("Projection still active", activity.isProjectionActive())
}
}
What this covers
- Permission flow (UIAutomator)
- Visibility of preview (Espresso)
- Clean shutdown (custom
isProjectionActive()flag you expose via aViewModel)
Direct MediaProjection API Unit Tests
You can unit‑test the core capture logic without launching a full UI by using a mock ImageReader and a CountDownLatch to verify that frames are produced.
// MediaCaptureTest.java
@Test
public void testFrameProduction() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicLong lastTimestamp = new AtomicLong(-1);
ImageReader reader = ImageReader.newInstance(
1080, 1920, ImageFormat.YUV_420_888, 2, new Handler(Looper.getMainLooper()));
reader.setOnImageAvailableListener(reader1AvailableListener(reader1 -> {
Image img = reader1.acquireLatestImage();
long ts = img.getTimestamp();
assertTrue("Timestamp went backwards", ts > lastTimestamp.get());
lastTimestamp.set(ts);
img.close();
latch.countDown();
}, null);
// Simulate a projection that feeds the reader
MediaProjection projection = createMockProjection(reader.getSurface());
projection.start(); // hypothetical method that begins feeding frames
boolean success = latch.await(5, TimeUnit.SECONDS);
assertTrue("No frames received within timeout", success);
reader.close();
projection.stop();
}
The test asserts that timestamps strictly increase, catching encoder stalls or buffer reuse bugs.
ADB‑Based Stress Scripts
For long‑running stability checks, a simple Bash loop can start/stop sharing many times while monitoring logcat for crashes.
#!/usr/bin/env bash
PACKAGE=com.example.screenshare
ITERATIONS=200
LOG=adb_stress.log
echo "Starting stress test – $ITERATIONS iterations" > $LOG
for ((i=1; i<=ITERATIONS; i++)); do
echo "Iteration $i" >> $LOG
# Launch share via a hidden activity or a shortcut
adb shell am start -n $PACKAGE/.ShareLauncherActivity >> $LOG 2>&1
sleep 2 # allow share to start
# Stop share
adb shell am broadcast -a com.example.STOP_SHARE >> $LOG 2>&1
sleep 1
# Check for any crash or ANR since last iteration
adb logcat -d -b crash >> $LOG 2>&1
adb logcat -d -b system >> $LOG 2>&1
done
echo "Stress test finished" >> $LOG
You can extend the script to vary network conditions (adb shell cmd netpolicy set ...) or toggle battery‑saver between iterations.
Leveraging SUSA for Autonomous, Persona‑Driven Exploration
SUSA explores an app without pre‑written scripts by simulating distinct user personas. When you point SUSA at your screen‑sharing feature, it will:
- Curious persona – Tap every visible element, long‑press on the preview, try to drag the share UI off‑screen.
- Impatient persona – Rapidly toggle the share button, spam the stop action, and attempt to start a second share while the first is still initializing.
- Novice persona – Follow system prompts literally, often denying permission the first time, then granting after a toast appears.
- Adversarial persona – Inject malicious intents (e.g., broadcast
android.media.projection.action.STARTwith a craftedIntent) to see if the app mishandles unsolicited projection tokens. - Elderly persona – Use larger font sizes and slower interaction timing; verify that UI elements remain reachable.
- Accessibility persona – Enable TalkBack and Switch Control, then attempt to start a share via accessibility shortcuts.
- Power‑user persona – Open developer options, force GPU rendering, enable “Don’t keep activities”, and observe if the share survives activity recreation.
Susa’s cross‑session memory means that after a first run it remembers which screens led to dead ends (e.g., a permission denial that crashes the app) and avoids repeating fruitless paths, focusing instead on novel combinations like “share while in PiP with TalkBack enabled and battery‑saver on”. The output includes a concise PASS/FAIL matrix for each user flow (login → share → stop) and a list of discovered issues such as:
- ANR when the share button is tapped three times within 400 ms (impulsive persona).
- Black screen when the app is launched in split‑screen mode and the share preview is rendered into an invisible Surface (curious persona).
- WCAG 2.1 1.4.3 contrast failure on the stop button when the system theme is set to high contrast (accessibility persona).
These are precisely the scenarios that hand‑written automated tests often overlook because they combine multiple system states that are difficult to anticipate in isolation. By running SUSA on each PR, you get a safety net that complements your deterministic Espresso/UIAutomator suite.
Choosing the Right Tool for Each Matrix Cell
| Test Dimension | Best Fit Tool | Reason |
|---|---|---|
| Permission flow & UI state | Espresso + UIAutomator | Precise assertion on view visibility and system dialog handling |
| Frame‑rate & timestamp validation | JUnit + mocked ImageReader | Allows deterministic checking of MediaCodec output without UI |
| Long‑running stability / stress | Bash/ADB loop | Easy to repeat hundreds of cycles and capture crash logs |
| Battery‑saver, network throttling, PiP, multi‑window | ADB shell commands (cmd, wm) combined with UIAutomator for navigation | |
| Accessibility & font scaling | UIAutomator with UiObject2.setText() and accessibility service enabled | |
| Adversarial / unexpected intents | SUSA (persona‑driven) or custom adb am broadcast fuzzing | |
| Regression after code change | Combination of the above, stored as CI jobs; Susa provides a baseline “smoke” set that expands over time |
By aligning each matrix cell with the most efficient technique, you keep test execution time low while still achieving high coverage.
Edge Cases That Appear Only in Production
Even the most exhaustive lab matrix can miss conditions that arise only when the app runs in the wild. Below are the most common production‑only pitfalls for screen sharing on Android, along with concrete ways to surface them in a test environment.
1. Network Variability Beyond Simple Throttling
Production networks exhibit bursty loss, jitter, and occasional reconnects. A steady 500 kbps pipe does not capture the effect of a sudden drop to 50 kbps for 2 seconds followed by a spike to 5 Mbps. To emulate this:
# Using tc netem on a Linux router or on the device if rooted
adb shell su -c "tc qdisc add dev wlan0 root netem loss 10% delay 100ms distribution normal"
# Simulate a burst
adb shell su -c "tc qdisc change dev wlan0 root netem loss 30% delay 300ms"
sleep 5
adb shell su -c "tc qdisc change dev wlan0 root netem loss 0% delay 0ms"
Observe whether the encoder adapts its bitrate, whether the app drops frames gracefully, and whether the MediaCodec recovers without dropping the Surface.
2. Multi‑Window and Picture‑in‑Picture Interactions
Some OEMs treat the preview Surface as a normal window; others assign it a higher z‑order when the app is in PiP. Test the following sequence on a device with split‑screen support:
- Launch the app in the left half of split‑screen.
- Start screen sharing.
- Drag the divider to make the app’s width less than 360 dp (the minimum width for many UI elements).
- Verify that the preview does not get clipped and that touch events still reach the share controls.
On certain Samsung devices, the system may automatically pause the projection when the app becomes invisible; ensure your MediaProjectionCallback#onStop handler re‑requests a new projection if the user returns to the app.
3. Manufacturer‑Specific MediaProjection Restrictions
Xiaomi’s MIUI, Huawei’s EMUI, and OnePlus’s OxygenOS sometimes require an additional android.permission.READ_EXTERNAL_STORAGE for the projection to work, or they impose a background execution limit that kills the service after a few minutes. To test:
- Install the device‑specific OEM ROM in an emulator (if available) or use a cloud‑based device farm.
- Run a 10‑minute share session while monitoring
adb shell dumpsys activity processesfor your package. - If the process disappears, check logcat for
ActivityManager: Killing ... due to background execution limit.
The fix often involves moving the capture work to a foreground service with a persistent notification.
4. Overlay Permission Conflicts
If your app also uses a custom overlay (e.g., a floating chat head), the system may grant the overlay permission but deny the MediaProjection request because both request SYSTEM_ALERT_WINDOW. Some devices treat the first granted permission as the winner, causing the second to silently fail. Test by:
- Granting overlay permission via settings.
- Starting the share flow.
- Checking
adb logcat | grep MediaProjectionforPermissionDenialException.
If you see a denial, restructure your app to request the projection *before* showing any overlay, or merge both functionalities into a single window with the appropriate flags.
5. Accessibility Service Interference
Services like TalkBack or Switch Control can capture touch events and deliver them with added latency. When a user enables these services, the share UI might become unresponsive because the accessibility service intercepts the tap before it reaches your view. To validate:
- Enable TalkBack (
Settings > Accessibility > TalkBack). - Use the accessibility shortcut (volume‑up + volume‑down) to open the global context menu.
- Attempt to start share via the share button.
- Observe whether the button press registers (look for
View#performClickin logcat).
If the press is lost, consider exposing an alternative activation method (e.g., a voice command or a persistent notification action) that bypasses the touch layer.
6. FLAG_SECURE and Secure Surface Conflicts
Some apps deliberately mark certain windows as secure (e.g., payment dialogs). If you attempt to share the entire screen while a secure window is present, the projection will black out that region. A subtle bug occurs when the app *temporarily* marks its own window as secure (perhaps to protect a credential field) and then forgets to clear the flag before starting the share. The result is a partially blacked‑out stream that users may not notice until they see missing UI elements. Test by:
// In your test
activity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
activity.shareScreen() // should still show non-secure parts
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
Check the captured frames (you can save them via ImageReader to disk) for any unexpected black rectangles.
7. Battery‑Saver and Thermal Throttling
When the device reaches a high temperature, the system may clamp the CPU frequency and also lower the maximum bitrate allowed for video encoders. This can cause the encoder to drop frames or produce low‑quality output without raising an error. To simulate:
adb shell cmd power set-power-save true # enable battery saver
adb shell svc wifi disable # force CPU to work harder (optional)
# Run a CPU‑intensive benchmark in background to raise temperature
adb shell "yes > /dev/null &"
After a few minutes, inspect the encoder’s output format (MediaFormat.getInteger(MediaFormat.KEY_BIT_RATE)) and compare it to the requested bitrate. A significant reduction indicates the system is throttling; ensure your app adapts gracefully (e.g., by lowering resolution or notifying the user of reduced quality).
Checklist for Screen Sharing Release
Before you tag a release as “screen sharing ready”, run through this concise checklist. Each item can be verified with a combination of the manual steps, automated tests, and SUSA runs described above.
| ✅ Item | How to Verify |
|---|---|
| Permission flow handles both grant and denial | Espresso/UIAutomator test + manual denial |
| Preview Surface appears and is updated at ≥ 24 fps | Frame‑rate check via MediaCodec timestamps |
| Audio‑video sync stays < 40 ms drift | Insert audio tone + visual flash, measure offset |
| Share stops cleanly on Back, system navigation, or incoming call | Verify MediaProjectionCallback#onStop fires, no leaked Surface |
No black‑screen regions when FLAG_SECURE windows are present | Secure window test, inspect saved frames |
| Overlay apps (chat heads, floating widgets) do not block share UI | Launch overlay, attempt share, check for permission denial |
| Accessibility services (TalkBack, Switch Control) still functional | Enable service, navigate UI, start share |
| Multi‑window and PiP modes keep share alive and visible | Split‑screen + PiP tests, check preview bounds |
| Battery‑saver and thermal throttling do not crash the app | Enable saver, run heat‑generating load, observe bitrate adaptation |
| OEM‑specific quirks (MIUI, EMUI, OxygenOS) are handled | Test on at least one device per OEM family, watch for process kills |
| No resource leaks after repeated start/stop cycles | Run ADB stress loop (≥ 50 iterations), check dumpsys media.projection and meminfo |
| Security: no leakage of secure content via projection | Attempt to share while a secure dialog is up, confirm blacked‑out region only |
| User‑facing error messages are clear and actionable | Manual denial scenarios, verify toast/snackbar text |
| Regression: all previously passing scenarios still pass after code change | Run full matrix + Susa baseline on CI |
If any item fails, treat it as a blocker and fix before promoting the build.
Takeaways
Screen sharing on Android is deceptively simple to start but notoriously hard to keep reliable across the fragmented ecosystem. The feature sits at the intersection of permissions, window management, media codecs, and power policy, which means that a single overlooked flag—like forgetting to clear FLAG_SECURE or neglecting to request the overlay permission before the projection—can produce a black screen, a crash, or a silent data leak.
A disciplined testing strategy combines three layers:
- Deterministic UI tests (Espresso/UIAutomator) that guard the happy path and the most common error flows.
- Targeted unit and integration tests that validate the MediaProjection contract—frame timestamps, Surface lifecycle, and encoder bitrate adjustments.
- Exploratory, persona‑driven runs (via SUSA or similar autonomous agents) that surface the surprising combinations of states—like “impatient user toggling share while TalkBack is active and the device is in battery‑saver mode”—that scripted tests rarely consider.
By exercising the matrix dimensions in both manual and automated forms, and by continuously feeding the results back into your development loop, you can catch the majority of defects before they reach users. Remember to treat each device family as a separate variable; a pass on a Pixel does not guarantee a pass on a Xiaomi Mi or a Samsung Galaxy.
Finally, treat screen sharing not as a static feature but as a living contract with the OS. Whenever you update your target SDK, add a new permission, or change the way you handle windows, re‑run the full matrix and let Susa’s cross‑session memory guide you toward the next hidden bug. When the contract holds, your users get a seamless, trustworthy way to share what they see—exactly what collaboration apps need to thrive.
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