Guides
ADB Python Tutorial: Control an Android Emulator
An adb Python tutorial for Macro Automation Studio: tap, swipe, type, press keys and take screenshots on BlueStacks, with adb managed by the app for you.
- Windows
- Mac
- Emulator
- Cloud device
- Phone
- Python SDK
On this page
- Before you start
- How the pieces fit
- Create the project
- Screen size and coordinates
- Take a Python adb screenshot
- Tap
- Swipe
- Type
- Press keys
- Open and check apps
- A complete script
- Where adb fits in this Python tutorial
- What can go wrong
- Could not discover RPC port
- Taps land in the wrong place
- input_text typed nothing
- Device not found or offline
Most adb Python tutorials end in a subprocess wrapper around adb shell input tap. This adb Python tutorial for Macro Automation Studio (MAS) takes a different route: the app owns adb, and your script talks to the app through the mas package. This guide goes from a fresh project to a script that opens an app, searches for a term, scrolls and saves a screenshot. It is for Python developers who want to drive BlueStacks, LDPlayer, MuMu Player or MEmu without writing adb calls.
Before you start
- MAS is installed and you are signed in with the trial or a plan. See Install MAS.
- An emulator is running with ADB enabled and is added as a device in Device Groups. See Devices.
- You know basic Python. Nothing else is installed by you: MAS ships its own Python 3.13 environment with
masinside.
How the pieces fit
MAS starts your script as python -u -m src.app with the project folder as the working directory. It passes the connection details in environment variables (MAS_RPC_PORT, MAS_RPC_HOST, MAS_DEVICE_ID, MAS_SESSION_TOKEN). Every mas.* call becomes a JSON-RPC 2.0 request to the app. The app runs the adb command, the template search or the OCR, and sends the result back. The device is bound before your first line runs, so there is no connect() call and no serial to manage. See Concepts.
Create the project
- Open Macros and click Create New Project.
- Choose Code-Based, set Target Device to mobile, name the project and click Create Project.
- Open the project. The Code Editor shows
src/app.py. - Pick the emulator in the device selector and click Run (F5) whenever you want to try a snippet. Stop is Shift+F5.
Screen size and coordinates
Every coordinate you pass to the SDK is a pixel offset from the top-left corner of the device screen, in the device’s own resolution. The size of the emulator window on your monitor does not matter.
import mas
size = mas.get_screen_size()
print(f"Screen: {size.width}x{size.height}")
center = (size.width // 2, size.height // 2)
mas.click(*center)get_screen_size() returns a ScreenSize with width and height. get_device_info() returns the same plus the device name, type and connected flag. When a script must survive a resolution change, compute positions as fractions of the size, as the example at the end does. Template images and OCR regions do not survive it; see the image recognition guide.
Take a Python adb screenshot
import base64
import mas
shot = mas.take_screenshot()
print(shot.width, shot.height, shot.timestamp)
with open("screen.png", "wb") as f:
f.write(base64.b64decode(shot.base64))take_screenshot() returns a Screenshot: base64 holds a PNG, width and height are its pixel size, and timestamp is an ISO 8601 string. The file above lands in the project folder because that is the working directory. Pass the same object to several find_object calls with screenshot=shot and they all search one frame instead of capturing again.
Tap
mas.click(270, 800) # tap, then wait 1000 ms
mas.click(270, 800, delay_ms=300) # shorter pause for tight loopsclick(x, y, delay_ms=1000) taps once and waits delay_ms afterwards so the app can react. There is no hold duration on a tap; for a long press use key_press with duration_ms.
Swipe
mas.swipe((270, 750), (270, 300), duration_ms=400) # scroll a list down: drag from lower to upper
mas.swipe((100, 500), (400, 500), duration_ms=1000) # drag and drop: slow and deliberateswipe(from_coords, to_coords, duration_ms=1000) takes two (x, y) tuples. A short duration flicks; a long one drags. Pinch gestures are separate calls: zoom_in() and zoom_out() default to the screen centre with percent=50.
Type
mas.click(270, 120, delay_ms=500) # focus the field first
mas.input_text("hello world")
mas.input_text("new value", clear=True) # replace what is thereinput_text(text, delay_ms=0, clear=False) types into the focused field, so tap the field first. clear=True moves the cursor to the end and deletes the existing characters before typing.
Press keys
from mas import KeyCode
mas.key_press(KeyCode.BACK)
mas.key_press(KeyCode.HOME)
mas.key_press(KeyCode.ENTER)
mas.key_press(KeyCode.DELETE, repeat=10) # ten presses in one command
mas.key_press(KeyCode.POWER, duration_ms=3000) # long presskey_press(key_code, modifiers=None, duration_ms=100, repeat=1) sends an Android key event. A duration_ms of 500 or more becomes a long press; the exact number of milliseconds beyond that is not honoured. repeat runs from 1 to 100 and is much faster than a Python loop. KeyCode also has the D-pad, volume, media and number keys.
Open and check apps
mas.open_app("com.android.settings", timeout_ms=5000)
print(mas.get_current_app()) # package name in front
print(mas.is_app_focused("com.android.settings")) # True or False
if mas.get_app_state("com.android.chrome") == mas.NOT_RUNNING:
mas.open_app("com.android.chrome")
mas.close_app("com.android.settings")open_app(package_name, timeout_ms=2000) launches by package name and waits timeout_ms. To learn a package name, open the app by hand and print mas.get_current_app(). get_app_state returns NOT_INSTALLED, NOT_RUNNING, RUNNING_IN_BACKGROUND_SUSPENDED, RUNNING_IN_BACKGROUND or RUNNING_IN_FOREGROUND. close_app force-stops the package.
A complete script
The script opens Android Settings, searches for a term, scrolls the results, saves a screenshot and checks with OCR that the term is on screen. The search box is a template you crop in Asset Lab; replace the ID with yours. The swipe uses fractions of the screen size so it works at any resolution.
import base64
import sys
import time
import mas
from mas import KeyCode
PACKAGE = "com.android.settings"
TERM = "Display"
images = mas.images({"search_box": 301})
def main():
size = mas.get_screen_size()
mas.log(f"Screen {size.width}x{size.height}")
mas.open_app(PACKAGE, timeout_ms=5000)
if not mas.is_app_focused(PACKAGE):
mas.log(f"{PACKAGE} did not come to the front", level="error")
sys.exit(2)
box = mas.find_object_retry(images.search_box, total_tries=3, time_sleep=2.0)
if box is None:
mas.log("Search box not found; crop it in Asset Lab", level="error")
sys.exit(2)
mas.click(box.x, box.y, delay_ms=800)
mas.input_text(TERM, clear=True)
mas.key_press(KeyCode.ENTER)
time.sleep(2)
x = size.width // 2
mas.swipe((x, int(size.height * 0.75)), (x, int(size.height * 0.35)), duration_ms=600)
time.sleep(1)
shot = mas.take_screenshot()
with open("search_results.png", "wb") as f:
f.write(base64.b64decode(shot.base64))
mas.log(f"Saved search_results.png ({shot.width}x{shot.height})")
text = mas.read_text(screenshot=shot, psm=11)
mas.log(f"Term on screen: {TERM.lower() in text.text.lower()}")
mas.key_press(KeyCode.HOME)
if __name__ == "__main__":
main()read_text with psm=11 reads sparse text across the whole frame, which suits a results list. The OCR guide covers regions and modes for reading one counter. If you would rather describe the task than write it, MAS Agent writes this kind of script for you.
Where adb fits in this Python tutorial
You never call adb, but it is doing the work underneath.
- Binary. The installer ships adb. On Windows it sits under
C:\ProgramData\MacroAutomationStudio\3rdparty, with a built-in copy as a fallback. On a Mac it sits inside the app bundle, with Homebrew’s adb as the fallback. Nothing goes on your PATH. The Install page has the details. - Connection. Emulators expose adb on a local TCP port. When you click Start or Run, MAS runs
adb connect 127.0.0.1:<port>for the port on the device card and keeps that session for the run. - Input.
clickbecomesadb shell input tap,swipebecomesadb shell input touchscreen swipe,input_textbecomesadb shell input text, andkey_pressbecomesadb shell input keyevent. Pinch gestures are written straight to the emulator’s touch input device. - Screen.
take_screenshotand everyfind_objectcapture withadb exec-out screencap -p, the binary-safe form that does not mangle the PNG on Windows.get_screen_sizereadswm size;get_current_appreads the window manager. - Apps.
open_appfires the launcher intent throughmonkeyand falls back toam start;close_apprunsam force-stop. - Servers. MAS runs its own adb server. If a second adb build on your PATH starts its own, the two replace each other and your device drops to Connecting. The ADB troubleshooting page lists the fixes, port by port.
Because the app does all of this, a script written on BlueStacks runs unchanged on LDPlayer, on a cloud device, or on your own phone over a local port.
What can go wrong
Could not discover RPC port
The script was started from a terminal instead of from MAS, so the environment variables are missing. Run it with Run in the Code Editor or from a device card.
Taps land in the wrong place
You are using window pixels instead of device pixels, or the emulator’s resolution changed. Print mas.get_screen_size() and compare it with the coordinates you pass. Coordinates from Asset Lab are already device pixels.
input_text typed nothing
No field had focus. Tap the field with click and give it delay_ms=500 before typing. Some apps open a keyboard that covers the field; key_press(KeyCode.BACK) closes it after typing.
Device not found or offline
ADB is off in the emulator, the port on the device card is wrong, or another adb server took over. Follow ADB troubleshooting.
If the app you drive is a game, treat it with the same care as any other automated account. No automation tool is 100% risk-free, so automate responsibly and at your own discretion.
Next steps
Related pages
Thanks. If something is wrong, tell us in Discord.
Questions? Ask in Discord