Skip to content

Actions Architecture & Execution Lifecycle ​

In Macrion, Actions represent the discrete operations executed when an Event's conditions are satisfied or when an event trigger fires. While conditions and triggers observe the device environment, actions directly alter it—injecting physical touch gestures, writing text into form fields, mutating runtime variables, dispatching system shortcuts, or firing broadcast intents to external automation tools.

This document details the internal architecture of Macrion's action execution pipeline, the sequential dispatch lifecycle, anti-detection coordinate and duration randomization, and OS-level safety mechanisms.


The Event-Action Relationship ​

Actions do not exist independently; they are strictly bound to a parent Event.

Scenario
 └── Event 1 (Priority 10)
      ├── Conditions (Vision: Image / Color / Text / Number) OR Triggers (Timer / Counter / Broadcast)
      └── Actions [Sequential Execution List]
           ├── Action 1: Click (Tap button)
           ├── Action 2: Pause (Wait 300 ms)
           ├── Action 3: ChangeCounter (Increment run count)
           └── Action 4: ToggleEvent (Disable self, enable Phase 2)

Each event contains an ordered list of actions:

  • Sequential Execution: Actions within an event are evaluated sequentially in the exact order they are listed.
  • Synchronous Completion: Each action must fully complete (e.g., a physical tap must finish its press duration, a pause must elapse its full timer) before the dispatcher advances to the next action in the list.
  • Fail-Safe Processing: If an action encounters a non-fatal error (such as failing to locate a focused text input), Macrion logs a diagnostic warning and continues executing the subsequent actions in the sequence.

Action Execution Pipeline ​

When an event triggers, ActionExecutor handles the execution loop. It bridges the background coroutine processing engine with Android's main UI thread and the Android Accessibility framework.

mermaid
flowchart TD
    Trigger["Event Condition / Trigger Satisfied"] --> ActionLoop["ActionExecutor.executeActions()"]
    ActionLoop --> Iterate["Iterate actions sequentially"]

    subgraph MemoryState["In-Memory Execution (Background Thread)"]
        Iterate -->|ChangeCounter| StateCounter["ProcessingState.setCounterValue()"]
        Iterate -->|ToggleEvent| StateToggle["ProcessingState.enableEvent() / disableEvent()"]
        Iterate -->|Pause| StateDelay["kotlinx.coroutines.delay(pauseDurationMs)"]
    end

    subgraph MainThread["Main UI Thread (Dispatchers.Main)"]
        Iterate -->|Click / Swipe| Gesture["AndroidActionExecutor.dispatchGesture()"]
        Iterate -->|SetText| Text["AndroidActionExecutor.writeTextOnFocusedItem()"]
        Iterate -->|SystemAction| Global["AccessibilityService.performGlobalAction()"]
        Iterate -->|Intent| IntentExec["startActivity() / sendBroadcast()"]
        Iterate -->|ExternalAction| Plugin["Broadcast to Locale / Tasker Plugin"]
        Iterate -->|Notification| Notif["NotificationManager.postNotification()"]
    end

    Gesture --> GestureExecutor["GestureExecutor (with adaptive timeout)"]
    GestureExecutor --> CompleteCheck{"Gesture Succeeded?"}
    CompleteCheck -->|Yes| NextAction["Proceed to next action"]
    CompleteCheck -->|No / Dropped| Backoff["Automatic 500 ms Safety Backoff"]
    Backoff --> NextAction

    StateCounter --> NextAction
    StateToggle --> NextAction
    StateDelay --> NextAction
    Text --> NextAction
    Global --> NextAction
    IntentExec --> NextAction
    Plugin --> NextAction
    Notif --> NextAction

Thread Marshaling ​

Macrion separates internal state management from Android framework interactions:

  1. Background Processing: Operations that do not touch Android views or system services (ChangeCounter, ToggleEvent, Pause) execute directly on the background worker coroutine thread. This avoids unnecessary thread hops and guarantees immediate variable updates.
  2. Main Thread Marshaling (Dispatchers.Main): Android's AccessibilityService.dispatchGesture, performGlobalAction, window node inspection, and activity launching must be invoked from the main thread. ActionExecutor marshals these calls using withContext(Dispatchers.Main).

Anti-Detection & Humanization Engine ​

Repetitive, pixel-perfect clicks and rigid timing intervals can trigger anti-bot heuristics in certain games and security-sensitive applications. Macrion includes a built-in anti-detection engine governed by RandomizerConfig.

When randomization is enabled in Scenario Settings, Macrion injects controlled pseudo-random Gaussian jitter into both spatial coordinates and temporal durations.

Spatial Randomization (Coordinates) ​

Whenever a touch position is dispatched (whether for static USER_SELECTED clicks, dynamic ON_DETECTED_CONDITION taps, or Swipe endpoints), Macrion perturbs the coordinates within a bounded offset:

$$\Delta x \in [-5, +5]\text{ px}, \quad \Delta y \in [-5, +5]\text{ px}$$

kotlin
// GestureHelpers.kt
fun Path.moveTo(position: Point, random: Random?) {
    if (random == null) safeMoveTo(position.x, position.y)
    else safeMoveTo(
        random.nextIntInOffset(position.x, RANDOMIZATION_POSITION_MAX_OFFSET_PX),
        random.nextIntInOffset(position.y, RANDOMIZATION_POSITION_MAX_OFFSET_PX),
    )
}

This prevents touches from repeatedly striking the identical pixel, replicating natural human thumb and stylus variance without straying outside standard button hitboxes.

Temporal Randomization (Durations & Pauses) ​

Fixed touch durations (e.g., exactly 50 ms) and fixed pause delays (e.g., exactly 1000 ms) produce artificial, easily fingerprinted execution signatures. Macrion applies a duration offset of up to $\pm 5\text{ ms}$:

$$\Delta t \in [-5, +5]\text{ ms}$$

  • Touch Press Duration: A configured 50 ms click executes between 45 ms and 55 ms.
  • Swipe Travel Time: A configured 300 ms swipe executes between 295 ms and 305 ms.
  • Pause Intervals: A configured 500 ms pause sleeps between 495 ms and 505 ms.

NOTE

Duration values are strictly normalized after randomization. Press durations cannot fall below MINIMUM_STROKE_DURATION_MS (1 ms) or exceed MAXIMUM_STROKE_DURATION_MS (65,000 ms).


OS-Level Safety & Error Handling ​

Automating gestures across external applications can encounter system lag, ANRs, or unresponsive touch dispatchers. Macrion incorporates multiple defensive layers to maintain stability.

1. Adaptive Gesture Timeout ​

Android's AccessibilityService.dispatchGesture operates asynchronously with a GestureResultCallback (onCompleted or onCancelled). If the underlying display server or window manager stalls, a coroutine could hang indefinitely waiting for callback confirmation.

GestureExecutor wraps every gesture dispatch in a withTimeoutOrNull block:

$$\text{Timeout} = \text{clamp}(2 \times \text{durationMs}, 100\text{ ms}, 65,000\text{ ms})$$

If the system fails to report completion before the timeout elapses:

  1. The pending continuation is safely cancelled.
  2. An error counter is incremented in diagnostic dumps.
  3. The dispatcher safely proceeds rather than freezing the scenario loop.

2. Automatic Throttling Backoff ​

If the Android system reports that a gesture was cancelled or dropped, injecting further gestures immediately could overwhelm an already struggling system:

kotlin
// AndroidActionExecutorImpl.kt
if (!gestureExecutor.dispatchGesture(service, gestureDescription)) {
    Log.w(TAG, "System did not execute the gesture properly, delaying processing to avoid spamming slow system")
    delay(500)
}

An automatic 500 ms backoff is enforced before the scenario engine continues.

3. Google Pixel Android 15 Unblock Injection ​

On Google Pixel devices running early Android 15 builds, a known OS bug (Issue Tracker #384188031) causes the system InputDispatcher to freeze gesture dispatch if accessibility touches are submitted without intermittent hardware-like unblock taps.

When the Pixel Touch Unfreeze Workaround is active in App Settings, UnblockGestureScheduler automatically injects a harmless, multi-finger wake gesture at 10-second intervals upon loop completion.

4. Intent Flooding Protection ​

Firing external activities or broadcasts too rapidly can overwhelm Android's ActivityManager. Macrion enforces hard delays after executing intent actions:

  • Broadcast Delay: 100 ms post-broadcast delay (INTENT_BROADCAST_DELAY).
  • Activity Start Delay: 1000 ms post-activity delay (INTENT_START_ACTIVITY_DELAY) to allow the target application's window to create and focus.

Complete Action Catalog Matrix ​

The table below summarizes the action types available in Macrion:

Action TypeExecution DomainKey ParametersPrimary Use Case
ClickAccessibility TouchPosition (USER_SELECTED vs ON_DETECTED_CONDITION), clickOffset, pressDurationTapping buttons, confirming dialogs, or dynamically tapping detected images/text.
SwipeAccessibility Touchfrom coordinate, to coordinate, swipeDurationScrolling lists, dragging sliders, flicking carousels, or performing swipe gestures.
SetTextAccessibility Focustext (with {counter} tags), validateInput (IME Enter)Typing dynamic strings or numbers into focused input fields; falls back to clipboard paste.
SystemActionAccessibility GlobalType (BACK, HOME, RECENT_APPS)Hardware-level navigation shortcuts (dismissing modals, returning home, opening app switcher).
ChangeCounterIn-Memory StatecounterName, operation (ADD, MINUS, SET), operationValueTracking iterations, tallying rewards, or updating loop bounds.
ToggleEventIn-Memory StatetoggleAll or specific eventToggles (ENABLE, DISABLE, TOGGLE)Controlling state machines, switching automation phases, or halting execution loops.
PauseCoroutine DelaypauseDuration (milliseconds)Inserting non-blocking settling delays between consecutive actions.
IntentAndroid FrameworkisBroadcast, intentAction, componentName, flags, extrasLaunching external apps, triggering Tasker profiles, or sending custom system broadcasts.
NotificationNotificationManagermessageText (with {counter} tags), channelImportanceDisplaying heads-up or status bar progress alerts during automation runs.
ExternalActionLocale / Tasker IPCexternalActionNameSending synchronous query broadcasts to Tasker/Locale event plugins.

Free and open source under the GNU GPL v3.0 License.