Skip to content

Device Methods

Complete API reference for marketing_system.bots.common.adb.Device.

from marketing_system.bots.common.adb import Device
dev = Device() # Auto-detect (single device only)
dev = Device("L9AIB7603188953") # Specific device by serial

The Device stores the serial and uses it for all subsequent adb -s <serial> commands.

Execute a raw ADB command and return stdout as string.

output = dev.adb("shell", "wm", "size")
# "Physical size: 1080x2340"
dev.adb("push", "/local/video.mp4", "/sdcard/video.mp4")

Execute ADB with live output to stdout (useful for progress bars during adb push).

Single tap at screen coordinates. Waits delay seconds after tapping.

dev.tap(540, 1200)
dev.tap(540, 1200, delay=0.3) # shorter wait

Swipe from (x1,y1) to (x2,y2) over ms milliseconds.

dev.swipe(540, 1800, 540, 600) # swipe up, 500ms
dev.swipe(540, 1800, 540, 600, ms=300) # faster swipe

Press the BACK key event.

Press the ENTER key event.

long_press(x, y, duration_ms=1000, delay=0.5)

Section titled “long_press(x, y, duration_ms=1000, delay=0.5)”

Long press via swipe to the same point with the given duration.

dev.long_press(540, 1200, duration_ms=2000) # 2-second long press

Type ASCII text via adb shell input text. Does not support emoji or unicode characters.

dev.type_text("hello world")

pinch_in(cx, cy, start_dist, end_dist, duration_ms)

Section titled “pinch_in(cx, cy, start_dist, end_dist, duration_ms)”

Pinch-to-zoom in (two fingers moving inward).

pinch_out(cx, cy, start_dist, end_dist, duration_ms)

Section titled “pinch_out(cx, cy, start_dist, end_dist, duration_ms)”

Pinch-to-zoom out (two fingers moving outward).

Note: Due to ADB input swipe limitations, only one finger moves at a time.

Type emoji, CJK, and other unicode characters via ADBKeyboard IME broadcast.

Flow: enable ADBKeyboard -> set as default IME -> broadcast text -> restore Gboard -> disable ADBKeyboard.

dev.type_unicode("Hello! Nice to see you")

Requires ADBKeyboard APK installed on the device.

Get clipboard text content (requires Android API 29+).

text = dev.clipboard_get()

Set clipboard text content.

dev.clipboard_set("text to paste")
# Then paste via: dev.adb("shell", "input", "keyevent", "279")

Expand the notification shade.

Collapse the notification shade.

Dump XML of the notification shade (call open_notifications() first).

dev.open_notifications()
xml = dev.read_notifications()
for node in dev.nodes(xml):
print(dev.node_text(node))
dev.close_notifications()

Open notifications and tap “Clear all”.

Open a specific Android settings page.

dev.open_settings("WIFI_SETTINGS")
dev.open_settings("BLUETOOTH_SETTINGS")
dev.open_settings("DISPLAY_SETTINGS")
dev.open_settings("LOCATION_SOURCE_SETTINGS")

Tap with Gaussian coordinate jitter (sigma=8px).

stealth_swipe(x1, y1, x2, y2, ms=None, delay=0.5)

Section titled “stealth_swipe(x1, y1, x2, y2, ms=None, delay=0.5)”

Swipe with variable speed (300-700ms) and endpoint jitter (sigma=5px).

stealth_type(text, delay_range=(0.05, 0.2))

Section titled “stealth_type(text, delay_range=(0.05, 0.2))”

Character-by-character typing with random inter-keystroke delays.

dev.stealth_type("hello", delay_range=(0.1, 0.4)) # slower typing

Get the current UI hierarchy as XML string. Tries three sources in order:

  1. Portal HTTP (~33ms) — requires Droidrun Portal APK
  2. Portal content provider (~1.2s)
  3. uiautomator dump (~2.0s) — always available
xml = dev.dump_xml()

Get UI hierarchy as JSON via Droidrun Portal (alternative to XML).

Convert bounds string [x1,y1][x2,y2] to center (cx, cy).

cx, cy = dev.bounds_center("[0,100][200,300]")
# (100, 200)

find_bounds(xml, text=None, resource_id=None, content_desc=None)

Section titled “find_bounds(xml, text=None, resource_id=None, content_desc=None)”

Find an element’s bounds string by attribute. Returns the bounds string or None.

bounds = dev.find_bounds(xml, text="OK")
bounds = dev.find_bounds(xml, resource_id="com.app:id/btn")
if bounds:
dev.tap(*dev.bounds_center(bounds))

Find element by text and tap its center. Falls back to fallback_xy if not found.

Poll dump_xml() until the given text appears on screen, or timeout.

xml = dev.wait_for("Profile", timeout=10)

Extract all <node> strings from the XML hierarchy.

for node in dev.nodes(xml):
print(dev.node_text(node), dev.node_rid(node))

node_text(node) / node_rid(node) / node_content_desc(node)

Section titled “node_text(node) / node_rid(node) / node_content_desc(node)”

Extract text, resource-id, or content-desc attribute from a node string.

Parse bounds from node string, returns (x1, y1, x2, y2) tuple.

Get center point from bounds tuple: (cx, cy).

Filter nodes by resource_id and/or text. Returns list of matching node strings.

Tap the center of a node.

Force-stop TikTok, relaunch, dismiss overlays (including invisible draft overlay).

Restart TikTok, tap Profile tab, wait for profile screen indicators.

Navigate from profile to the Drafts grid. Returns None if 0 drafts exist.

Open search, type query, press Enter, navigate to specified tab (with retries).

Classify the current screen. Returns one of: home, search_input, search_results, users_tab, filters_panel, unknown.

Auto-dismiss known TikTok popups using the 3-tier system: specific patterns (10 known) -> generic dismiss words -> invisible overlay detection.

Return installed TikTok version, warn if it does not match the known version (44.3.3).

Return versionName for any installed package.

Open Play Store page for the app and tap Update.