404
Page not found :(
The requested page could not be found.
diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..259d25b8d --- /dev/null +++ b/404.html @@ -0,0 +1 @@ +
Page not found :(
The requested page could not be found.
Marks request as dependant on features Doesn't mean it's a strong dependency, see specific request for more information
Mitigation of running JDK 9 code on JRE 8
java.lang.NoSuchMethodError: java.nio.ByteBuffer.xxx()Ljava/nio/ByteBuffer;
Copies up to limit bytes into transformer using buffer. If limit is null - copy until EOF
TODO: rewrite Assumes buffer hasArray == true
Mitigation of running JDK 9 code on JRE 8
TODO: rewrite Assumes buffer hasArray == true
Copies up to limit bytes into transformer using buffer. If limit is null - copy until EOF
Reads file using passed coroutineContext and pushes updates of predefined structure using channel
Reads file using passed coroutineContext and pushes updates of predefined structure using channel
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Called after the initial OKAY confirmation
Called after each readElement
Called after the initial OKAY confirmation
Called after each readElement
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Note: the following messages are expected to be quite stable from emulator. Emulator console will send the following message upon connection:
Android Console: Authentication required Android Console: type 'auth ' to authenticate Android Console: you can find your in '//.emulator_console_auth_token' OK\r\n
and the following after authentication: Android Console: type 'help' for a list of commands OK\r\n
So we try to search and skip first two "OK\r\n", return the rest.
use help
to list all available commands, may be emulator-dependant
usually localhost since emulator port is binded to the loopback interface only
console port of the emulator. if your device is emulator-5554, port is 5554, emulator-5556 - 5556 and so on
authentication token is required for connecting to emulator. If null, will be read from $HOME/.emulator_console_auth_token. If you want to remove the auth completely - $HOME/.emulator_console_auth_token file should be empty
by default all responses contain the emulator auth output that is unnecessary. If you want the output to include everything the emulator returns - set this to false
This request is completely different to other requests: it connects directly to emulator instead of adb server For simplicity it can be used in the same way as adb server requests and shares the socket creation logic (and hence the coroutine context) with other requests
use help
to list all available commands, may be emulator-dependant
usually localhost since emulator port is binded to the loopback interface only
console port of the emulator. if your device is emulator-5554, port is 5554, emulator-5556 - 5556 and so on
authentication token is required for connecting to emulator. If null, will be read from $HOME/.emulator_console_auth_token. If you want to remove the auth completely - $HOME/.emulator_console_auth_token file should be empty
by default all responses contain the emulator auth output that is unnecessary. If you want the output to include everything the emulator returns - set this to false
This request is completely different to other requests: it connects directly to emulator instead of adb server For simplicity it can be used in the same way as adb server requests and shares the socket creation logic (and hence the coroutine context) with other requests
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Optionally returns a local TCP port that is occupied now if using LocalTcpPortSpec without any parameters
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Default value 0 will allocate a random port that will be returned by the request
Optionally returns a local TCP port that is occupied now if using LocalTcpPortSpec without any parameters
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
TYPE_INT_ARGB buffered image
consider reusing the buffer between screencapture requests to reduce heap memory pressure
reuse the color models required for image conversion
If you reuse the adapter per device - you automatically recycle the internal buffer
consider reusing the buffer between screencapture requests to reduce heap memory pressure
reuse the color models required for image conversion
If you reuse the adapter per device - you automatically recycle the internal buffer
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
check if mdns discovery is available
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Connects a remote device
Disconnects a device previously connected using ConnectDeviceRequest
target device that will be disconnected. if null then disconnects all devices
Executes the command and provides the channel as the input to the command. Does not return anything
://android.googlesource.com/platform/system/core/+/refs/heads/master/adb/adb.h#62
Only supports host target
Pairs adb server with device over WiFi connection See https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
target device in the form of host:port, port is optional
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
default behaviour depends on the target serial that will be specified during execution
This request is quite tricky to use since the target of the request varies with the reconnection target
If you don't specify anything in reconnectTarget then it's treated as find the first available device
and reconnect If you specify Device target then you have to provide the target either here or via serial during execution If you use Offline then you have to use the host target only
default behaviour depends on the target serial that will be specified during execution
Connects a remote device
Disconnects a device previously connected using ConnectDeviceRequest
Executes the command and provides the channel as the input to the command. Does not return anything
Only supports host target
Pairs adb server with device over WiFi connection See https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
This request is quite tricky to use since the target of the request varies with the reconnection target
Android 21+
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Android 21+
Push one or more packages to the device and install them atomically
Requires Feature.CMD support on the target device Even if CMD is supported, there is no promise that cmd package install --multi-package
is available: there were version that didn't have this mode
Optionally uses Feature.ABB_EXEC Support for Feature.APEX is required for installing apex packages
Descendants should override this method to map the response to appropriate output
If both CMD and ABB_EXEC are missing, falls back to exec:pm
pre-KitKat
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
This request requires either Feature.CMD or Feature.ABB_EXEC support on the target device Additionally, support for Feature.APEX is required for installing apex packages
keep the data and cache directories around after package removal
There is no way to remove the remaining data. You will have to reinstall the application with the same signature, and fully uninstall it.
Descendants should override this method to map the response to appropriate output
Push one or more packages to the device and install them atomically
If both CMD and ABB_EXEC are missing, falls back to exec:pm
pre-KitKat
This request requires either Feature.CMD or Feature.ABB_EXEC support on the target device Additionally, support for Feature.APEX is required for installing apex packages
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
Doesn't work with SerialTarget, have to use the serial as a parameter for the execute method
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Doesn't work with SerialTarget, have to use the serial as a parameter for the execute method
Doesn't work with SerialTarget, have to use the serial as a parameter for the execute method
On some devices, this might not return the actual port if you're passing tcp:0
Doesn't work with SerialTarget, have to use the serial as a parameter for the execute method
Doesn't work with SerialTarget, have to use the serial as a parameter for the execute method
Doesn't work with SerialTarget, have to use the serial as a parameter for the execute method
Doesn't work with SerialTarget, have to use the serial as a parameter for the execute method
On some devices, this might not return the actual port if you're passing tcp:0
Disables or re-enables dm-verity checking on userdebug builds
Returns a string response of executing the command. One possible value is "verity cannot be disabled/enabled - USER build" when the device is not a debug build
Called after the initial OKAY confirmation
Called after each readElement
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
shell v1 service doesn't support exit codes we append an echo $?
at the end to print the exit code as well and parse it
Descendants should override this method to map the response to appropriate output
shell v1 service doesn't support exit codes we append an echo $?
at the end to print the exit code as well and parse it
Optionally send a message The transport connection is not available at this point
Called after the initial OKAY confirmation
Handles stdin
Called after the initial OKAY confirmation
Handles stdin
Close subprocess stdin if possible
Indicates an invalid or unknown packet
Window size change (an ASCII version of struct winsize)
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Descendants should override this method to map the response to appropriate output
shell v2 service required for this request
shell v2 service required for this request
if you don't specify your context then you'll have no control over the wait for file to finish writing
: closing the channel doesn't close the underlying resources
if you don't specify your context then you'll have no control over the wait for file to finish writing
: closing the channel doesn't close the underlying resources
Called after the initial OKAY confirmation
Called after each readElement
Called after the initial OKAY confirmation
Called after each readElement
if you don't specify your context then you'll have no control over the wait for file to finish writing
: closing the channel doesn't close the underlying resources
if you don't specify your context then you'll have no control over the wait for file to finish writing
: closing the channel doesn't close the underlying resources
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
if you don't specify your context then you'll have no control over the wait for file to finish writing
: closing the channel doesn't close the underlying resources
if you don't specify your context then you'll have no control over the wait for file to finish writing
: closing the channel doesn't close the underlying resources
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
if you don't specify your context then you'll have no control over the wait for file to finish writing
: closing the channel doesn't close the underlying resources
if you don't specify your context then you'll have no control over the wait for file to finish writing
: closing the channel doesn't close the underlying resources
if true, requires SENDRECV_V2_DRY_RUN_SEND
Some requests ignore the initial OKAY/FAIL response and instead stream the actual response To implement these we allow overriding this method
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Descendants should override this method to map the response to appropriate output
Descendants should override this method to map the response to appropriate output
can be a file or a directory
true if successful, false if not. false can be a partial success: some files might be pulled
Notes:
Doesn't handle file links
Destination doesn't have to exist
If source is a directory and the destination is an existing directory -> a subdirectory will be created
All features are optional
can be a file or a directory
true if successful, false if not. false can be a partial success: some files might be pulled
Notes:
Doesn't handle file links
Destination doesn't have to exist
If source is a directory and the destination is an existing directory -> a subdirectory will be created
All features are optional
can be a file or a directory
Notes:
Notes:
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
The fully-qualified Java package name for one of the packages in the test application. Any test case class that uses this package name is executed. Notice that this is not an Android package name; a test package has a single Android package name but may have several Java packages within it.
The fully-qualified Java class name for one of the test case classes. Only this test case class is executed.
or
#method name A fully-qualified test case class name, and one of its methods. Only this method is executed. Note the hash mark (#) between the class name and the method name.
Runs all test classes that extend InstrumentationTestCase.
Runs all test classes that do not extend either InstrumentationTestCase or PerformanceTestCase.
Runs a test method annotated by size. The annotations are @SmallTest, @MediumTest, and @LargeTest.
Runs all test classes that implement PerformanceTestCase.
Runs tests in debug mode.
Loads and logs all specified tests, but does not run them. The test information appears in STDOUT. Use this to verify combinations of other filters and test specifications.
Runs an EMMA code coverage analysis and writes the output to /data//coverage.ec on the device. To override the file location, use the coverageFile key that is described in the following entry.
Overrides the default location of the EMMA coverage file on the device. Specify this value as a path and filename in UNIX format. The default filename is described in the entry for the emma key.
The fully-qualified Java class name for one of the test case classes. Only this test case class is executed.
or
#method name A fully-qualified test case class name, and one of its methods. Only this method is executed. Note the hash mark (#) between the class name and the method name.
Overrides the default location of the EMMA coverage file on the device. Specify this value as a path and filename in UNIX format. The default filename is described in the entry for the emma key.
Runs an EMMA code coverage analysis and writes the output to /data//coverage.ec on the device. To override the file location, use the coverageFile key that is described in the following entry.
Runs a test method annotated by size. The annotations are @SmallTest, @MediumTest, and @LargeTest.
Runs all test classes that extend InstrumentationTestCase.
The fully-qualified Java package name for one of the packages in the test application. Any test case class that uses this package name is executed. Notice that this is not an Android package name; a test package has a single Android package name but may have several Java packages within it.
The fully-qualified Java class name for one of the test case classes. Only this test case class is executed.
or
#method name A fully-qualified test case class name, and one of its methods. Only this method is executed. Note the hash mark (#) between the class name and the method name.
Runs all test classes that extend InstrumentationTestCase.
Runs all test classes that do not extend either InstrumentationTestCase or PerformanceTestCase.
Runs a test method annotated by size. The annotations are @SmallTest, @MediumTest, and @LargeTest.
Runs all test classes that implement PerformanceTestCase.
Runs tests in debug mode.
Loads and logs all specified tests, but does not run them. The test information appears in STDOUT. Use this to verify combinations of other filters and test specifications.
Runs an EMMA code coverage analysis and writes the output to /data//coverage.ec on the device. To override the file location, use the coverageFile key that is described in the following entry.
Overrides the default location of the EMMA coverage file on the device. Specify this value as a path and filename in UNIX format. The default filename is described in the entry for the emma key.
Runs all test classes that implement PerformanceTestCase.
The fully-qualified Java package name for one of the packages in the test application. Any test case class that uses this package name is executed. Notice that this is not an Android package name; a test package has a single Android package name but may have several Java packages within it.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
Returns an array containing the constants of this enum type, in the order they're declared.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
Returns an array containing the constants of this enum type, in the order they're declared.
This method may be used to iterate over the constants.
if specified with protobuf then write output as protobuf to a file (machine readable). If path is not specified, default directory and file name will be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto
API 26+
don't use isolated storage sandbox and mount full external storage
disable restrictions on use of hidden API
turn off window animations while running
Specify user instrumentation runs in; current user if not specified
Launch the instrumented process with the selected ABI. This assumes that the process supports the selected ABI.
write profiling data to
override for socket idle timeout. This should be longer than the longest test
if specified with protobuf then write output as protobuf to a file (machine readable). If path is not specified, default directory and file name will be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto
API 26+
don't use isolated storage sandbox and mount full external storage
disable restrictions on use of hidden API
turn off window animations while running
Specify user instrumentation runs in; current user if not specified
Launch the instrumented process with the selected ABI. This assumes that the process supports the selected ABI.
write profiling data to
override for socket idle timeout. This should be longer than the longest test
://android.googlesource.com/platform/frameworks/base/+/master/cmds/am/src/com/android/commands/am/Am.java#155
WARNING: the logcat field in the proto message can lead to huge memory consumption on devices as well as on the user's side.
The logcat is read only once on the am instrument's side and the StringBuilder is uncapped. So if you have a huge logcat - you'll have to transfer it via the socket and then also parse from protobuf and allocate a String in the JVM.
The read loop catches exception and doesn't have a way to suspend until it can read the message properly.
This needs work on the am instrument's side:
proper framing support with length of the message sent first, then the actual message
streaming logcat support: this should NOT be a one-shot operation, instead we can append to file while the test executes
logcat command customisation: there is no way to change the format or filter at the moment, the command is hard-coded
see frameworks/base/cmds/am/src/com/android/commands/am/Instrument.java#readLogcat
WARNING: the logcat field in the proto message can lead to huge memory consumption on devices as well as on the user's side.
MultiRequest that accumulates char sequence responses
Read and write are called in sequence, hence you have to give the control flow back if you want cooperative multitasking to happen
Called after the initial OKAY confirmation
Called after each readElement
This type of request starts with single serialized request and then proceed to do several reads and writes that have dynamic size
adbd supports android binder bridge (abb) in interactive mode using shell protocol.
adbd supports abb using raw pipe.
adbd supports installing .apex packages.
The 'cmd' command is available, Android 24+
adbd has b/110953234 fixed.
adbd properly updates symlink timestamps on push.
The server is running with libusb enabled
adbd supports push --sync
Implement adb remount
via shelling out to /system/bin/remount.
adbd supports version 2 of send/recv.
adbd supports brotli for send/recv v2.
adbd supports dry-run send for send/recv v2.
adbd supports LZ4 for send/recv v2.
adbd supports Zstd for send/recv v2.
adbd supports track-app
service reporting debuggable/profileable apps.
adbd has b/110953234 fixed.
adbd properly updates symlink timestamps on push.
Implement adb remount
via shelling out to /system/bin/remount.
adbd supports version 2 of send/recv.
adbd supports brotli for send/recv v2.
adbd supports LZ4 for send/recv v2.
adbd supports Zstd for send/recv v2.
adbd supports dry-run send for send/recv v2.
Returns the enum constant of this type with the specified name. The string must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
if this enum type has no constant with the specified name
When asking for information related to a device, 'host:' can also be interpreted as 'any single device or emulator connected to/running on the host'.
A variant of host-serial used to target the single emulator instance running on the host. This will fail if there is none or more than one.
This type of request is a wrapper of a sequence of requests
By default all requests are targeted at adb daemon itself
override for socket idle timeout
MultiRequest that accumulates char sequence responses
This type of request starts with single serialized request and then proceed to do several reads and writes that have dynamic size
When asking for information related to a device, 'host:' can also be interpreted as 'any single device or emulator connected to/running on the host'.
A variant of host-serial used to target the single emulator instance running on the host. This will fail if there is none or more than one.
This type of request is a wrapper of a sequence of requests
This is a special form of query, where the 'host-serial::' prefix can be used to indicate that the client is asking the ADB server for information related to a specific device.
This method is called to provide the parser with data.toChannel
a chunk of data
TODO: rewrite Assumes buffer hasArray == true
Copies up to limit bytes into transformer using buffer. If limit is null - copy until EOF
TODO: rewrite Assumes buffer hasArray == true
Copies up to limit bytes into transformer using buffer. If limit is null - copy until EOF
If you're reusing the socket factory across multiple clients then this will affect another client
Android Binder Bridge gives the ability to communicate directly with the services on the devices. An example of service is package
which is handling the package management. To see the full list available services use -l
on the abb request.
Requires Feature.ABB
Executing something on a service is equivalent to executing an arbitrary cmd
sub-command (cmd package list
, cmd statusbar expand-notifications
, etc) on the device. Here is an example of listing currently available services:
val result = adb.execute(
+ request = AbbRequest(listOf("-l")),
+ serial = "emulator-5554"
+)
+
+println(result.stdout)
+
This will give you the result of the execution that includes stdout, stderr and exit code.
Requires Feature.ABB_EXEC
Some devices will not support the abb requests, instead they will support abb_exec that only returns stdout.
val stdout = adb.execute(
+ request = AbbExecRequest(listOf("-l")),
+ serial = "emulator-5554"
+)
+
+println(stdout)
+
You can restart adbd with root permissions:
val stdout = adb.execute(
+ request = RestartAdbdRequest(RootAdbdMode),
+ serial = "emulator-5554"
+)
+
+println(stdout)
+
Or without root permissions:
val stdout = adb.execute(
+ request = RestartAdbdRequest(UnrootAdbdMode),
+ serial = "emulator-5554"
+)
+
+println(stdout)
+
You can switch your device to start listening on a TCP port instead of USB:
val stdout = adb.execute(
+ request = RestartAdbdRequest(TcpIpAdbdMode(8080)),
+ serial = "emulator-5554"
+)
+
+println(stdout)
+
To switch back to USB:
val stdout = adb.execute(
+ request = RestartAdbdRequest(UsbAdbdMode),
+ serial = "emulator-5554"
+)
+
+println(stdout)
+
When working with adam it’s a good idea to keep the following things in mind.
Every request in adam requires you to create an instance of AndroidDebugBridgeClient
in order to execute a requests. All the requests produce either a single response (e.g. ListDevicesRequest
):
val devices: List<Device> = adbClient.execute(request = ListDevicesRequest())
+
or request produces a stream of responses, e.g. a progress of pulling a file:
val testFile = createTempFile()
+val channel = adbClient.execute(
+ request = PullFileRequest("/data/local/tmp/testfile", testFile),
+ scope = GlobalScope,
+ serial = "emulator-5554"
+)
+
+var percentage = 0
+while (!channel.isClosedForReceive) {
+ val progressDouble = channel.receiveOrNull() ?: break
+ println(progressDouble)
+}
+println("done!")
+
In general, you can expect the following for any request:
ClosedWriteChannelException
if the device connection is not be available anymoreRequestRejectedException
if ADB server doesn’t respond properlyRequestValidationException
if request’s #validate()
returns false before executionThere are additional exceptions, namely:
PullFailedException
, PushFailedException
and UnsupportedSyncProtocolException
for file requestsUnsupportedForwardingSpecException
for port forwarding requestsUnsupportedImageProtocolException
for screenshot requestsWhen executing the request agains an ADB server client sends what is the target for that particular request.
Possible targets are:
HostTarget
. When asking for information related to a device, ‘host:’ can also be interpreted as ‘any single device or emulator connected to/running on the host’.SerialTarget
. This is a special form of query, where the ‘host-serial:UsbTarget
. A variant of host-serial used to target the single USB device connected to the host. This will fail if there is none or more than one.LocalTarget
. A variant of host-serial used to target the single emulator instance running on the host. This will fail if there is none or more than one.NonSpecifiedTarget
In most of the cases you can specify any of them and there are sensible defaults. For example, KillAdbRequest
’s default target is HostTarget
since this request doesn’t make sense for Android device itself.
For all the requests targeting a particular device, e.g. ScreenCaptureRequest
you have to specify the serial
parameter when executing, e.g.:
adb.execute(
+ request = ScreenCaptureRequest(),
+ serial = "emulator-5554"
+ )
+
The serial for each particular device can be retrieved by executing either ListDevicesRequest
or AsyncDeviceMonitorRequest
There are three ways to control an emulator:
At boot time each emulator allocates 2 ports on the host’s loopback interface: a console port and a gRPC bridge port. The console port can be established using the serial number of emulator, for example emulator-5554
’s console port is 5554, emulator-5556
- 5556. If you want to make sure emulator uses a particular console port you can start the emulator with a parameter:
$ emulator @Nexus_5X_API_23 -port <port>
+
gRPC bridge port is usually calculated as console port + 3000
, so for emulator-5554
default gRPC port will be 8554. If you want to make sure emulator uses a particular grpc bridge port you can start the emulator with a parameter:
$ emulator @Nexus_5X_API_23 -grpc <port>
+
This request will execute an emulator console command:
val devices: List<Device> = adb.execute(
+ request =
+ EmulatorCommandRequest(
+ "help",
+ InetSocketAddress("localhost", 5556)
+ )
+)
+
This request is completely different to other requests: it connects directly to emulator instead of adb server. For simplicity, it can be used in the same way as adb server requests and shares the socket creation logic with other requests.
Adam bundles a gRPC client for emulator gRPC bridge. The spec is generated from emulator_controller.proto
Please refer to the gRPC docs on using the client. A very simple example looks something like this:
val channel = ManagedChannelBuilder.forAddress(grpcHost, grpcPort).apply {
+ usePlaintext()
+ executor(Dispatchers.IO.asExecutor())
+}.build()
+val client = EmulatorControllerGrpcKt.EmulatorControllerCoroutineStub(channel)
+val state = client.getVmState(Empty.getDefaultInstance())
+
Please refer to the emulator_controller.proto for the supported functionality.
This request type transfers files or information about files
If you want to push/pull without worrying about the device features and protocols - see recommended requests.
When working with sync v1/v2 protocols, a proper fallback has to be implemented if device doesn’t support some of the features. To simplify working with this - see compatibility requests.
If you want to directly interact with sync v1 see sync v1 docs, for sync v2 - sync v2 docs
For compatibility reasons, you might want to use plain ls
. This is wrapped in ListFilesRequest
Optionally uses Feature.STAT_V2
The following request will return file stats:
val stats = adb.execute(CompatStatFileRequest("/data/local/tmp/app-debug.apk", supportedFeaturesList), "emulator-5554")
+
The model of stats is represented as FileEntry
that will be an instance of FileEntryV1
or FileEntryV2
depending on the features available:
sealed class FileEntry {
+ abstract val mode: UInt
+ abstract val name: String?
+ abstract val mtime: Instant
+
+ fun isDirectory()
+ fun isRegularFile()
+ fun isBlockDevice()
+ fun isCharDevice()
+ fun isLink()
+
+ fun size()
+
+ abstract fun exists(): Boolean
+}
+
Name is optional and is only filled by list requests but not stat requests.
Optionally uses Feature.LS_V2
The following request will return list of files for a particular path:
val list: List<FileEntry> = adb.execute(CompatListFileRequest("/sdcard/", supportedFeaturesList), "emulator-5554")
+
Optionally uses Feature.SENDRECV_V2
Use the following to pull a file(not a folder) with a known path on the device
launch {
+ val channel = adb.execute(
+ PullFileRequest("/data/local/tmp/testfile", createTempFile(), supportedFeaturesList, coroutineContext = coroutineContext),
+ scope = this,
+ "emulator-5554"
+ )
+
+ var percentage = 0
+ for (percentageDouble in channel) {
+ percentage = (percentageDouble * 100).roundToInt()
+ println(percentage)
+ }
+}
+
Optionally uses Feature.SENDRECV_V2
To push a local file to Android device’s folder (remotePath
should be the full path with the name of the target file):
launch {
+ val file = File("some-file")
+ val channel = adb.execute(
+ PushFileRequest(
+ local = file,
+ remotePath = "/data/local/tmp/some-file",
+ supportedFeaturesList,
+ mode = "0644"
+ ),
+ scope = this,
+ serial = "emulator-5554"
+ )
+
+ var percentage = 0
+ for (percentageDouble in channel) {
+ percentage = (percentageDouble * 100).roundToInt()
+ println(percentage)
+ }
+}
+
mode is the access rights in octal represented as an instance of String
.
Traversing directories can be done using the following wrapper around ls
:
val files: List<AndroidFile> = adb.execute(
+ request = ListFilesRequest(
+ directory = "/sdcard/"
+ ),
+ serial = "emulator-5554"
+)
+
AndroidFile
is a data class with the following properties:
/**
+ * @property permissions full permissions string, e.g. -rw-rw----
+ * @property owner file owner, e.g. root
+ * @property group file group, e.g. sdcard_rw
+ * @property date e.g. 2020-12-01
+ * @property time e.g. 22:22
+ * @property name the file name without path, e.g. testfile.txt
+ * @property directionality file's directory, e.g. /sdcard/
+ * @property size file's size, e.g. 1024
+ * @property type file's type
+ * @property link if the file is a symbolic link, this field is what the link points to
+ */
+data class AndroidFile(
+ val permissions: String,
+ val owner: String,
+ val group: String,
+ val date: String,
+ val time: String,
+ val name: String,
+ val directory: String,
+ val size: Long,
+ val type: AndroidFileType,
+ val link: String? = null
+)
+
Optionally uses Feature.STAT_V2, Feature.LS_V2, Feature.SENDRECV_V2
The following request will pull remote file(s) or folder(s):
val success: Boolean = adb.execute(PullRequest("/data/local/tmp/testdir", destination, supportedFeatures), "emulator-5554")
+
Please note that this request doesn’t handle file links. If source is a directory and the destination is an existing directory, a subdirectory will be created
Optionally uses Feature.STAT_V2, Feature.LS_V2, Feature.SENDRECV_V2
The following request will push local file(s) or folder(s) to the remote device:
val success: Boolean = adb.execute(PushRequest(source, "/data/local/tmp/testdir", supportedFeatures), "emulator-5554")
+
Please note that this request doesn’t handle file links. If source is a directory and the destination is an existing directory, a subdirectory will be created
The following request will return file stats:
val stats = adb.execute(StatFileRequest("/data/local/tmp/app-debug.apk"), "emulator-5554")
+
The model of stats is represented as FileEntryV1
:
data class FileEntryV1(
+ val name: String? = null,
+ val mode: UInt,
+ val size: UInt,
+ val mtime: Instant
+) {
+ fun exists(): Boolean
+ fun isDirectory(): Boolean
+ fun isRegularFile(): Boolean
+ fun isBlockDevice(): Boolean
+ fun isCharDevice(): Boolean
+ fun isLink(): Boolean
+}
+
Name is optional and is only filled by list requests but not stat requests.
The following request will return list of files for a particular path:
val list: List<FileEntryV1> = adb.execute(ListFileRequest("/sdcard/"), "emulator-5554")
+
Use the following to pull a file(not a folder) with a known path on the device
launch {
+ val channel = adb.execute(
+ PullFileRequest("/data/local/tmp/testfile", createTempFile(), coroutineContext = coroutineContext),
+ scope = this,
+ "emulator-5554"
+ )
+
+ var percentage = 0
+ for (percentageDouble in channel) {
+ percentage = (percentageDouble * 100).roundToInt()
+ println(percentage)
+ }
+}
+
To push a local file to Android device’s folder (remotePath
should be the full path with the name of the target file):
launch {
+ val file = File("some-file")
+ val channel = adb.execute(
+ PushFileRequest(
+ local = file,
+ remotePath = "/data/local/tmp/some-file",
+ mode = "0644"
+ ),
+ scope = this,
+ serial = "emulator-5554"
+ )
+
+ var percentage = 0
+ for(percentageDouble in channel) {
+ percentage = (percentageDouble * 100).roundToInt()
+ println(percentage)
+ }
+}
+
mode is the access rights in octal represented as an instance of String
.
Requires Feature.STAT_V2
The following request will return file stats:
val stats = adb.execute(StatFileRequest("/data/local/tmp/app-debug.apk", supportedFeaturesList), "emulator-5554")
+
The model of stats is represented as FileEntryV1
:
data class FileEntryV2(
+ val error: UInt,
+ val dev: ULong,
+ val ino: ULong,
+ val mode: UInt,
+ val nlink: UInt,
+ val uid: UInt,
+ val gid: UInt,
+ val size: ULong,
+ val atime: Instant,
+ val mtime: Instant,
+ val ctime: Instant,
+ val name: String? = null
+) {
+ fun exists(): Boolean
+ fun isDirectory(): Boolean
+ fun isRegularFile(): Boolean
+ fun isBlockDevice(): Boolean
+ fun isCharDevice(): Boolean
+ fun isLink(): Boolean
+}
+
Name is optional and is only filled by list requests but not stat requests.
Requires Feature.LS_V2
The following request will return list of files for a particular path:
val list: List<FileEntryV2> = adb.execute(ListFileRequest("/sdcard/", supportedFeaturesList), "emulator-5554")
+
Requires Feature.SENDRECV_V2
Use the following to pull a file(not a folder) with a known path on the device
launch {
+ val channel = adb.execute(
+ PullFileRequest("/data/local/tmp/testfile", createTempFile(), supportedFeaturesList, coroutineContext = coroutineContext),
+ scope = this,
+ "emulator-5554"
+ )
+
+ var percentage = 0
+ for (percentageDouble in channel) {
+ percentage = (percentageDouble * 100).roundToInt()
+ println(percentage)
+ }
+}
+
Requires Feature.SENDRECV_V2
To push a local file to Android device’s folder (remotePath
should be the full path with the name of the target file):
launch {
+ val file = File("some-file")
+ val channel = adb.execute(
+ PushFileRequest(
+ local = file,
+ remotePath = "/data/local/tmp/some-file",
+ supportedFeaturesList,
+ mode = "0644"
+ ),
+ scope = this,
+ serial = "emulator-5554"
+ )
+
+ var percentage = 0
+ for (percentageDouble in channel) {
+ percentage = (percentageDouble * 100).roundToInt()
+ println(percentage)
+ }
+}
+
mode is the access rights in octal represented as an instance of String
.
Optionally uses Feature.SHELL_V2
Executing tests can be done using the TestRunnerRequest
:
val channel: ReceiveChannel<List<TestEvents>> = adb.execute(
+ request = TestRunnerRequest(
+ testPackage = "com.example.test",
+ instrumentOptions = InstrumentOptions(
+ clazz = listOf("com.example.MyTest")
+ ),
+ supportedFeatures = emptyList(),
+ coroutineScope = GlobalScope,
+ ),
+ serial = "emulator-5554"
+)
+
+
The result is a channel ReadChannel<List<TestEvents>>
that contains parsed and converted output of the am instrument
command.
To execute tests you have to provide the testPackage
, default InstrumentOptions()
, coroutineScope
and supportedFeatures
for the target device. Caution: you have to provide the supportedFeatures
because newer Android devices write information into the stderr for some reason. The textual am instrument parser doesn’t support this and needs to read only stdout.
Default test runner class is android.support.test.runner.AndroidJUnitRunner
but can be changed using the runnerClass
option.
For all the options check the source of the am
command
Disables restrictions on the use of hidden APIs
Turn off window animations while running
Specify user instrumentation runs in. Defaults to current user if not specified”
Write profiling data to specified path on the device
Write test log to specified path
The fully-qualified Java package name for one of the packages in the test application. Any test case class that uses this package name is executed. Notice that this is not an Android package name; a test package has a single Android package name but may have several Java packages within it.
The fully-qualified Java class name for one of the test case classes. Only this test case class is executed.
or
<class_name>#method name
. A fully-qualified test case class name, and one of its methods. Only this method is executed. Note the hash mark (#) between the class name and the method name.
Runs all test classes that extend InstrumentationTestCase
.
Runs all test classes that do not extend either InstrumentationTestCase
or PerformanceTestCase
.
Runs a test method annotated by size. The annotations are @SmallTest
, @MediumTest
, and @LargeTest
.
Runs all test classes that implement PerformanceTestCase
.
Runs tests in debug mode.
Loads and logs all specified tests, but does not run them.
The test information appears in STDOUT. Use this to verify combinations of other filters and test specifications.
Runs an EMMA code coverage analysis and writes the output to /data/
To override the file location, use the [coverageFile] key that is described in the following entry.
Overrides the default location of the EMMA coverage file on the device. Specify this value as a path and filename in UNIX format. The default filename is described in the entry for the [emma] key.
To read logcat once you can execute:
val log = adb.execute(
+ request = SyncLogcatRequest(
+ since = Instant.now().minusSeconds(60),
+ filters = listOf(LogcatFilterSpec("TAG", LogcatVerbosityLevel.E))
+ ),
+ serial = "emulator-5554"
+)
+
SyncLogcatRequest
maps most of the options exposed by the underlying logcat
command:
class SyncLogcatRequest(
+ since: Instant? = null,
+ modes: List<LogcatReadMode> = listOf(LogcatReadMode.long),
+ buffers: List<LogcatBuffer> = listOf(LogcatBuffer.default),
+ pid: Long? = null,
+ lastReboot: Boolean? = null,
+ filters: List<LogcatFilterSpec> = emptyList()
+)
+
Recording the output from logcat (for example when writing to a file):
launch {
+ val channel = adb.execute(
+ request = ChanneledLogcatRequest(),
+ scope = this,
+ serial = "emulator-5554"
+ )
+
+ val logcatChunk = channel.receive()
+ //logcatChunk == "I/ActivityManager( 585): Starting activity: Intent { action=android.intent.action...}\nI/MyActivity( 1557): MyClass"
+ //write to a file or append to a buffer
+
+ //Dispose of channel to close the resources
+ channel.cancel()
+}
+
Logcat chunks that you receive might not be \n
terminated so if you need to parse logcat output line-by-line then you need to accumulate the chunks in a buffer first.
ChanneledLogcatRequest
maps most of the options exposed by the underlying logcat
command:
class ChanneledLogcatRequest(
+ since: Instant? = null,
+ modes: List<LogcatReadMode> = listOf(LogcatReadMode.long),
+ buffers: List<LogcatBuffer> = emptyList(),
+ pid: Long? = null,
+ lastReboot: Boolean? = null,
+ filters: List<LogcatFilterSpec> = emptyList()
+)
+
See the official docs for more info on what these options change.
This request returns the adb server version specified in adb/adb.h
(e.g. here). It is useful for debugging incompatible versions of adb and also making sure your requests are supported by the adb server.
val version: Int = adb.execute(request = GetAdbServerVersionRequest())
+
This request is equivalent to executing adb kill-server
:
adb.execute(request = KillAdbRequest())
+
Remount partitions read-write. If a reboot is required, autoReboot = true
will automatically reboot the device.
val output: String = adb.execute(request = RemountPartitionsRequest(autoReboot = false), "emulator-5554")
+
val output: String = adb.execute(request = SetDmVerityCheckingRequest(false), "emulator-5554")
+
val features: List<Feature> = adb.execute(request = FetchHostFeaturesRequest())
+
mDNS is used for automatic discovery and connection of remote devices (for example with Android 11 ADB over WiFi)
val status: MdnsStatus = adb.execute(MdnsCheckRequest())
+
val services: List<MdnsService> = adb.execute(ListMdnsServicesRequest())
+
data class MdnsService(
+ val name: String,
+ val serviceType: String,
+ val url: String
+)
+
This request will capture a snapshot of device states at a point of execution:
val devices: List<Device> = adb.execute(request = ListDevicesRequest())
+
If listing devices once is not enough, i.e. you want to continually monitor if devices change their states (disconnect, connect, etc) use the following request:
val deviceEventsChannel: ReceiveChannel<List<Device>> = adb.execute(
+ request = AsyncDeviceMonitorRequest(),
+ scope = GlobalScope
+)
+
+for (currentDeviceList in deviceEventsChannel) {
+ //...
+}
+
Keep in mind that this will send the device events for all devices even if some of them didn’t change.
This request will retrieve a list of features supported by a particular device:
val features: List<Feature> = adb.execute(request = FetchDeviceFeaturesRequest("emulator-5554"))
+
Here is a list of features adam is aware of:
There are more features, but adam is not using them at the moment.
Every time you see in the documentation something like Requires Feature.ABB
it means that this request will not succeed unless the device has support for a particular feature. You can check the support by executing the FetchDeviceFeaturesRequest
beforehand or catch the RequestValidationException
.
Sometimes a feature might be optionally used if there is a fallback, see docs for a particular request.
If you need to connect remote Android devices to a local adb server:
val output = adb.execute(ConnectDeviceRequest("10.0.0.2", 5555))
+
To disconnect a previously connected Android device:
val output = adb.execute(DisconnectDeviceRequest("10.0.0.2", 5555))
+
This request is quite tricky to use since the target of the request varies with the reconnection target
If you don’t specify anything in reconnectTarget then it’s treated as find the first available device
and reconnect
val output = adb.execute(ReconnectRequest())
+
If you specify Device target then you have to provide the target either here or via serial during execution
val output = adb.execute(ReconnectRequest(reconnectTarget = Device, target = SerialTarget("10.0.0.2:5555")))
+
If you use Offline then you have to use the host target only
val output = adb.execute(ReconnectRequest(reconnectTarget = Offline, target = HostTarget))
+
Pairs adb server with device over WiFi connection See https://developer.android.com/studio/command-line/adb#connect-to-a-device-over-wi-fi-android-11+
val output = adb.execute(PairDeviceRequest("10.0.0.2:39567", "123456"))
+
The target device should be in the form of host[:port]
, port is optional.
If you need to reboot a particular device (for example if it stopped executing requests properly):
adb.execute(request = RebootRequest(), serial = "emulator-5554")
+
Or if you want to reboot to recovery:
adb.execute(request = RebootRequest(mode = RECOVERY), serial = "emulator-5554")
+
Or bootloader:
adb.execute(request = RebootRequest(mode = BOOTLOADER), serial = "emulator-5554")
+
In order to install a package you first need to push the file with PushFileRequest
to appropriate location. You should push your apks to /data/local/tmp
since it’s a user-writable path on all versions of Android (so far).
val output: String = adb.execute(
+ request = InstallRemotePackageRequest(
+ absoluteRemoteFilePath = "/data/local/tmp/$apkFileName",
+ reinstall = true,
+ extraArgs = emptyList()
+ ),
+ serial = "emulator-5554"
+)
+
Requires Feature.CMD or Feature.ABB_EXEC
Optionally uses Feature.APEX
This mode streams the package file so that you don’t need to push the file to the device beforehand. This saves you a couple of requests, namely push file and delete file at the end.
val success = client.execute(
+ StreamingPackageInstallRequest(
+ pkg = testFile,
+ supportedFeatures,
+ reinstall = false,
+ extraArgs = emptyList()
+ ),
+ serial = "emulator-5554"
+)
+
Optionally uses Feature.CMD or Feature.ABB_EXEC
Install an apk split as follows:
val success = client.execute(
+ InstallSplitPackageRequest(
+ pkg = ApkSplitInstallationPackage(appFile1, appFile2),
+ supportedFeatures,
+ reinstall = false,
+ extraArgs = emptyList()
+ ),
+ serial = "emulator-5554"
+)
+
If both CMD
and ABB_EXEC
features are missing then falls back to ‘exec:’. In this case there is no guarantee that the pm
binary can install the split packages at all.
Requires Feature.CMD or Feature.ABB_EXEC
Optionally uses Feature.APEX
This request installs multiple packages as a single atomic operation. If one of them fails - all will fail.
val success = client.execute(
+ AtomicInstallPackageRequest(
+ pkgList = listOf(
+ SingleFileInstallationPackage(appFile),
+ ApkSplitInstallationPackage(appFile1, appFile2)
+ ),
+ supportedFeatures,
+ reinstall = false,
+ extraArgs = emptyList()
+ ),
+ serial = "emulator-5554"
+)
+
To list all installed packages:
val packages: List<Package> = adb.execute(
+ request = PmListRequest(
+ includePath = false
+ ),
+ serial = "emulator-5554"
+)
+
val output: String = adb.execute(
+ request = UninstallRemotePackageRequest(
+ packageName = "com.example",
+ keepData = false
+ ),
+ serial = "emulator-5554"
+)
+
If you need to use sideload to install packages use the following:
val success = client.execute(
+ request = SideloadRequest(
+ pkg = file
+ ),
+ serial = "emulator-5554"
+)
+
val success = client.execute(
+ request = LegacySideloadRequest(
+ pkg = file
+ ),
+ serial = "emulator-5554"
+)
+
This request forwards some local port to a remote device port.
Local port can be defined as:
LocalTcpPortSpec(val port: Int)
. This will map a local TCP port.LocalUnixSocketPortSpec(val path: String)
. This will create a local named unix path.Remote port can be defined as:
RemoteTcpPortSpec(val port: Int)
. This will map a remote TCP port.RemoteAbstractPortSpec(val unixDomainSocketName: String)
RemoteReservedPortSpec(val unixDomainSocketName: String)
RemoteFilesystemPortSpec(val unixDomainSocketName: String)
RemoteDevPortSpec(val charDeviceName: String)
JDWPPortSpec(val processId: Int)
adb.execute(request = PortForwardRequest(
+ local = LocalTcpPortSpec(12042),
+ remote = RemoteTcpPortSpec(12042),
+ serial = "emulator-5554",
+ mode = DEFAULT)
+)
+
DEFAULT
mode does not rebind the port. If you need to rebind use the NO_REBIND
value.
To retrieve a list of port forwarding rules use the following:
val rules: List<PortForwardingRule> = adb.execute(ListPortForwardsRequest("emulator-5554"))
+
To remove a forwarding you don’t need to specify the remote port spec.
adb.execute(request = RemovePortForwardRequest(
+ local = LocalTcpPortSpec(12042),
+ serial = "emulator-5554")
+)
+
To clean all the rules:
adb.execute(request = RemoveAllPortForwardsRequest(
+ serial = "emulator-5554"
+))
+
This request forwards some remote device port to a local host port.
Remote(host) port can be defined as:
LocalTcpPortSpec(val port: Int)
. This will map a local TCP port.LocalUnixSocketPortSpec(val path: String)
. This will create a local named unix path.Local(device) port can be defined as:
RemoteTcpPortSpec(val port: Int)
. This will map a remote TCP port.RemoteAbstractPortSpec(val unixDomainSocketName: String)
RemoteReservedPortSpec(val unixDomainSocketName: String)
RemoteFilesystemPortSpec(val unixDomainSocketName: String)
RemoteDevPortSpec(val charDeviceName: String)
JDWPPortSpec(val processId: Int)
adb.execute(request = ReversePortForwardRequest(
+ local = RemoteTcpPortSpec(12042),
+ remote = LocalTcpPortSpec(12042),
+ serial = "emulator-5554",
+ mode = DEFAULT)
+)
+
DEFAULT
mode does not rebind the port. If you need to rebind use the NO_REBIND
value.
To retrieve a list of reverse port forwarding rules use the following:
val rules: List<ReversePortForwardingRule> = adb.execute(ListReversePortForwardsRequest(), "emulator-5554")
+
To remove a forwarding rule you don’t need to specify a remote port spec.
adb.execute(request = RemoveReversePortForwardRequest(
+ local = RemoteTcpPortSpec(12042),
+ serial = "emulator-5554")
+)
+
To clean all the rules:
adb.execute(request = RemoveAllReversePortForwardsRequest(
+ serial = "emulator-5554"
+))
+
Retrieving device properties (equivalent to executing getprop
on the device) can be done using the requests below.
This requests retrieves all properties and create a Map of String -> String to allow working with properties like this properties["sys.boot_completed"]
.
val properties: Map<String, String> = adb.execute(
+ request = GetPropRequest(),
+ serial = "emulator-5554"
+)
+
If only a single property is needed then you can use the shorter version:
val value: String = adb.execute(
+ request = GetSinglePropRequest(name = "sys.boot_completed"),
+ serial = "emulator-5554"
+)
+
Capturing screenshots is done using the ScreenCaptureRequest
. This request will check the remote protocol version and will fail if the format is unsupported.
val adapter = RawImageScreenCaptureAdapter()
+val image = adb.execute(
+ request = ScreenCaptureRequest(adapter),
+ serial = "emulator-5554"
+).toBufferedImage()
+
+if (!ImageIO.write(image, "png", File("/tmp/screen.png"))) {
+ throw IOException("Failed to find png writer")
+}
+
In order to receive the image you’ll have to transform the framebuffer bytes into something meaningful. There are two options here: RawImageScreenCaptureAdapter
and BufferedImageScreenCaptureAdapter
. The RawImageScreenCaptureAdapter
is a bare minimum to receive the necessary metadata as well as the byte[]
that holds the screenshot. The return type of this adapter is RawImage
that supports retrieving the pixel value by index using RawImage#getARGB(index: Int)
. You can also transform the image into Java’s BufferedImage
.
However, if you intend to capture a lot of screenshots for a particular device, consider using the BufferedImageScreenCaptureAdapter
that will reduce additional allocations of memory when transforming the image.
Please note, that all adapter by default will try to reduce the memory consumption and reuse the internal buffers. If you’re using the same adapter on multiple threads in parallel either set the buffer to null
all the time or provide an external buffer that is allocated per thread.
This is a description of requests in com.malinskiy.adam.request.shell.v1
You can execute arbitrary commands (ls
, date
, etc) on the device using the ShellCommandRequest
:
val response: ShellCommandResult = adb.execute(
+ request = ShellCommandRequest("echo hello"),
+ serial = "emulator-5554"
+)
+
The response contains stdout mixed with stderr (since this protocol doesn’t support separate streams). On top of this, shell v1 doesn’t support returning an exit code natively. To mitigate this whenever you execute any shell v1 command adam appends ;echo $?
to the end of the command and parses it automatically.
data class ShellCommandResult(
+ val stdout: ByteArray,
+ val exitCode: Int
+)
+
If the output is UTF-8 encoded then you can use lazy property output
for conversion of bytes into a String, e.g. result.output
.
This request expects that the command returns immediately, or you don’t want to stream the output.
You can execute arbitrary commands (cat
, tail -f
, etc) on the device using the ChanneledShellCommandRequest
:
launch {
+ val updates = adb.execute(
+ request = ChanneledShellCommandRequest("logcat -v"),
+ scope = this,
+ serial = "emulator-5554"
+ )
+
+ for (lines in updates) {
+ println(lines)
+ }
+}
+
Executes the command and provides the channel as the input to the command. Does not return anything
val blockSizeChannel = Channel<Int>(capacity = 1)
+//You have to implement the function below for applicable source of data that you have.
+//Testing code in adam has an example for a file
+val channel: ReceiveChannel<ByteArray> = someFunctionThatProducesByteArrayInResponseToRequestsOverBlockSizeChannel(blockSizeChannel)
+val success = client.execute(
+ request = ExecInRequest(
+ cmd = "cmd package install -S ${testFile.length()}",
+ channel = testFile.readChannel(),
+ sizeChannel = blockSizeChannel
+ ),
+ serial = "emulator-5554"
+)
+
This is a description of requests in com.malinskiy.adam.request.shell.v2
Requires Feature.SHELL_V2
You can execute arbitrary commands (ls
, date
, etc) on the device using the ShellCommandRequest
:
val response: ShellCommandResult = adb.execute(
+ request = ShellCommandRequest("echo hello"),
+ serial = "emulator-5554"
+)
+
The response contains separate stdout and stderr as well as an exit code.
data class ShellCommandResult(
+ val stdout: ByteArray,
+ val stderr: ByteArray,
+ val exitCode: Int
+)
+
If the output is UTF-8 encoded then you can use lazy properties output
and errorOutput
for conversion of bytes into a String, e.g. result.output
.
This request expects that the command returns immediately, or you don’t want to stream the output.
Requires Feature.SHELL_V2
You can execute arbitrary commands (cat
, tail -f
, etc) on the device using the ChanneledShellCommandRequest
. Shell v2 brings in support for stdin implemented as a separate channel.
launch {
+ val stdio = Channel<ShellCommandInputChunk>()
+ val receiveChannel = adb.execute(ChanneledShellCommandRequest("cat", stdio), this, "emulator-5554")
+ //Sending commands requires additional pool, otherwise we might deadlock
+ val stdioJob = launch(Dispatchers.IO) {
+ stdio.send(
+ ShellCommandInputChunk(
+ stdin = "cafebabe".toByteArray(Charsets.UTF_8)
+ )
+ )
+
+ stdio.send(
+ ShellCommandInputChunk(
+ close = true
+ )
+ )
+ }
+
+ val stdoutBuilder = StringBuilder()
+ val stderrBuilder = StringBuilder()
+ var exitCode = 1
+ for (i in receiveChannel) {
+ i.stdout?.let { stdoutBuilder.append(String(it, Charsets.UTF_8)) }
+ i.stderr?.let { stderrBuilder.append(String(it, Charsets.UTF_8)) }
+ i.exitCode?.let { exitCode = it }
+ }
+ stdioJob.join()
+
+ println(stdoutBuilder.toString())
+}
+
Adam supports tests that need to interact with the device be it adb or emulator console/gRPC access. This means that you can execute a test, set the location on the device or emulate a particular sensor input.
This ability makes it possible to provide end-to-end testing environment for a lot of complex applications. Some examples could be a fitness tracker app that needs to recognize a particular pattern of movement in the device, or an application that needs to handle phone calls/SMS.
Here is a list of currently supported sensors by the emulator_controller.proto just as an example of the possibilities:
enum SensorType {
+ // Measures the acceleration force in m/s2 that is applied to a device
+ // on all three physical axes (x, y, and z), including the force of
+ // gravity.
+ ACCELERATION = 0;
+ // Measures a device's rate of rotation in rad/s around each of the
+ // three physical axes (x, y, and z).
+ GYROSCOPE = 1;
+ // Measures the ambient geomagnetic field for all three physical axes
+ // (x, y, z) in μT.
+ MAGNETIC_FIELD = 2;
+ // Measures degrees of rotation that a device makes around all three
+ // physical axes (x, y, z)
+ ORIENTATION = 3;
+ // Measures the temperature of the device in degrees Celsius (°C).
+ TEMPERATURE = 4;
+ // Measures the proximity of an object in cm relative to the view screen
+ // of a device. This sensor is typically used to determine whether a
+ // handset is being held up to a person's ear.
+ PROXIMITY = 5;
+ // Measures the ambient light level (illumination) in lx.
+ LIGHT = 6;
+ // Measures the ambient air pressure in hPa or mbar.
+ PRESSURE = 7;
+ // Measures the relative ambient humidity in percent (%).
+ HUMIDITY = 8;
+ MAGNETIC_FIELD_UNCALIBRATED = 9;
+ }
+
In order to use the following rules, support from your test runner is required. A reference implementation of this can be found in Marathon test runner.
Tests can be injected with a usable implementation of adb, console or gRPC as following using a custom JUnit 4 rule. All rules provide ways to fail via assumption, e.g. EmulatorGrpcRule(mode = Mode.ASSUME)
in case testing is done in a mixed device environment where not every run has emulators/adb access.
The rule for adb provides access to the usual adam requests that target only the current device via explicitly specifying the serial number of the device for each request.
class AdbActivityTest {
+ @get:Rule
+ val rule = ActivityScenarioRule(MainActivity::class.java)
+
+ @get:Rule
+ val adbRule = AdbRule(mode = Mode.ASSERT)
+
+ @Test
+ fun testVmState() {
+ runBlocking {
+ val result = adbRule.adb.execute(ShellCommandRequest("echo \"hello world\""))
+ assert(result.exitCode == 0)
+ assert(result.output.startsWith("hello world"))
+ }
+ }
+}
+
The rule for gRPC provides access to the supported gRPC requests and is only valid for an emulator. For a list of supported requests check the official source emulator_controller.proto
No support for gRPC TLS or auth is currently implemented
class GrpcActivityTest {
+ @get:Rule
+ val rule = ActivityScenarioRule(MainActivity::class.java)
+
+ @get:Rule
+ val emulator = EmulatorGrpcRule(mode = Mode.ASSERT)
+
+ @Test
+ fun testVmState() {
+ runBlocking {
+ val vmState = emulator.grpc.getVmState(Empty.getDefaultInstance())
+ assert(vmState.state == VmRunState.RunState.RUNNING)
+ }
+ }
+}
+
Emulator console port requires an auth token by default that is stored on the host OS of the emulator in the $HOME/.emulator_console_auth_token
file. If you want to get rid of the auth, leave an empty file at the same location. This will prevent the emulator from requiring an auth token during the request.
class ConsoleActivityTest {
+ @get:Rule
+ val rule = ActivityScenarioRule(MainActivity::class.java)
+
+ @get:Rule
+ val console = EmulatorConsoleRule(mode = Mode.ASSERT)
+
+ @Test
+ fun testVmState() {
+ runBlocking {
+ val result = console.execute("avd status")
+ Allure.description("VM state is $result")
+ assert(result.contains("running"))
+ }
+ }
+}
+
For real devices only adb access can be exposed. This is achieved via reverse port forwarding on the test runner’s side. This works regardless of the transport mode (direct USB connection or TCP via adb connect). The connection to adb can then be achieved via direct communication to localhost on the real device. The port number that is by default 5037, can be different though. Only test runner has the actual method of establishing the adb port (since test runner communicates with this adb server), hence test runner has to provide this port number via Instrumentation arguments to the test.
For emulators there are two cases: If the emulator is a local emulator, then access to the host’s loopback interface (which has all the necessary services) can be achieved via special 10.0.2.2 IP. For adb test runner should provide the port number for the same reasons as for real phones. For telnet communication only test runner can calculate the port number (emulator-5554 -> 5554). Accessing this port requires auth token which has to be supplied by test runner since there is no way to access host’s file system for the $HOME/.emulator_console_auth_token. Same problem with the gRPC - everything has to be supplied by the test runner.
If the emulator is a remote emulator connected via adb connect then it is not currently possible to establish the telnet port as well as gRPC port. Although technically possible, in practice the adb port for such a case would most likely be port-forwarded already and there is no way to guarantee that the adb connect port is actually equal to the real adb port. This would be possible to solve provided the emulator was able to receive emulator-5554 variable somehow, but any Android properties would break on the load of a snapshot for example.
The contract for the test runner defines passing host and port of a provided adb/console/grpc connection. You can get it as a shared dependency:
dependencies {
+ implementation 'com.malinskiy.adam:android-testrunner-contract:X.X.X'
+}
+
Here is roughly how it looks (please use the maven dependency and don’t copy paste this):
public class TestRunnerContract {
+ public static String grpcPortArgumentName = "com.malinskiy.adam.android.GRPC_PORT";
+ public static String grpcHostArgumentName = "com.malinskiy.adam.android.GRPC_HOST";
+ public static String adbPortArgumentName = "com.malinskiy.adam.android.ADB_PORT";
+ public static String adbHostArgumentName = "com.malinskiy.adam.android.ADB_HOST";
+ public static String consolePortArgumentName = "com.malinskiy.adam.android.CONSOLE_PORT";
+ public static String consoleHostArgumentName = "com.malinskiy.adam.android.CONSOLE_HOST";
+ public static String emulatorAuthTokenArgumentName = "com.malinskiy.adam.android.AUTH_TOKEN";
+ public static String deviceSerialArgumentName = "com.malinskiy.adam.android.ADB_SERIAL";
+}
+
It is test runner’s responsibility to set these up when executing tests, e.g.:
$ am instrument -w -r --no-window-animation -e class com.example.AdbActivityTest#testUnsafeAccess -e debug false -e com.malinskiy.adam.android.ADB_PORT 5037 -e com.malinskiy.adam.android.ADB_HOST 10.0.2.2 -e com.malinskiy.adam.android.ADB_SERIAL emulator-5554 -e com.malinskiy.adam.android.GRPC_PORT 8554 -e com.malinskiy.adam.android.GRPC_HOST 10.0.2.2 com.example.test/androidx.test.runner.AndroidJUnitRunner
+
Adam provides several producers of test statuses that inform test runners about the test execution.
Artifact: com.malinskiy.adam:android-junit4-test-annotation-producer:${LATEST_VERSION}
This producer emits current test annotations as a metric, e.g.:
INSTRUMENTATION_STATUS_CODE: 0
+INSTRUMENTATION_STATUS: class=com.example.FailedAssumptionTest
+INSTRUMENTATION_STATUS: current=4
+INSTRUMENTATION_STATUS: id=AndroidJUnitRunner
+INSTRUMENTATION_STATUS: numtests=39
+INSTRUMENTATION_STATUS: stream=
+com.example.FailedAssumptionTest:
+INSTRUMENTATION_STATUS: test=ignoreTest
+INSTRUMENTATION_STATUS_CODE: 1
+INSTRUMENTATION_STATUS: com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer.v2=[androidx.test.filters.SmallTest(), io.qameta.allure.kotlin.Severity(value=critical), io.qameta.allure.kotlin.Story(value=Slow), org.junit.Test(expected=class org.junit.Test$None:timeout=0), io.qameta.allure.kotlin.Owner(value=user2), io.qameta.allure.kotlin.Feature(value=Text on main screen), io.qameta.allure.kotlin.Epic(value=General), org.junit.runner.RunWith(value=class io.qameta.allure.android.runners.AllureAndroidJUnit4), kotlin.Metadata(bytecodeVersion=[I@bdf6b25:data1=[Ljava.lang.String;@46414fa:data2=[Ljava.lang.String;@5d4aab:extraInt=0:extraString=:kind=1:metadataVersion=[I@fbb1508:packageName=), io.qameta.allure.kotlin.Severity(value=critical), io.qameta.allure.kotlin.Story(value=Slow)]
+INSTRUMENTATION_STATUS_CODE: 2
+INSTRUMENTATION_STATUS: class=com.example.FailedAssumptionTest
+INSTRUMENTATION_STATUS: current=4
+INSTRUMENTATION_STATUS: id=AndroidJUnitRunner
+INSTRUMENTATION_STATUS: numtests=39
+INSTRUMENTATION_STATUS: stream=.
+INSTRUMENTATION_STATUS: test=ignoreTest
+
This is useful, for example, when bytecode analysis is undesirable in complexity and it’s easier to use the Android OS itself to report back the annotations of each test.
Artifact: com.malinskiy.adam:androidx-screencapture:${LATEST_VERSION}
This producer emits test screen captures that utilise androidx.test.runner.screenshot
, e.g.:
INSTRUMENTATION_STATUS_CODE: 0
+INSTRUMENTATION_STATUS: class=com.example.FailedAssumptionTest
+INSTRUMENTATION_STATUS: current=4
+INSTRUMENTATION_STATUS: id=AndroidJUnitRunner
+INSTRUMENTATION_STATUS: numtests=39
+INSTRUMENTATION_STATUS: stream=
+com.example.FailedAssumptionTest:
+INSTRUMENTATION_STATUS: test=ignoreTest
+INSTRUMENTATION_STATUS_CODE: 1
+INSTRUMENTATION_STATUS: com.malinskiy.adam.junit4.android.screencapture.AdamScreenCaptureProcessor.v1=/sdcard/images/screenshot/screenshot-1.png
+INSTRUMENTATION_STATUS_CODE: 2
+INSTRUMENTATION_STATUS: class=com.example.FailedAssumptionTest
+INSTRUMENTATION_STATUS: current=4
+INSTRUMENTATION_STATUS: id=AndroidJUnitRunner
+INSTRUMENTATION_STATUS: numtests=39
+INSTRUMENTATION_STATUS: stream=.
+INSTRUMENTATION_STATUS: test=ignoreTest
+
This is useful to inform the test runner of the test screenshots and perhaps attach them to the test report.
Android Debug Bridge helper written in Kotlin
Get started now View it on GitHub
The only way to get access to the adb programmatically from java world currently is to use the ddmlib java project. Unfortunately it has several limitations, namely:
To optimize the resources usage adam uses coroutines instead of blocking threads. This reduced the load dramatically for scenarios where dozens of devices are connected and are communicated with. Full E2E testing with at least Android emulator is also used to guarantee stability.
shell:
support (with stdout and patched exit code)ls
sync:
gsm call
, rotate
, etc)Not to mention any device shell commands.
To add a dependency on Adam using Maven, use the following:
+<dependency>
+ <groupId>com.malinskiy.adam</groupId>
+ <artifactId>adam</artifactId>
+ <version>X.X.X</version>
+</dependency>
+
To add a dependency using Gradle:
dependencies {
+ implementation 'com.malinskiy.adam:adam:X.X.X'
+}
+
//Start the adb server
+StartAdbInteractor().execute()
+
+//Create adb client
+val adb = AndroidDebugBridgeClientFactory().build()
+
+//Execute a request
+val output = adb.execute(ShellCommandRequest("echo hello"), "emulator-5554")
+println(output) // hello
+
Adam is © 2019-2024 by Anton Malinskiy.
Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
When contributing to this repository, please first discuss the change you wish to make via issue, email, or Slack #adam with the owners of this repository before making a change.
All the operations in adam require to be executed in some coroutine scope. For simplicity, you can run everything in runBlocking{}
for trying out, but you should know/get to know coroutines and how to use them. In all the examples below the scoping will be omitted.
First, we need to make sure adb server is actually running:
StartAdbInteractor().execute()
+
Next, we create an instance of AndroidDebugBridgeClient
using the factory:
val adb = AndroidDebugBridgeClientFactory().build()
+
The AndroidDebugBridgeClient
instance adb
has an execute()
method to execute a request. Right now we don’t know what devices are connected to a particular adb server. Let’s list them and find one that we can use:
val devices: List<Device> = adb.execute(ListDevicesRequest())
+val device = devices.firstOrNull { it.state == DeviceState.DEVICE } ?: throw RuntimeException("no devices available")
+
Now we have a device and can execute a request for it:
val response: ShellCommandResult = adb.execute(ShellCommandRequest("echo hello"), device.serial)
+
All the waiting for response and establishing a transport connection happens transparently, you don’t need to wait for anything. This also doesn’t allocate new threads.
Some operations in adam require you to stream the output. One such example is streaming the logcat since this source of data will not stop producing output unless you stop reading it or the device terminates.
Here is the boilerplate from part 1 to setup the communication with the device:
StartAdbInteractor().execute()
+val adb = AndroidDebugBridgeClientFactory().build()
+val devices: List<Device> = adb.execute(ListDevicesRequest())
+val device = devices.firstOrNull { it.state == DeviceState.DEVICE } ?: throw RuntimeException("no devices available")
+
Now we need to execute the request:
val response: ReceiveChannel<String> = adb.execute(ChanneledLogcatRequest(), device.serial)
+
Pay attention to the return type ReceiveChannel<String>
. This means that we might get more instances of String
as the time goes by. In order to read the output we do the following:
do {
+ val line = channel.receiveOrNull()?.let { println(it) }
+
+ if(externalSignal) {
+ channel.cancel()
+ break
+ }
+} while (line != null)
+
First, we try to receive the output. This might succeed, then we print the string. This might fail, then we don’t print anything.
Second, we check some external signal to stop streaming logcat (user pressed a key or something else). To close the whole request we need to cancel the channel. Then we break out of the loop.
Third, we want to continue this loop until we reach other the device failure to provide us the output or we receive some external signal to stop.
There are many more options available for ChanneledLogcatRequest
that change the format of the output as well as filtering and more.
Here is the boilerplate from part 1 to setup the communication with the device:
StartAdbInteractor().execute()
+val adb = AndroidDebugBridgeClientFactory().build()
+val devices: List<Device> = adb.execute(ListDevicesRequest())
+val device = devices.firstOrNull { it.state == DeviceState.DEVICE } ?: throw RuntimeException("no devices available")
+
Now we need to execute the request. The InstallRemotePackageRequest
installs a package from file that is already available on the device. This means that we first need to transfer our package to the device:
val apkFile = File("/my/precious/application/app-debug.apk")
+val fileName = apkFile.name
+val channel = adb.execute(PushFileRequest(testFile, "/data/local/tmp/$fileName"), GlobalScope, serial = device.serial)
+while (!channel.isClosedForReceive) {
+ val progress: Double? = channel.poll()
+}
+
After executing the request we need to poll the channel for progress until the channel is closed.
Next we need to actually install this file:
val output: String = adb.execute(InstallRemotePackageRequest("/data/local/tmp/$fileName", true), serial = device.serial)
+if(!output.startsWith("Success")) throw RuntimeException("Unable to install the apk")
+
If everything is ok then the output should contain something along the lines of Success
.
Next we can verify that this package was indeed installed:
val packages: List<Package> = adb.execute(PmListRequest(), serial = device.serial)
+val pkg: Package? = packages.find { it.name == "com.example" }
+