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
On this page
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.
| Constant | Value | Meaning |
|---|---|---|
mas.RUNNING_IN_FOREGROUND | 4 | the app is on screen |
mas.RUNNING_IN_BACKGROUND | 3 | running, but another app is in front |
mas.RUNNING_IN_BACKGROUND_SUSPENDED | 2 | in the background and suspended |
mas.NOT_RUNNING | 1 | installed but not running |
mas.NOT_INSTALLED | 0 | not 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.
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 aScreenSizewithwidthandheightin pixels. Use it to derive positions instead of hard-coding them; see Interaction.get_device_info()returns aDeviceInfowithid,name,type("android"or"desktop"),screen_width,screen_heightandconnected.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 raisesRPCErrorrather than returning an empty string when the host has no ID.
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.
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.
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.
eventis your name for the event: letters, numbers, dots, dashes and underscores, 64 characters or fewer. It is namespaced undercustom., so"level.reached"arrives ascustom.level.reached. A script can never impersonate a lifecycle event.datais an optional JSON-serializable dictionary of up to 50 keys. The run and the device are attached for you asexecution_idanddevice_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) andqueued(how many endpoints it went to). Aqueuedof 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.
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.
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.
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
Thanks. If something is wrong, tell us in Discord.
Questions? Ask in Discord