droidrun 0.3.1__py3-none-any.whl → 0.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- droidrun/__init__.py +7 -12
- droidrun/agent/codeact/codeact_agent.py +9 -7
- droidrun/agent/common/events.py +44 -1
- droidrun/agent/context/personas/__init__.py +2 -2
- droidrun/agent/context/personas/big_agent.py +96 -0
- droidrun/agent/context/personas/ui_expert.py +1 -0
- droidrun/agent/droid/droid_agent.py +63 -11
- droidrun/agent/droid/events.py +4 -0
- droidrun/agent/planner/planner_agent.py +2 -2
- droidrun/agent/utils/executer.py +10 -2
- droidrun/agent/utils/llm_picker.py +1 -0
- droidrun/agent/utils/trajectory.py +258 -11
- droidrun/cli/main.py +179 -86
- droidrun/macro/__init__.py +14 -0
- droidrun/macro/__main__.py +10 -0
- droidrun/macro/cli.py +228 -0
- droidrun/macro/replay.py +309 -0
- droidrun/portal.py +138 -0
- droidrun/telemetry/__init__.py +4 -0
- droidrun/telemetry/events.py +27 -0
- droidrun/telemetry/tracker.py +84 -0
- droidrun/tools/adb.py +704 -372
- droidrun/tools/ios.py +169 -166
- droidrun/tools/tools.py +70 -17
- {droidrun-0.3.1.dist-info → droidrun-0.3.3.dist-info}/METADATA +31 -29
- droidrun-0.3.3.dist-info/RECORD +54 -0
- droidrun/adb/__init__.py +0 -13
- droidrun/adb/device.py +0 -315
- droidrun/adb/manager.py +0 -93
- droidrun/adb/wrapper.py +0 -226
- droidrun/agent/context/personas/extractor.py +0 -52
- droidrun-0.3.1.dist-info/RECORD +0 -50
- {droidrun-0.3.1.dist-info → droidrun-0.3.3.dist-info}/WHEEL +0 -0
- {droidrun-0.3.1.dist-info → droidrun-0.3.3.dist-info}/entry_points.txt +0 -0
- {droidrun-0.3.1.dist-info → droidrun-0.3.3.dist-info}/licenses/LICENSE +0 -0
droidrun/cli/main.py
CHANGED
@@ -7,13 +7,23 @@ import click
|
|
7
7
|
import os
|
8
8
|
import logging
|
9
9
|
import warnings
|
10
|
+
from contextlib import nullcontext
|
10
11
|
from rich.console import Console
|
12
|
+
from adbutils import adb
|
11
13
|
from droidrun.agent.droid import DroidAgent
|
12
14
|
from droidrun.agent.utils.llm_picker import load_llm
|
13
|
-
from droidrun.adb import DeviceManager
|
14
15
|
from droidrun.tools import AdbTools, IOSTools
|
16
|
+
from droidrun.agent.context.personas import DEFAULT, BIG_AGENT
|
15
17
|
from functools import wraps
|
16
18
|
from droidrun.cli.logs import LogHandler
|
19
|
+
from droidrun.telemetry import print_telemetry_message
|
20
|
+
from droidrun.portal import (
|
21
|
+
download_portal_apk,
|
22
|
+
enable_portal_accessibility,
|
23
|
+
PORTAL_PACKAGE_NAME,
|
24
|
+
ping_portal,
|
25
|
+
)
|
26
|
+
from droidrun.macro.cli import macro_cli
|
17
27
|
|
18
28
|
# Suppress all warnings
|
19
29
|
warnings.filterwarnings("ignore")
|
@@ -21,7 +31,6 @@ os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
21
31
|
os.environ["GRPC_ENABLE_FORK_SUPPORT"] = "false"
|
22
32
|
|
23
33
|
console = Console()
|
24
|
-
device_manager = DeviceManager()
|
25
34
|
|
26
35
|
|
27
36
|
def configure_logging(goal: str, debug: bool):
|
@@ -30,16 +39,21 @@ def configure_logging(goal: str, debug: bool):
|
|
30
39
|
|
31
40
|
handler = LogHandler(goal)
|
32
41
|
handler.setFormatter(
|
33
|
-
logging.Formatter("%(levelname)s %(message)s", "%H:%M:%S")
|
42
|
+
logging.Formatter("%(levelname)s %(name)s %(message)s", "%H:%M:%S")
|
34
43
|
if debug
|
35
44
|
else logging.Formatter("%(message)s", "%H:%M:%S")
|
36
45
|
)
|
37
46
|
logger.addHandler(handler)
|
38
47
|
|
39
|
-
|
40
48
|
logger.setLevel(logging.DEBUG if debug else logging.INFO)
|
41
49
|
logger.propagate = False
|
42
50
|
|
51
|
+
if debug:
|
52
|
+
tools_logger = logging.getLogger("droidrun-tools")
|
53
|
+
tools_logger.addHandler(handler)
|
54
|
+
tools_logger.propagate = False
|
55
|
+
tools_logger.setLevel(logging.DEBUG if debug else logging.INFO)
|
56
|
+
|
43
57
|
return handler
|
44
58
|
|
45
59
|
|
@@ -64,9 +78,11 @@ async def run_command(
|
|
64
78
|
reasoning: bool,
|
65
79
|
reflection: bool,
|
66
80
|
tracing: bool,
|
67
|
-
debug: bool,
|
81
|
+
debug: bool,
|
82
|
+
use_tcp: bool,
|
68
83
|
save_trajectory: bool = False,
|
69
84
|
ios: bool = False,
|
85
|
+
allow_drag: bool = False,
|
70
86
|
**kwargs,
|
71
87
|
):
|
72
88
|
"""Run a command on your Android device using natural language."""
|
@@ -78,6 +94,7 @@ async def run_command(
|
|
78
94
|
with log_handler.render() as live:
|
79
95
|
try:
|
80
96
|
logger.info(f"🚀 Starting: {command}")
|
97
|
+
print_telemetry_message()
|
81
98
|
|
82
99
|
if not kwargs.get("temperature"):
|
83
100
|
kwargs["temperature"] = 0
|
@@ -87,23 +104,34 @@ async def run_command(
|
|
87
104
|
# Device setup
|
88
105
|
if device is None and not ios:
|
89
106
|
logger.info("🔍 Finding connected device...")
|
90
|
-
|
91
|
-
devices =
|
107
|
+
|
108
|
+
devices = adb.list()
|
92
109
|
if not devices:
|
93
110
|
raise ValueError("No connected devices found.")
|
94
111
|
device = devices[0].serial
|
95
112
|
logger.info(f"📱 Using device: {device}")
|
96
113
|
elif device is None and ios:
|
97
|
-
raise ValueError(
|
114
|
+
raise ValueError(
|
115
|
+
"iOS device not specified. Please specify the device base url (http://device-ip:6643) via --device"
|
116
|
+
)
|
98
117
|
else:
|
99
118
|
logger.info(f"📱 Using device: {device}")
|
100
119
|
|
101
|
-
tools = AdbTools(serial=device) if not ios else IOSTools(url=device)
|
120
|
+
tools = AdbTools(serial=device, use_tcp=use_tcp) if not ios else IOSTools(url=device)
|
121
|
+
# Set excluded tools based on CLI flags
|
122
|
+
excluded_tools = [] if allow_drag else ["drag"]
|
123
|
+
|
124
|
+
# Select personas based on --drag flag
|
125
|
+
personas = [BIG_AGENT] if allow_drag else [DEFAULT]
|
102
126
|
|
103
127
|
# LLM setup
|
104
128
|
log_handler.update_step("Initializing LLM...")
|
105
129
|
llm = load_llm(
|
106
|
-
provider_name=provider,
|
130
|
+
provider_name=provider,
|
131
|
+
model=model,
|
132
|
+
base_url=base_url,
|
133
|
+
api_base=api_base,
|
134
|
+
**kwargs,
|
107
135
|
)
|
108
136
|
logger.info(f"🧠 LLM ready: {provider}/{model}")
|
109
137
|
|
@@ -116,10 +144,13 @@ async def run_command(
|
|
116
144
|
if tracing:
|
117
145
|
logger.info("🔍 Tracing enabled")
|
118
146
|
|
147
|
+
|
119
148
|
droid_agent = DroidAgent(
|
120
149
|
goal=command,
|
121
150
|
llm=llm,
|
122
151
|
tools=tools,
|
152
|
+
personas=personas,
|
153
|
+
excluded_tools=excluded_tools,
|
123
154
|
max_steps=steps,
|
124
155
|
timeout=1000,
|
125
156
|
vision=vision,
|
@@ -127,7 +158,7 @@ async def run_command(
|
|
127
158
|
reflection=reflection,
|
128
159
|
enable_tracing=tracing,
|
129
160
|
debug=debug,
|
130
|
-
save_trajectories=save_trajectory
|
161
|
+
save_trajectories=save_trajectory,
|
131
162
|
)
|
132
163
|
|
133
164
|
logger.info("▶️ Starting agent execution...")
|
@@ -202,13 +233,19 @@ class DroidRunCLI(click.Group):
|
|
202
233
|
default=None,
|
203
234
|
)
|
204
235
|
@click.option(
|
205
|
-
"--vision",
|
236
|
+
"--vision",
|
237
|
+
is_flag=True,
|
238
|
+
help="Enable vision capabilites by using screenshots",
|
239
|
+
default=False,
|
206
240
|
)
|
207
241
|
@click.option(
|
208
242
|
"--reasoning", is_flag=True, help="Enable planning with reasoning", default=False
|
209
243
|
)
|
210
244
|
@click.option(
|
211
|
-
"--reflection",
|
245
|
+
"--reflection",
|
246
|
+
is_flag=True,
|
247
|
+
help="Enable reflection step for higher reasoning",
|
248
|
+
default=False,
|
212
249
|
)
|
213
250
|
@click.option(
|
214
251
|
"--tracing", is_flag=True, help="Enable Arize Phoenix tracing", default=False
|
@@ -216,6 +253,9 @@ class DroidRunCLI(click.Group):
|
|
216
253
|
@click.option(
|
217
254
|
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
218
255
|
)
|
256
|
+
@click.option(
|
257
|
+
"--use-tcp", is_flag=True, help="Use TCP communication for device control", default=False
|
258
|
+
)
|
219
259
|
@click.option(
|
220
260
|
"--save-trajectory",
|
221
261
|
is_flag=True,
|
@@ -236,6 +276,7 @@ def cli(
|
|
236
276
|
reflection: bool,
|
237
277
|
tracing: bool,
|
238
278
|
debug: bool,
|
279
|
+
use_tcp: bool,
|
239
280
|
save_trajectory: bool,
|
240
281
|
):
|
241
282
|
"""DroidRun - Control your Android device through LLM agents."""
|
@@ -271,13 +312,19 @@ def cli(
|
|
271
312
|
default=None,
|
272
313
|
)
|
273
314
|
@click.option(
|
274
|
-
"--vision",
|
315
|
+
"--vision",
|
316
|
+
is_flag=True,
|
317
|
+
help="Enable vision capabilites by using screenshots",
|
318
|
+
default=False,
|
275
319
|
)
|
276
320
|
@click.option(
|
277
321
|
"--reasoning", is_flag=True, help="Enable planning with reasoning", default=False
|
278
322
|
)
|
279
323
|
@click.option(
|
280
|
-
"--reflection",
|
324
|
+
"--reflection",
|
325
|
+
is_flag=True,
|
326
|
+
help="Enable reflection step for higher reasoning",
|
327
|
+
default=False,
|
281
328
|
)
|
282
329
|
@click.option(
|
283
330
|
"--tracing", is_flag=True, help="Enable Arize Phoenix tracing", default=False
|
@@ -285,6 +332,9 @@ def cli(
|
|
285
332
|
@click.option(
|
286
333
|
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
287
334
|
)
|
335
|
+
@click.option(
|
336
|
+
"--use-tcp", is_flag=True, help="Use TCP communication for device control", default=False
|
337
|
+
)
|
288
338
|
@click.option(
|
289
339
|
"--save-trajectory",
|
290
340
|
is_flag=True,
|
@@ -292,8 +342,13 @@ def cli(
|
|
292
342
|
default=False,
|
293
343
|
)
|
294
344
|
@click.option(
|
295
|
-
"--
|
345
|
+
"--drag",
|
346
|
+
"allow_drag",
|
347
|
+
is_flag=True,
|
348
|
+
help="Enable drag tool",
|
349
|
+
default=False,
|
296
350
|
)
|
351
|
+
@click.option("--ios", is_flag=True, help="Run on iOS device", default=False)
|
297
352
|
def run(
|
298
353
|
command: str,
|
299
354
|
device: str | None,
|
@@ -308,7 +363,9 @@ def run(
|
|
308
363
|
reflection: bool,
|
309
364
|
tracing: bool,
|
310
365
|
debug: bool,
|
366
|
+
use_tcp: bool,
|
311
367
|
save_trajectory: bool,
|
368
|
+
allow_drag: bool,
|
312
369
|
ios: bool,
|
313
370
|
):
|
314
371
|
"""Run a command on your Android device using natural language."""
|
@@ -326,18 +383,19 @@ def run(
|
|
326
383
|
reflection,
|
327
384
|
tracing,
|
328
385
|
debug,
|
386
|
+
use_tcp,
|
329
387
|
temperature=temperature,
|
330
388
|
save_trajectory=save_trajectory,
|
331
|
-
|
389
|
+
allow_drag=allow_drag,
|
390
|
+
ios=ios,
|
332
391
|
)
|
333
392
|
|
334
393
|
|
335
394
|
@cli.command()
|
336
|
-
|
337
|
-
async def devices():
|
395
|
+
def devices():
|
338
396
|
"""List connected Android devices."""
|
339
397
|
try:
|
340
|
-
devices =
|
398
|
+
devices = adb.list()
|
341
399
|
if not devices:
|
342
400
|
console.print("[yellow]No devices connected.[/]")
|
343
401
|
return
|
@@ -350,28 +408,25 @@ async def devices():
|
|
350
408
|
|
351
409
|
|
352
410
|
@cli.command()
|
353
|
-
@click.argument("
|
354
|
-
|
355
|
-
@coro
|
356
|
-
async def connect(ip_address: str, port: int):
|
411
|
+
@click.argument("serial")
|
412
|
+
def connect(serial: str):
|
357
413
|
"""Connect to a device over TCP/IP."""
|
358
414
|
try:
|
359
|
-
device =
|
360
|
-
if device:
|
361
|
-
console.print(f"[green]Successfully connected to {
|
415
|
+
device = adb.connect(serial)
|
416
|
+
if device.count("already connected"):
|
417
|
+
console.print(f"[green]Successfully connected to {serial}[/]")
|
362
418
|
else:
|
363
|
-
console.print(f"[red]Failed to connect to {
|
419
|
+
console.print(f"[red]Failed to connect to {serial}: {device}[/]")
|
364
420
|
except Exception as e:
|
365
421
|
console.print(f"[red]Error connecting to device: {e}[/]")
|
366
422
|
|
367
423
|
|
368
424
|
@cli.command()
|
369
425
|
@click.argument("serial")
|
370
|
-
|
371
|
-
async def disconnect(serial: str):
|
426
|
+
def disconnect(serial: str):
|
372
427
|
"""Disconnect from a device."""
|
373
428
|
try:
|
374
|
-
success =
|
429
|
+
success = adb.disconnect(serial, raise_error=True)
|
375
430
|
if success:
|
376
431
|
console.print(f"[green]Successfully disconnected from {serial}[/]")
|
377
432
|
else:
|
@@ -381,18 +436,16 @@ async def disconnect(serial: str):
|
|
381
436
|
|
382
437
|
|
383
438
|
@cli.command()
|
384
|
-
@click.option("--path", required=True, help="Path to the APK file to install")
|
385
439
|
@click.option("--device", "-d", help="Device serial number or IP address", default=None)
|
386
|
-
@
|
387
|
-
|
388
|
-
""
|
440
|
+
@click.option("--path", help="Path to the Droidrun Portal APK to install on the device. If not provided, the latest portal apk version will be downloaded and installed.", default=None)
|
441
|
+
@click.option(
|
442
|
+
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
443
|
+
)
|
444
|
+
def setup(path: str | None, device: str | None, debug: bool):
|
445
|
+
"""Install and enable the DroidRun Portal on a device."""
|
389
446
|
try:
|
390
|
-
if not os.path.exists(path):
|
391
|
-
console.print(f"[bold red]Error:[/] APK file not found at {path}")
|
392
|
-
return
|
393
|
-
|
394
447
|
if not device:
|
395
|
-
devices =
|
448
|
+
devices = adb.list()
|
396
449
|
if not devices:
|
397
450
|
console.print("[yellow]No devices connected.[/]")
|
398
451
|
return
|
@@ -400,72 +453,108 @@ async def setup(path: str, device: str | None):
|
|
400
453
|
device = devices[0].serial
|
401
454
|
console.print(f"[blue]Using device:[/] {device}")
|
402
455
|
|
403
|
-
device_obj =
|
456
|
+
device_obj = adb.device(device)
|
404
457
|
if not device_obj:
|
405
458
|
console.print(
|
406
459
|
f"[bold red]Error:[/] Could not get device object for {device}"
|
407
460
|
)
|
408
461
|
return
|
409
|
-
|
410
|
-
console.print(f"[bold blue]Step 1/2: Installing APK:[/] {path}")
|
411
|
-
result = await device_obj.install_app(path, False, True)
|
412
462
|
|
413
|
-
if
|
414
|
-
console.print(
|
415
|
-
|
463
|
+
if not path:
|
464
|
+
console.print("[bold blue]Downloading DroidRun Portal APK...[/]")
|
465
|
+
apk_context = download_portal_apk(debug)
|
416
466
|
else:
|
467
|
+
console.print(f"[bold blue]Using provided APK:[/] {path}")
|
468
|
+
apk_context = nullcontext(path)
|
469
|
+
|
470
|
+
with apk_context as apk_path:
|
471
|
+
if not os.path.exists(apk_path):
|
472
|
+
console.print(f"[bold red]Error:[/] APK file not found at {apk_path}")
|
473
|
+
return
|
474
|
+
|
475
|
+
console.print(f"[bold blue]Step 1/2: Installing APK:[/] {apk_path}")
|
476
|
+
try:
|
477
|
+
device_obj.install(apk_path, uninstall=True, flags=["-g"], silent=not debug)
|
478
|
+
except Exception as e:
|
479
|
+
console.print(f"[bold red]Installation failed:[/] {e}")
|
480
|
+
return
|
481
|
+
|
417
482
|
console.print(f"[bold green]Installation successful![/]")
|
418
483
|
|
419
|
-
|
484
|
+
console.print(f"[bold blue]Step 2/2: Enabling accessibility service[/]")
|
420
485
|
|
421
|
-
|
486
|
+
try:
|
487
|
+
enable_portal_accessibility(device_obj)
|
422
488
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
)
|
489
|
+
console.print("[green]Accessibility service enabled successfully![/]")
|
490
|
+
console.print(
|
491
|
+
"\n[bold green]Setup complete![/] The DroidRun Portal is now installed and ready to use."
|
492
|
+
)
|
428
493
|
|
429
|
-
|
430
|
-
|
431
|
-
|
494
|
+
except Exception as e:
|
495
|
+
console.print(
|
496
|
+
f"[yellow]Could not automatically enable accessibility service: {e}[/]"
|
497
|
+
)
|
498
|
+
console.print(
|
499
|
+
"[yellow]Opening accessibility settings for manual configuration...[/]"
|
500
|
+
)
|
501
|
+
|
502
|
+
device_obj.shell(
|
503
|
+
"am start -a android.settings.ACCESSIBILITY_SETTINGS"
|
504
|
+
)
|
505
|
+
|
506
|
+
console.print(
|
507
|
+
"\n[yellow]Please complete the following steps on your device:[/]"
|
508
|
+
)
|
509
|
+
console.print(
|
510
|
+
f"1. Find [bold]{PORTAL_PACKAGE_NAME}[/] in the accessibility services list"
|
511
|
+
)
|
512
|
+
console.print("2. Tap on the service name")
|
513
|
+
console.print(
|
514
|
+
"3. Toggle the switch to [bold]ON[/] to enable the service"
|
515
|
+
)
|
516
|
+
console.print("4. Accept any permission dialogs that appear")
|
517
|
+
|
518
|
+
console.print(
|
519
|
+
"\n[bold green]APK installation complete![/] Please manually enable the accessibility service using the steps above."
|
520
|
+
)
|
432
521
|
|
433
|
-
|
434
|
-
|
435
|
-
"\n[bold green]Setup complete![/] The DroidRun Portal is now installed and ready to use."
|
436
|
-
)
|
522
|
+
except Exception as e:
|
523
|
+
console.print(f"[bold red]Error:[/] {e}")
|
437
524
|
|
438
|
-
|
439
|
-
|
440
|
-
f"[yellow]Could not automatically enable accessibility service: {e}[/]"
|
441
|
-
)
|
442
|
-
console.print(
|
443
|
-
"[yellow]Opening accessibility settings for manual configuration...[/]"
|
444
|
-
)
|
525
|
+
if debug:
|
526
|
+
import traceback
|
445
527
|
|
446
|
-
|
447
|
-
device, "am start -a android.settings.ACCESSIBILITY_SETTINGS"
|
448
|
-
)
|
528
|
+
traceback.print_exc()
|
449
529
|
|
450
|
-
console.print(
|
451
|
-
"\n[yellow]Please complete the following steps on your device:[/]"
|
452
|
-
)
|
453
|
-
console.print(
|
454
|
-
f"1. Find [bold]{package}[/] in the accessibility services list"
|
455
|
-
)
|
456
|
-
console.print("2. Tap on the service name")
|
457
|
-
console.print("3. Toggle the switch to [bold]ON[/] to enable the service")
|
458
|
-
console.print("4. Accept any permission dialogs that appear")
|
459
530
|
|
460
|
-
|
461
|
-
|
462
|
-
|
531
|
+
@cli.command()
|
532
|
+
@click.option("--device", "-d", help="Device serial number or IP address", default=None)
|
533
|
+
@click.option(
|
534
|
+
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
535
|
+
)
|
536
|
+
def ping(device: str | None, debug: bool):
|
537
|
+
"""Ping a device to check if it is ready and accessible."""
|
538
|
+
try:
|
539
|
+
device_obj = adb.device(device)
|
540
|
+
if not device_obj:
|
541
|
+
console.print(f"[bold red]Error:[/] Could not find device {device}")
|
542
|
+
return
|
463
543
|
|
544
|
+
ping_portal(device_obj, debug)
|
545
|
+
console.print(
|
546
|
+
"[bold green]Portal is installed and accessible. You're good to go![/]"
|
547
|
+
)
|
464
548
|
except Exception as e:
|
465
549
|
console.print(f"[bold red]Error:[/] {e}")
|
466
|
-
|
550
|
+
if debug:
|
551
|
+
import traceback
|
552
|
+
|
553
|
+
traceback.print_exc()
|
554
|
+
|
467
555
|
|
468
|
-
|
556
|
+
# Add macro commands as a subgroup
|
557
|
+
cli.add_command(macro_cli, name="macro")
|
469
558
|
|
470
559
|
|
471
560
|
if __name__ == "__main__":
|
@@ -481,9 +570,11 @@ if __name__ == "__main__":
|
|
481
570
|
reflection = False
|
482
571
|
tracing = True
|
483
572
|
debug = True
|
573
|
+
use_tcp = True
|
484
574
|
base_url = None
|
485
575
|
api_base = None
|
486
576
|
ios = False
|
577
|
+
allow_drag = False
|
487
578
|
run_command(
|
488
579
|
command=command,
|
489
580
|
device=device,
|
@@ -496,8 +587,10 @@ if __name__ == "__main__":
|
|
496
587
|
reflection=reflection,
|
497
588
|
tracing=tracing,
|
498
589
|
debug=debug,
|
590
|
+
use_tcp=use_tcp,
|
499
591
|
base_url=base_url,
|
500
592
|
api_base=api_base,
|
501
593
|
api_key=api_key,
|
502
|
-
|
594
|
+
allow_drag=allow_drag,
|
595
|
+
ios=ios,
|
503
596
|
)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"""
|
2
|
+
DroidRun Macro Module - Record and replay UI automation sequences.
|
3
|
+
|
4
|
+
This module provides functionality to replay macro sequences that were
|
5
|
+
recorded during DroidAgent execution.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .replay import MacroPlayer, replay_macro_file, replay_macro_folder
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"MacroPlayer",
|
12
|
+
"replay_macro_file",
|
13
|
+
"replay_macro_folder"
|
14
|
+
]
|