adb_utils 0.4.7
adb_utils: ^0.4.7 copied to clipboard
A Dart library for interacting with ADB (Android Debug Bridge) server and devices.
adb_utils #
Dart library for interacting with the ADB (Android Debug Bridge) server and Android devices through the native socket protocol. Inspired by Python's openatx/adbutils.
This library communicates directly with the local ADB server over TCP (127.0.0.1:5037), avoiding repeated, inefficient invocations of the adb executable.
Features #
- Device management: list, connect, disconnect, and track device state.
- Shell execution: run commands and capture output and exit codes (
returnCode). - Installation and apps: install/uninstall APKs through byte streaming and retrieve advanced app information (
dumpsys). - Interaction: capture native screenshot bytes and simulate taps, swipes, and key events.
- Sockets and forwarding: create local/reverse port forwards and raw socket connections to device services.
- Native SYNC file transfer: push, read, inspect, and list files through ADB SYNC.
- Phantom (UiAutomator agent): orchestrate instrumented-agent startup and send JSON actions over TCP.
- Phantom video stream (H.264): consume a raw video stream (NAL units) through dynamically discovered ports.
- HTML test report: automatically generate
report.htmlafterdart test, including detailed failure evidence.
Getting Started #
Requirements #
- Dart SDK
^3.11.4 - ADB (Android Debug Bridge) installed and its server running (
adb start-server).
Installation #
Add adb_utils to your pubspec.yaml:
dependencies:
adb_utils: ^0.4.7
Then install it with:
dart pub get
Tests #
# Runs every available test through the aggregate tag
dart test --tags all_possible
Usage #
Here is a quick example of connecting to ADB and reading a device model:
import 'package:adb_utils/adb_utils.dart';
void main() async {
final adb = AdbClient();
// List all connected devices
for (final d in await adb.deviceList()) {
print('${d.serial} - ${d.state.name} - ${d.model ?? ''}');
}
// Get a device (throws when none are connected)
final device = await adb.device();
// Read properties through `getprop`, with optional native caching
print(await device.prop.model); // e.g. "Pixel 7"
// Execute a shell command
print(await device.shell('uname -r')); // kernel version
}
Detailed API #
AdbClient #
AdbClient is the library entry point. It represents the connection to the ADB server on your machine.
final adb = AdbClient(
host: '127.0.0.1',
port: 5037,
socketTimeout: Duration(seconds: 10),
);
// Get a device by serial or transport ID
final d1 = await adb.device(serial: '8d1f93be');
final d2 = await adb.device(transportId: 24);
// Connect over TCP (equivalent to adb connect)
await adb.connect('192.168.1.100:5555');
// Track additions, state changes, and removals in real time
await for (final event in adb.trackDevices()) {
print('${event.serial} conectado? ${event.present}');
}
AdbDevice #
AdbDevice represents a specific Android device.
Shell and properties
String out = await d.shell('ls -l /sdcard');
// shell2 returns a ShellResult with the exit code (returnCode).
ShellResult result = await d.shell2('ls /root');
print(result.returnCode); // 0 = success; any other value = error.
// Convenient shortcuts for properties (getprop)
print(await d.prop.sdkVersion); // e.g. "33"
Display information and screenshots
var (width, height) = await d.windowSize();
bool isScreenOn = await d.isScreenOn();
// Capture the screen directly as binary data (without saving a device file)
Uint8List png = await d.screenshot();
Taps and keys
await d.click(540, 960);
await d.swipe(100, 500, 100, 200, 0.3); // start X,Y -> end X,Y in 0.3s
await d.sendKeys('Hello World!');
await d.keyEvent('KEYCODE_HOME');
APK and package management
// Install an APK through socket streaming (no manual push required)
await d.install(
apkPath: 'build/app.apk',
replace: true,
grantAllPermissions: true,
);
await d.uninstall(packageName: 'com.example.app');
// Get the app currently displayed on screen
ForegroundAppInfo app = await d.appCurrent();
File Transfer (AdbSync) #
Operations through the native ADB SYNC transfer protocol (adb.sync).
// PUSH (local -> Android)
await d.sync.push('/local/path/file.txt', '/sdcard/file.txt');
await d.sync.push(Uint8List.fromList([...]), '/sdcard/data.bin'); // directly from memory
// PULL (Android -> local)
await d.sync.pull('/sdcard/config.json', '/local/path/config.json'); // writes directly to disk (streaming)
// READ (Android -> local memory)
Uint8List bytes = await d.sync.readBytes('/sdcard/config.json');
String texto = await d.sync.readText('/sdcard/config.json');
// STAT (read size and modification information)
Map<String, int> info = await d.sync.stat('/sdcard/file.txt');
// returns data such as: {'mode': 33188, 'size': 1024, 'mtime': 16843453}
UiAutomator Agent (PhantomClient) #
For UI automation through the instrumented agent:
import 'package:adb_utils/adb_utils.dart';
final adb = AdbClient();
final d = await adb.device();
final phantom = d.phantom;
await phantom.startAgent();
final xml = await phantom.dumpWindow();
final hierarchy = await phantom.dumpWindowHierarchy();
final clicked = await phantom.clickByText('Entrar');
final videoStream = await phantom.startVideoStream();
await for (final nalChunk in videoStream) {
// `nalChunk` contains raw H.264 bytes (NAL units).
// Send it to your decoder/player.
}
print(xml);
print('rotation => ${hierarchy.rotation}');
print('clickByText => $clicked');
startAgent pushes APKs to /data/local/tmp when needed, installs them with
pm install -t -r, force-stops the old agent, starts instrumentation with the
explicit PhantomServer#startServer class, reads the JSON handshake at
context.filesDir/phantom_ports.json, and discovers dynamic
command_port/video_port values through run-as com.example.phantom_agent.
The client then reserves free host ports and automatically applies:
adb forward tcp:<host_command_port> tcp:<device_command_port>adb forward tcp:<host_video_port> tcp:<device_video_port>
startVideoStream() uses the dynamic mapping resolved by startAgent() and
returns a continuous Stream<List<int>> containing raw H.264 video (NAL
units), without hard-coded ports.
Automatic HTML Report After dart test #
When you run:
dart test
the project automatically generates a report.html file in the root containing:
- a summary panel (total, passed, failed, total duration);
- a table of every executed test;
- highlighted failure evidence with a dark-red background, monospace
<pre><code>block, and horizontal scrolling for long messages.
Available reporting APIs #
Reporting APIs are also exported by the main barrel:
import 'package:adb_utils/adb_utils.dart';
final result = TestResult(
nome: 'Connect through socket',
categoria: 'Connectivity',
duracao: const Duration(milliseconds: 120),
passou: true,
);
final reporter = HtmlReporter(outputPath: 'report.html');
await reporter.writeReport([result]);
Sequential execution wrapper (TestRunner) #
For custom scenarios outside package:test, use:
final runner = TestRunner();
await runner.runTest('Name', 'Category', () async {
// asynchronous test logic
});
await HtmlReporter(outputPath: 'report.html').writeReport(runner.resultados);
Note: temporary report aggregation files are written to
logs/test-report/.
Error Handling #
The library represents unsuccessful responses with three Exception-based types in exceptions.dart:
| Exception | Description |
|---|---|
AdbError |
The ADB server returned FAIL or a malformed protocol response. |
AdbTimeout |
The socket reached its timeout or a shell connection lost access. |
AdbInstallError |
An installation (install) session reported a specific error. |
License #
This project is distributed under the MIT License.