Search

Python SDK

Open App Package over ADB in Python, Android Device Info

Open an app by package name over ADB, check app state, read Android device info, log to the MAS run console and send webhook events from a Python macro.

  • Windows
  • Mac
  • Emulator
  • Cloud device
  • Python SDK
Intermediate Updated 5 min read
On this page
  1. App management: open an app package over ADB from Python
  2. open_app and close_app
  3. get_app_state
  4. is_app_focused and get_current_app
  5. Android device info from Python
  6. get_clipboard
  7. log
  8. webhook
  9. Example: restart the app if it crashed
  10. Example: emit a custom event when a task finishes
  11. Errors

This page covers the functions a Macro Automation Studio (MAS) macro uses around its main loop. You open an app by package name from Python, over adb on an emulator, check its state, read Android device info, write to the run console, read the clipboard, and tell the outside world that something happened. All of them are top-level functions on mas. Full signatures are on the API reference. If you describe the task to MAS Agent, it writes this code for you.

App management: open an app package over ADB from Python

Android apps are addressed by package name, such as com.android.chrome or com.android.settings. To find a package name, open the app on the device and call get_current_app().

open_app and close_app

open_app(package_name, timeout_ms=2000) launches an app and waits up to timeout_ms for it to come up. Heavy games need more than the default two seconds; give them 5000 or more, then wait for a known template with find_object_retry before you tap anything. close_app(package_name) force-stops the app. That clears its state and is the reliable way to reset an app that has hung.

get_app_state

get_app_state(package_name) returns an integer. Compare it with the constants exported by mas rather than with literal numbers.

ConstantValueMeaning
mas.RUNNING_IN_FOREGROUND4the app is on screen
mas.RUNNING_IN_BACKGROUND3running, but another app is in front
mas.RUNNING_IN_BACKGROUND_SUSPENDED2in the background and suspended
mas.NOT_RUNNING1installed but not running
mas.NOT_INSTALLED0not installed on this device

is_app_focused and get_current_app

is_app_focused(package_name) asks the window manager whether the package has input focus and returns a bool. get_current_app() returns the package name of the foreground app. Use them after open_app to wait until the app is in front, and inside long loops to notice when a pop-up or another app has taken over.

python
import time
import mas

mas.open_app("com.android.chrome", timeout_ms=5000)
for _ in range(20):
    if mas.is_app_focused("com.android.chrome"):
        break
    time.sleep(0.5)
mas.log(f"foreground app: {mas.get_current_app()}")

Android device info from Python

  • get_screen_size() returns a ScreenSize with width and height in pixels. Use it to derive positions instead of hard-coding them; see Interaction.
  • get_device_info() returns a DeviceInfo with id, name, type ("android" or "desktop"), screen_width, screen_height and connected.
  • get_host_machine_id() returns the persistent UUID of the computer running MAS. It is generated once, survives restarts, and is shared by every script on that machine. Storage keys depend on it, so the call raises RPCError rather than returning an empty string when the host has no ID.
python
import mas

info = mas.get_device_info()
size = mas.get_screen_size()
mas.log(f"{info.name} ({info.type}) {size.width}x{size.height}")

get_clipboard

get_clipboard() returns the device clipboard as a string. Copying is app-specific: usually a long press on the text, then a tap on the app’s copy control. Read the result straight afterwards.

python
import mas

mas.click(300, 500, delay_ms=1500)     # select the text
mas.click(400, 300)                    # tap the app's Copy control
code = mas.get_clipboard().strip()
mas.log(f"copied: {code}")

log

log(message, level=LogLevel.INFO) writes a line to the run console in the Code Editor, tagged with a level, and mirrors it to the app’s own log. level is a LogLevel member or one of the strings "debug", "info", "warning" and "error". Unlike print, the line is leveled, so warnings and errors stand out in the console. No device is required, so log also works in the parts of a script that run before the first device call.

python
import mas
from mas import LogLevel

mas.log("run started")
mas.log("retries running low", level="warning")
mas.log("step failed", level=LogLevel.ERROR)

webhook

webhook(event, data=None) sends a custom event to the webhook endpoints you configured on the Webhooks page. Use it for things only your script knows: a level reached, a captcha on screen, an inventory full. You write no HTTP code, hold no signing secret and implement no retries. The event leaves from MAS’s servers, not from the device, so it is signed, retried and recorded in the delivery log next to macro.started and macro.failed.

  • event is your name for the event: letters, numbers, dots, dashes and underscores, 64 characters or fewer. It is namespaced under custom., so "level.reached" arrives as custom.level.reached. A script can never impersonate a lifecycle event.
  • data is an optional JSON-serializable dictionary of up to 50 keys. The run and the device are attached for you as execution_id and device_name; you cannot set them yourself.
  • The return value is a dictionary with event_id (the receiver’s dedupe key), event_type (the namespaced name) and queued (how many endpoints it went to). A queued of 0 is not an error; it means no endpoint is subscribed to this event. If the event cannot be queued at all, the call raises instead of failing silently.

Subscribe an endpoint to custom.*, to *, or to the exact name such as custom.level.reached. Payload format, signature verification and the retry schedule are on Webhooks.

python
import mas

result = mas.webhook("level.reached", {"level": 40, "account": "alt-3"})
mas.log(f"{result['event_type']} sent to {result['queued']} endpoint(s)")

Example: restart the app if it crashed

Check the state and the focus, force-stop and relaunch when either is wrong, then wait for a template that proves the app is usable.

python
import mas

PACKAGE = "com.example.game"
images = mas.images({"lobby": 501})

def ensure_game_running() -> bool:
    state = mas.get_app_state(PACKAGE)
    if state == mas.NOT_INSTALLED:
        mas.log(f"{PACKAGE} is not installed", level="error")
        return False
    if state != mas.RUNNING_IN_FOREGROUND or not mas.is_app_focused(PACKAGE):
        mas.log("game is not in front, restarting", level="warning")
        mas.close_app(PACKAGE)
        mas.open_app(PACKAGE, timeout_ms=8000)
    lobby = mas.find_object_retry(images.lobby, total_tries=10, time_sleep=3.0)
    if lobby is None:
        mas.log("lobby never appeared after restart", level="error")
        return False
    return True

if not ensure_game_running():
    raise SystemExit(1)

Call it at the top of the run and again whenever a template search keeps failing; a crashed app is the most common reason a macro stops seeing what it expects.

Example: emit a custom event when a task finishes

Read whatever the receiver needs from the screen, then send it. The device port tags the event when several devices run the same macro; it comes from Storage, which uses the same port to key its entries.

python
import mas
from mas import Region

ENERGY = Region(x1=880, y1=40, x2=1060, y2=90)

def run_daily_tasks() -> int:
    done = 0
    # ... tap through the task list, counting each success ...
    return done

completed = run_daily_tasks()
energy_left = mas.read_text(region=ENERGY).text.strip()

mas.webhook("daily.finished", {
    "tasks_completed": completed,
    "energy_left": energy_left,
    "device_port": mas.get_current_device_port(),
})
mas.log(f"daily tasks done: {completed}")

A receiver subscribed to custom.daily.finished gets the signed payload with your keys plus execution_id and device_name, and can match it to the run’s macro.completed event by the execution id.

Errors

open_app, close_app and the state checks raise DeviceNotConnectedError when no device is bound to the run and CommandFailedError when the device rejects the command. log and webhook need no device. The full hierarchy is on Errors and exceptions.

Next steps

Related pages

Was this page helpful?

Questions? Ask in Discord