Skip to main content

What is Headless Mode?

Headless mode allows Scrcpy for Android to launch and automatically connect to a target device without displaying the main configuration UI. This is ideal for automation, kiosk applications, or scenarios where user interaction should be minimized.
Headless mode is triggered by passing the START_REMOTE intent extra when launching the MainActivity.

Enabling Headless Mode

To launch the app in headless mode, include the START_REMOTE boolean extra in your intent:

Via ADB Command

adb shell am start -n com.example.scrcpy/.MainActivity \
  --ez start_remote_headless true

Via Android Intent

Intent intent = new Intent(context, MainActivity.class);
intent.putExtra(MainActivity.START_REMOTE, true);
startActivity(intent);

Constant Definition

The constant is defined in MainActivity.java:61:
public final static String START_REMOTE = "start_remote_headless";

How Headless Mode Works

Behavior Changes

When headless mode is enabled:
1

UI Hidden

The main scroll view containing connection settings is hidden (MainActivity.java:213-218, 273-278):
if (headlessMode) {
    View scrollView = findViewById(R.id.main_scroll_view);
    if (scrollView != null) {
        scrollView.setVisibility(View.INVISIBLE);
    }
}
2

Auto-Connect

On first launch, the app automatically attempts to connect using saved preferences (MainActivity.java:209-212):
if (headlessMode && first_time) {
    getAttributes();
    connectScrcpyServer(PreUtils.get(this, Constant.CONTROL_REMOTE_ADDR, ""));
}
The target device address is loaded from SharedPreferences key CONTROL_REMOTE_ADDR.
3

No History Saving

Connection history is not saved in headless mode (MainActivity.java:509-512):
if (headlessMode) {
    // Headless mode does not save history
    return false;
}

State Persistence

The headless mode state is preserved across configuration changes (MainActivity.java:228):
outState.putBoolean("headlessMode", headlessMode);
This ensures that if the activity is recreated (e.g., due to rotation), it remains in headless mode.

Use Cases for Automation

Headless mode enables various automation scenarios:

Kiosk Mode

Deploy devices that automatically mirror another device’s screen on boot, useful for digital signage or monitoring displays.
# Auto-start on boot using init script
am start -n com.example.scrcpy/.MainActivity \
  --ez start_remote_headless true

Remote Support

Build remote support applications where users tap a “Get Help” button and their screen is automatically shared with support staff.
// In your support app
Intent intent = new Intent();
intent.setComponent(new ComponentName(
    "com.example.scrcpy",
    "com.example.scrcpy.MainActivity"
));
intent.putExtra("start_remote_headless", true);
startActivity(intent);

Testing Automation

Automated testing frameworks can programmatically mirror device screens for visual verification or recording.
# In your test script
os.system("adb shell am start -n com.example.scrcpy/.MainActivity --ez start_remote_headless true")
time.sleep(5)  # Wait for connection
# Perform tests...

Monitoring Systems

Create dashboard applications that monitor multiple devices by automatically connecting in headless mode.
// Launch multiple instances
for (String deviceIp : deviceList) {
    // Set target in preferences first
    PreUtils.put(context, Constant.CONTROL_REMOTE_ADDR, deviceIp);
    
    Intent intent = new Intent(context, MainActivity.class);
    intent.putExtra(MainActivity.START_REMOTE, true);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

Auto-Reconnect Behavior

Headless mode includes automatic reconnection logic when connections fail:

Reconnect Dialog

When a connection fails or drops in headless mode, a dialog is automatically displayed (MainActivity.java:816-831):
if (headlessMode && !resumeScrcpy && !result_of_Rotation) {
    if (!userDisconnect) {
        Dialog.displayDialog(this, 
            getString(R.string.connect_faild),
            getString(R.string.connect_faild_ask), 
            () -> {
                // Retry connection
                connectScrcpyServer(PreUtils.get(context, 
                    Constant.CONTROL_REMOTE_ADDR, ""));
            }, 
            () -> {
                // Cancel - close app
                finishAndRemoveTask();
            }
        );
    }
}

Reconnect Conditions

The auto-reconnect dialog appears only when:
  • Connection failure during initial connect
  • Unexpected disconnection during active session
  • Network timeout or error

User Options

The dialog provides two options:
  1. Retry: Attempts to reconnect using the same saved configuration
  2. Cancel: Exits the application completely using finishAndRemoveTask()
In standard (non-headless) mode, connection failures simply return to the main UI. The auto-reconnect dialog is exclusive to headless mode.

Integration Scenarios

Scenario 1: Boot-Time Auto-Connect

Create a boot receiver to start headless mode automatically:
public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            // Delay to ensure network is ready
            Handler handler = new Handler(Looper.getMainLooper());
            handler.postDelayed(() -> {
                Intent launchIntent = new Intent(context, MainActivity.class);
                launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                launchIntent.putExtra(MainActivity.START_REMOTE, true);
                context.startActivity(launchIntent);
            }, 10000); // 10 second delay
        }
    }
}
AndroidManifest.xml:
<receiver android:name=".BootReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Scenario 2: Tasker Integration

Use Tasker or similar automation apps: Task Configuration:
  1. Action: Send Intent
  2. Action: android.intent.action.MAIN
  3. Cat: Default
  4. Package: com.example.scrcpy
  5. Class: com.example.scrcpy.MainActivity
  6. Extra: start_remote_headless:true
  7. Target: Activity

Scenario 3: Programmatic Configuration

Set the target device before launching headless mode:
public void connectToDevice(String deviceIp) {
    // Save target address to SharedPreferences
    SharedPreferences prefs = context.getSharedPreferences(
        "scrcpy_prefs", 
        Context.MODE_PRIVATE
    );
    prefs.edit()
        .putString(Constant.CONTROL_REMOTE_ADDR, deviceIp)
        .apply();
    
    // Launch in headless mode
    Intent intent = new Intent(context, MainActivity.class);
    intent.putExtra(MainActivity.START_REMOTE, true);
    startActivity(intent);
}

Scenario 4: Tile Service Quick Connect

Create a Quick Settings tile for one-tap connection:
@RequiresApi(api = Build.VERSION_CODES.N)
public class ScrcpyTileService extends TileService {
    @Override
    public void onClick() {
        super.onClick();
        
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra(MainActivity.START_REMOTE, true);
        startActivityAndCollapse(intent);
    }
}

Configuration Requirements

For headless mode to work properly, the following must be pre-configured:
Must be saved in SharedPreferences under key Constant.CONTROL_REMOTE_ADDR.Setting:
PreUtils.put(context, Constant.CONTROL_REMOTE_ADDR, "192.168.1.100:5555");
Format: <ip_address>:<port> (port defaults to 5555 if omitted)
Resolution, bitrate, and delay settings are loaded from saved preferences:
  • PREFERENCE_SPINNER_RESOLUTION: Video resolution index
  • PREFERENCE_SPINNER_BITRATE: Bitrate index
  • PREFERENCE_SPINNER_DELAY: Delay tolerance index
These are set via the UI or programmatically using PreUtils.put().
Touch and navigation settings:
  • CONTROL_NO: Disable touch control (boolean)
  • CONTROL_NAV: Show navigation bar (boolean)
PreUtils.put(context, Constant.CONTROL_NO, false);
PreUtils.put(context, Constant.CONTROL_NAV, true);

Limitations

Be aware of these limitations when using headless mode:
  • No UI Fallback: If connection fails and user cancels retry, app exits entirely
  • No Configuration Changes: Cannot change target device without exiting and modifying preferences
  • History Not Saved: Connection history is disabled to avoid polluting logs with automated connections
  • Single Connection: Only supports connecting to one saved device address

Debugging Headless Mode

To troubleshoot headless mode issues:
# Check if intent was received
adb logcat -s Scrcpy:I

# Look for:
# "headlessMode: true"
# Connection attempts
# Error messages

# Verify saved preferences
adb shell run-as com.example.scrcpy \
  cat /data/data/com.example.scrcpy/shared_prefs/scrcpy_prefs.xml
For testing, manually set preferences via ADB before launching:
adb shell "am broadcast -a com.example.scrcpy.SET_PREF \
  --es key CONTROL_REMOTE_ADDR --es value '192.168.1.100:5555'"

Exit Behavior

In headless mode, the app uses finishAndRemoveTask() instead of returning to the main UI:
finishAndRemoveTask(); // Completely removes app from recents
This ensures the app doesn’t leave background tasks running when disconnected.

Build docs developers (and LLMs) love