How Android START, STOP, and TOGGLE Intents Control FlClash's VPN Service

FlClash processes Android START, STOP, and TOGGLE intents through a dedicated TempActivity that dispatches commands to the VPN service layer, enabling external control via shortcuts, quick settings tiles, or ADB commands.

The chen08209/FlClash repository implements a lightweight intent-driven pipeline on Android that exposes VPN control actions to external system components. By declaring specific action strings and handling them through a transient entry point Activity, FlClash allows automation tools, quick settings tiles, and third-party launchers to start, stop, or toggle the VPN tunnel without launching the main application interface.

Intent Definition and Construction

FlClash defines its external control interface through a sealed enum and extension functions that construct explicit intents targeting a hidden Activity.

The QuickAction Enum

The three supported operations are declared in android/common/src/main/java/com/follow/clash/common/Enums.kt as the QuickAction enum (lines 6-10). This definition specifies the action strings for START, STOP, and TOGGLE operations that the system recognizes when broadcast to FlClash's package.

Building Explicit Intents

The quickIntent extension function in android/common/src/main/java/com/follow/clash/common/Ext.kt (lines 60-66) constructs an explicit Intent targeting TempActivity. This function packages the selected QuickAction into an intent with the corresponding action string (e.g., com.follow.clash.action.START), preparing it for delivery via startActivity() or PendingIntent creation.

The Intent Processing Pipeline

When an external component fires one of these intents, FlClash routes the command through several abstraction layers before touching the actual VPN interface.

TempActivity Entry Point

android/app/src/main/kotlin/com/follow/clash/TempActivity.kt serves as the intent receiver. In lines 16-34, TempActivity extracts the action string from the incoming intent, matches it against the QuickAction enum values, and immediately dispatches to the appropriate coroutine handler in the State object. This Activity has no UI and finishes immediately after delegating the work, ensuring minimal overhead.

State-Level Command Serialization

The State class in android/app/src/main/kotlin/com/follow/clash/State.kt contains the core logic for handleStartServiceAction, handleStopServiceAction, and handleToggleAction (lines 39-99). These methods serialize the request, update the quick settings tile state if present, and determine whether to invoke the service layer based on current VPN status. They prevent race conditions by managing service state through centralized coroutines.

Service Abstraction and AIDL Binding

android/app/src/main/kotlin/com/follow/clash/Service.kt acts as a thin wrapper around a bound AIDL-based RemoteService. Lines 65-74 define startService and stopService methods that forward commands to the remote process. This abstraction separates the UI process from the VPN service process, allowing the VPN to persist even if the main app closes.

VPN Core Execution

The actual tunnel creation occurs in android/service/src/main/java/com/follow/clash/service/VpnService.kt. This class extends Android's VpnService and implements startService (lines 29-34) and stopService (lines 36-44). These methods build the TUN interface using the Android VPN API and hand the file descriptor to the native Clash core through Core.startTun and Core.stopTun, bringing the VPN tunnel up or down as requested.

Lifecycle Synchronization via BroadcastReceiver

To maintain UI consistency when the VPN starts or stops independently of direct intents, FlClash implements a broadcast mechanism. android/app/src/main/kotlin/com/follow/clash/BroadcastReceiver.kt (lines 13-27) listens for SERVICE_CREATED and SERVICE_DESTROYED broadcasts. When received, it updates the State singleton, ensuring that quick settings tiles and in-app indicators reflect the true service status even when toggled via external intents.

Practical Usage Examples

You can trigger FlClash's VPN service directly from ADB or integrate control into your own Android applications using the intent scheme.

Triggering via ADB

Send a START intent from the command line:

adb shell am start -n com.follow.clash/.TempActivity \
    -a com.follow.clash.action.START

Send a STOP intent:

adb shell am start -n com.follow.clash/.TempActivity \
    -a com.follow.clash.action.STOP

Send a TOGGLE intent:

adb shell am start -n com.follow.clash/.TempActivity \
    -a com.follow.clash.action.TOGGLE

Integrating in Kotlin Applications

Create a shortcut or quick settings tile using the extension functions:

val intent = QuickAction.START.quickIntent  // Or STOP / TOGGLE
val pendingIntent = intent.toPendingIntent    // See Ext.kt for conversion
// Attach pendingIntent to ShortcutInfo or TileBuilder

Fire an intent directly from another component:

val context: Context = GlobalState.application
context.startActivity(QuickAction.TOGGLE.quickIntent)

Summary

  • Intent Definition: QuickAction enum in Enums.kt defines START, STOP, and TOGGLE constants.
  • Entry Point: TempActivity receives external intents and dispatches them to State handlers.
  • State Management: State.kt serializes requests and manages the quick settings tile synchronization.
  • Service Layer: Service.kt forwards commands via AIDL to the remote VPN process.
  • VPN Execution: VpnService.kt creates the TUN interface and controls the native Clash core.
  • Synchronization: BroadcastReceiver.kt listens for service lifecycle events to keep the UI updated.

Frequently Asked Questions

What permissions are required to send these intents to FlClash?

Applications sending these intents must typically hold standard Android permissions to start Activities in other packages. Since TempActivity handles the specific action strings, any app with START_ACTIVITY permission can trigger these intents. However, FlClash may enforce signature-level permissions or require the caller to be the system or a privileged app depending on the Android version and specific build configuration.

Can these intents be triggered from a background service?

The intents target TempActivity, which is an Activity component. Therefore, they must be launched using startActivity() or via PendingIntent from notifications or shortcuts. Background services without a visible window generally cannot directly call startActivity() on these intents due to Android background execution limits unless they use PendingIntent or are bound to a foreground service context.

How does FlClash handle rapid successive TOGGLE intents?

The State class in State.kt implements state validation in handleToggleAction. Before executing a start or stop command, it checks the current service status through the bound Service connection. If a toggle is already in progress or the requested state matches the current state, the coroutine exits early, preventing conflicting calls to the native VPN interface.

What is the difference between using the STOP intent versus killing the FlClash app process?

Sending the STOP intent gracefully terminates the VPN tunnel by calling VpnService.stopService, which properly closes the TUN file descriptor and notifies the native Clash core via Core.stopTun. Killing the app process (via settings or am force-stop) terminates the VPN service abruptly without cleanup, potentially leaving routing rules or DNS settings in an inconsistent state until the system reclaims the VPN interface.

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 →