From 5e3717c22acd85a738d6f09c86c3de168dabc7d0 Mon Sep 17 00:00:00 2001 From: GiviMAD Date: Mon, 19 Apr 2021 19:05:00 +0200 Subject: [PATCH] [androiddebugbridge] add reboot and tap channels (#10497) * [androiddebugbridge] avoid concurrent command execution Signed-off-by: Miguel * [androiddebugbridge] add reboot channel Signed-off-by: Miguel * [androiddebugbridge] add tap channel Signed-off-by: Miguel * [androiddebugbridge] validate package name Signed-off-by: Miguel * [androiddebugbridge] fix reboot channel Signed-off-by: Miguel * [androiddebugbridge] remove reboot channel and add shutdown channel Signed-off-by: Miguel * [androiddebugbridge] fix shutdown channel Signed-off-by: Miguel * [androiddebugbridge] apply spotless Signed-off-by: Miguel --- .../README.md | 2 + .../AndroidDebugBridgeBindingConstants.java | 3 + .../internal/AndroidDebugBridgeDevice.java | 90 ++++++++++++++----- .../internal/AndroidDebugBridgeHandler.java | 16 ++++ .../resources/OH-INF/thing/thing-types.xml | 20 +++++ 5 files changed, 109 insertions(+), 22 deletions(-) diff --git a/bundles/org.openhab.binding.androiddebugbridge/README.md b/bundles/org.openhab.binding.androiddebugbridge/README.md index c2c159549..cc4481507 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/README.md +++ b/bundles/org.openhab.binding.androiddebugbridge/README.md @@ -58,11 +58,13 @@ This is a sample of the mediaStateJSONConfig thing configuration: |----------|--------|------------------------------| | key-event | String | Send key event to android device. Possible values listed below | | text | String | Send text to android device | +| tap | String | Send tap event to android device (format x,y) | | media-volume | Dimmer | Set or get media volume level on android device | | media-control | Player | Control media on android device | | start-package | String | Run application by package name | | stop-package | String | Stop application by package name | | current-package | String | Package name of the top application in screen | +| shutdown | String | Power off/reboot device (allowed values POWER_OFF, REBOOT) | | wake-lock | Number | Power wake lock value | | screen-state | Switch | Screen power state | diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeBindingConstants.java b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeBindingConstants.java index 3e738d6a1..26343ecc8 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeBindingConstants.java +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeBindingConstants.java @@ -36,6 +36,7 @@ public class AndroidDebugBridgeBindingConstants { // List of all Channel ids public static final String KEY_EVENT_CHANNEL = "key-event"; public static final String TEXT_CHANNEL = "text"; + public static final String TAP_CHANNEL = "tap"; public static final String MEDIA_VOLUME_CHANNEL = "media-volume"; public static final String MEDIA_CONTROL_CHANNEL = "media-control"; public static final String START_PACKAGE_CHANNEL = "start-package"; @@ -45,6 +46,8 @@ public class AndroidDebugBridgeBindingConstants { public static final String AWAKE_STATE_CHANNEL = "awake-state"; public static final String WAKE_LOCK_CHANNEL = "wake-lock"; public static final String SCREEN_STATE_CHANNEL = "screen-state"; + public static final String SHUTDOWN_CHANNEL = "shutdown"; + // List of all Parameters public static final String PARAMETER_IP = "ip"; public static final String PARAMETER_PORT = "port"; diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java index ece2a86e9..06ee5ff23 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java @@ -24,11 +24,8 @@ import java.security.NoSuchAlgorithmException; 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; @@ -55,6 +52,9 @@ public class AndroidDebugBridgeDevice { private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeDevice.class); private static final Pattern VOLUME_PATTERN = Pattern .compile("volume is (?\\d.*) in range \\[(?\\d.*)\\.\\.(?\\d.*)]"); + private static final Pattern TAP_EVENT_PATTERN = Pattern.compile("(?\\d+),(?\\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; @@ -73,6 +73,7 @@ public class AndroidDebugBridgeDevice { } private final ScheduledExecutorService scheduler; + private final ReentrantLock commandLock = new ReentrantLock(); private String ip = "127.0.0.1"; private int port = 5555; @@ -101,8 +102,21 @@ public class AndroidDebugBridgeDevice { 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"); @@ -111,6 +125,10 @@ public class AndroidDebugBridgeDevice { 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); } @@ -240,6 +258,24 @@ public class AndroidDebugBridgeDevice { 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(); @@ -281,25 +317,35 @@ public class AndroidDebugBridgeDevice { 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() { diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java index a88daa25e..39d18c332 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java @@ -58,6 +58,8 @@ public class AndroidDebugBridgeHandler extends BaseThingHandler { public static final String KEY_EVENT_PREVIOUS = "88"; public static final String KEY_EVENT_MEDIA_REWIND = "89"; public static final String KEY_EVENT_MEDIA_FAST_FORWARD = "90"; + private static final String SHUTDOWN_POWER_OFF = "POWER_OFF"; + private static final String SHUTDOWN_REBOOT = "REBOOT"; private static final Gson GSON = new Gson(); private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeHandler.class); private final AndroidDebugBridgeDevice adbConnection; @@ -111,6 +113,9 @@ public class AndroidDebugBridgeHandler extends BaseThingHandler { case TEXT_CHANNEL: adbConnection.sendText(command.toFullString()); break; + case TAP_CHANNEL: + adbConnection.sendTap(command.toFullString()); + break; case MEDIA_VOLUME_CHANNEL: handleMediaVolume(channelUID, command); break; @@ -154,6 +159,17 @@ public class AndroidDebugBridgeHandler extends BaseThingHandler { updateState(channelUID, OnOffType.from(screenState)); } break; + case SHUTDOWN_CHANNEL: + switch (command.toFullString()) { + case SHUTDOWN_POWER_OFF: + adbConnection.powerOffDevice(); + updateStatus(ThingStatus.OFFLINE); + break; + case SHUTDOWN_REBOOT: + adbConnection.rebootDevice(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Rebooting"); + break; + } } } diff --git a/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml index 77336ba90..3641be49d 100644 --- a/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml @@ -10,6 +10,7 @@ + @@ -18,6 +19,7 @@ + serial @@ -355,6 +357,12 @@ Send text to android device + + String + + Send tap event to android device + + String @@ -380,6 +388,18 @@ + + String + + Shutdown/Reboot Device + + + + + + + + Number