How to Implement the FadRec Screen Recording Accessibility Service in Android

The FadRec screen recording feature uses an Android AccessibilityService that listens for a custom broadcast intent to trigger screenshot capture via modern HardwareBuffer APIs on Android R+ or system global actions on older versions.

The FadRecScreenshotAccessibilityService in the anonfaded/FadCam repository enables screen recording through an accessibility-based architecture. This service coordinates screenshot capture by registering a BroadcastReceiver that responds to application-specific intents, allowing seamless integration with the FadCam recording workflow.

Service Architecture and Lifecycle

The service implements a broadcast-driven architecture defined in FadRecScreenshotAccessibilityService.java. It registers a private BroadcastReceiver during onServiceConnected to listen exclusively for the ACTION_TRIGGER_FADREC_SCREENSHOT intent.

// Inside FadRecScreenshotAccessibilityService.java
private final BroadcastReceiver triggerReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null ||
            !Constants.ACTION_TRIGGER_FADREC_SCREENSHOT.equals(intent.getAction())) {
            return;
        }
        FLog.d(TAG, "Trigger broadcast received in accessibility service.");
        captureNow();
    }
};

private void ensureReceiverRegistered() {
    if (receiverRegistered) return;
    IntentFilter filter = new IntentFilter(Constants.ACTION_TRIGGER_FADREC_SCREENSHOT);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        registerReceiver(triggerReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
    } else {
        registerReceiver(triggerReceiver, filter);
    }
    receiverRegistered = true;
}

The service maintains a "pending capture" flag in shared preferences (PREFS_NAME = "fadrec_screenshot_shortcut") to handle race conditions where the broadcast arrives before the service fully initializes.

Triggering Screenshots from Your Activity

To initiate capture, ScreenShotCaptureActivity.java first validates that the accessibility service is enabled using FadRecScreenshotAccessibilityService.isServiceEnabled(). If disabled, it redirects users to system accessibility settings. Otherwise, it marks a pending capture and broadcasts the trigger intent.

// Inside ScreenShotCaptureActivity.java
if (!FadRecScreenshotAccessibilityService.isServiceEnabled(this)) {
    // Prompt the user to enable the service
    Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
    startActivity(intent);
    return;
}

// Mark that a capture is pending, then send the broadcast
FadRecScreenshotAccessibilityService.markPendingCapture(this);
Intent triggerIntent = new Intent(Constants.ACTION_TRIGGER_FADREC_SCREENSHOT);
sendBroadcast(triggerIntent);

This pattern ensures the service receives the trigger even if it restarts between the activity check and broadcast delivery.

Screenshot Capture Implementation

The captureNow() method implements version-specific screenshot logic. On Android R (API 30) and above, it uses the accessibility service's native takeScreenshot() method with Display.DEFAULT_DISPLAY, receiving a ScreenshotResult containing a HardwareBuffer.

private void captureNow() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // Modern API – returns a ScreenshotResult with a HardwareBuffer
        takeScreenshot(Display.DEFAULT_DISPLAY, Executors.newSingleThreadExecutor(),
            new TakeScreenshotCallback() {
                @Override public void onSuccess(@NonNull ScreenshotResult result) {
                    saveScreenshotResult(result);
                }
                @Override public void onFailure(int errorCode) {
                    showToast(R.string.screenshot_capture_failed);
                }
            });
    } else {
        // Fallback for Android 9/10
        boolean ok = performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);
        showToast(ok ? R.string.screenshot_capture_system_saved
                    : R.string.screenshot_capture_failed);
    }
}

For Android 9-10 (API 28-29), the service falls back to performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT), delegating capture to the system screenshot mechanism.

Saving and Broadcasting Results

Upon successful capture on modern Android versions, the service converts the HardwareBuffer to a Bitmap, creates an ARGB copy, and persists it via PhotoStorageHelper.saveJpegBitmap().

private void saveScreenshotResult(@NonNull ScreenshotResult result) {
    HardwareBuffer hb = result.getHardwareBuffer();
    Bitmap hbBitmap = Bitmap.wrapHardwareBuffer(hb, result.getColorSpace());
    Bitmap copy = hbBitmap.copy(Bitmap.Config.ARGB_8888, false);
    Uri uri = PhotoStorageHelper.saveJpegBitmap(
            getApplicationContext(),
            copy,
            false,
            PhotoStorageHelper.ShotSource.FADREC);
    // …broadcast success/failure as shown earlier…
}

After saving, the service clears the pending capture flag and broadcasts Constants.ACTION_RECORDING_COMPLETE with the saved URI, allowing other application components to process the new screenshot immediately.

Summary

  • The FadRecScreenshotAccessibilityService registers a dedicated BroadcastReceiver for ACTION_TRIGGER_FADREC_SCREENSHOT intents during service connection.
  • Trigger validation occurs in ScreenShotCaptureActivity.java, which checks service status via isServiceEnabled() before marking captures pending and broadcasting.
  • Modern Android (API 30+) uses takeScreenshot(Display.DEFAULT_DISPLAY, ...) with HardwareBuffer processing, while older versions rely on performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT).
  • The PhotoStorageHelper class handles JPEG persistence, after which the service broadcasts completion with the content URI.

Frequently Asked Questions

How does the FadRec service receive capture commands?

The service implements a private BroadcastReceiver registered in onServiceConnected that listens exclusively for Constants.ACTION_TRIGGER_FADREC_SCREENSHOT. Applications send this broadcast via ScreenShotCaptureActivity to trigger the captureNow() method.

What Android versions support the screenshot API?

Android R (API 30) and above support the native takeScreenshot() method with HardwareBuffer return. Android 9 and 10 (API 28-29) use the fallback performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT), while earlier versions require alternative capture methods not implemented in this service.

How is the screenshot saved after capture?

On API 30+, the service extracts the HardwareBuffer from the ScreenshotResult, wraps it into a Bitmap, creates a mutable ARGB copy, and passes it to PhotoStorageHelper.saveJpegBitmap() with the FADREC shot source identifier. The method returns a content URI that gets broadcast to the application.

Why use an AccessibilityService instead of MediaProjection?

The FadRec implementation leverages AccessibilityService privileges to call takeScreenshot() without requiring the MediaProjection consent dialog on every capture. This provides a seamless user experience for automated screenshot workflows, though it requires the user to enable the service in system accessibility settings.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →