import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeDevice.class);
private static final Pattern VOLUME_PATTERN = Pattern
.compile("volume is (?<current>\\d.*) in range \\[(?<min>\\d.*)\\.\\.(?<max>\\d.*)]");
+ private static final Pattern TAP_EVENT_PATTERN = Pattern.compile("(?<x>\\d+),(?<y>\\d+)");
+ private static final Pattern PACKAGE_NAME_PATTERN = Pattern
+ .compile("^([A-Za-z]{1}[A-Za-z\\d_]*\\.)+[A-Za-z][A-Za-z\\d_]*$");
private static @Nullable AdbCrypto adbCrypto;
}
private final ScheduledExecutorService scheduler;
+ private final ReentrantLock commandLock = new ReentrantLock();
private String ip = "127.0.0.1";
private int port = 5555;
runAdbShell("input", "text", URLEncoder.encode(text, StandardCharsets.UTF_8));
}
+ public void sendTap(String point)
+ throws AndroidDebugBridgeDeviceException, InterruptedException, TimeoutException, ExecutionException {
+ var match = TAP_EVENT_PATTERN.matcher(point);
+ if (!match.matches()) {
+ throw new AndroidDebugBridgeDeviceException("Unable to parse tap event");
+ }
+ runAdbShell("input", "mouse", "tap", match.group("x"), match.group("y"));
+ }
+
public void startPackage(String packageName)
throws InterruptedException, AndroidDebugBridgeDeviceException, TimeoutException, ExecutionException {
+ if (!PACKAGE_NAME_PATTERN.matcher(packageName).matches()) {
+ logger.warn("{} is not a valid package name", packageName);
+ return;
+ }
var out = runAdbShell("monkey", "--pct-syskeys", "0", "-p", packageName, "-v", "1");
if (out.contains("monkey aborted")) {
throw new AndroidDebugBridgeDeviceException("Unable to open package");
public void stopPackage(String packageName)
throws AndroidDebugBridgeDeviceException, InterruptedException, TimeoutException, ExecutionException {
+ if (!PACKAGE_NAME_PATTERN.matcher(packageName).matches()) {
+ logger.warn("{} is not a valid package name", packageName);
+ return;
+ }
runAdbShell("am", "force-stop", packageName);
}
return volumeInfo;
}
+ public void rebootDevice()
+ throws AndroidDebugBridgeDeviceException, InterruptedException, TimeoutException, ExecutionException {
+ try {
+ runAdbShell("reboot", "&", "sleep", "0.1", "&&", "exit");
+ } finally {
+ disconnect();
+ }
+ }
+
+ public void powerOffDevice()
+ throws AndroidDebugBridgeDeviceException, InterruptedException, TimeoutException, ExecutionException {
+ try {
+ runAdbShell("reboot", "-p", "&", "sleep", "0.1", "&&", "exit");
+ } finally {
+ disconnect();
+ }
+ }
+
public boolean isConnected() {
var currentSocket = socket;
return currentSocket != null && currentSocket.isConnected();
if (adb == null) {
throw new AndroidDebugBridgeDeviceException("Device not connected");
}
- var commandFuture = scheduler.submit(() -> {
- var byteArrayOutputStream = new ByteArrayOutputStream();
- String cmd = String.join(" ", args);
- logger.debug("{} - shell:{}", ip, cmd);
- try {
- AdbStream stream = adb.open("shell:" + cmd);
- do {
- byteArrayOutputStream.writeBytes(stream.read());
- } while (!stream.isClosed());
- } catch (IOException e) {
- String message = e.getMessage();
- if (message != null && !message.equals("Stream closed")) {
- throw e;
+ try {
+ commandLock.lock();
+ var commandFuture = scheduler.submit(() -> {
+ var byteArrayOutputStream = new ByteArrayOutputStream();
+ String cmd = String.join(" ", args);
+ logger.debug("{} - shell:{}", ip, cmd);
+ try {
+ AdbStream stream = adb.open("shell:" + cmd);
+ do {
+ byteArrayOutputStream.writeBytes(stream.read());
+ } while (!stream.isClosed());
+ } catch (IOException e) {
+ String message = e.getMessage();
+ if (message != null && !message.equals("Stream closed")) {
+ throw e;
+ }
}
+ return byteArrayOutputStream.toString(StandardCharsets.US_ASCII);
+ });
+ this.commandFuture = commandFuture;
+ return commandFuture.get(timeoutSec, TimeUnit.SECONDS);
+ } finally {
+ var commandFuture = this.commandFuture;
+ if (commandFuture != null) {
+ commandFuture.cancel(true);
+ this.commandFuture = null;
}
- return byteArrayOutputStream.toString(StandardCharsets.US_ASCII);
- });
- this.commandFuture = commandFuture;
- return commandFuture.get(timeoutSec, TimeUnit.SECONDS);
+ commandLock.unlock();
+ }
}
private static AdbBase64 getBase64Impl() {
<channels>
<channel id="key-event" typeId="key-event-channel"/>
<channel id="text" typeId="text-channel"/>
+ <channel id="tap" typeId="tap-channel"/>
<channel id="media-volume" typeId="system.volume"/>
<channel id="media-control" typeId="system.media-control"/>
<channel id="start-package" typeId="start-package-channel"/>
<channel id="current-package" typeId="current-package-channel"/>
<channel id="wake-lock" typeId="wake-lock-channel"/>
<channel id="screen-state" typeId="screen-state-channel"/>
+ <channel id="shutdown" typeId="shutdown-channel"/>
<channel id="awake-state" typeId="awake-state-channel"/>
</channels>
<representation-property>serial</representation-property>
<description>Send text to android device</description>
</channel-type>
+ <channel-type id="tap-channel">
+ <item-type>String</item-type>
+ <label>Send Tap</label>
+ <description>Send tap event to android device</description>
+ </channel-type>
+
<channel-type id="start-package-channel">
<item-type>String</item-type>
<label>Start Package</label>
<state readOnly="true"/>
</channel-type>
+ <channel-type id="shutdown-channel" advanced="true">
+ <item-type>String</item-type>
+ <label>Shutdown</label>
+ <description>Shutdown/Reboot Device</description>
+ <state>
+ <options>
+ <option value="POWER_OFF">POWER_OFF</option>
+ <option value="REBOOT">REBOOT</option>
+ </options>
+ </state>
+ </channel-type>
+
<channel-type id="wake-lock-channel" advanced="true">
<item-type>Number</item-type>
<label>Wake Lock</label>