]> git.basschouten.com Git - openhab-addons.git/commitdiff
[androiddebugbridge] add reboot and tap channels (#10497)
authorGiviMAD <GiviMAD@users.noreply.github.com>
Mon, 19 Apr 2021 17:05:00 +0000 (19:05 +0200)
committerGitHub <noreply@github.com>
Mon, 19 Apr 2021 17:05:00 +0000 (19:05 +0200)
* [androiddebugbridge] avoid concurrent command execution

Signed-off-by: Miguel <miguelwork92@gmail.com>
* [androiddebugbridge] add reboot channel

Signed-off-by: Miguel <miguelwork92@gmail.com>
* [androiddebugbridge] add tap channel

Signed-off-by: Miguel <miguelwork92@gmail.com>
* [androiddebugbridge] validate package name

Signed-off-by: Miguel <miguelwork92@gmail.com>
* [androiddebugbridge] fix reboot channel

Signed-off-by: Miguel <miguelwork92@gmail.com>
* [androiddebugbridge] remove reboot channel and add shutdown channel

Signed-off-by: Miguel <miguelwork92@gmail.com>
* [androiddebugbridge] fix shutdown channel

Signed-off-by: Miguel <miguelwork92@gmail.com>
* [androiddebugbridge] apply spotless

Signed-off-by: Miguel <miguelwork92@gmail.com>
bundles/org.openhab.binding.androiddebugbridge/README.md
bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeBindingConstants.java
bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeDevice.java
bundles/org.openhab.binding.androiddebugbridge/src/main/java/org/openhab/binding/androiddebugbridge/internal/AndroidDebugBridgeHandler.java
bundles/org.openhab.binding.androiddebugbridge/src/main/resources/OH-INF/thing/thing-types.xml

index c2c159549783d29502b17b0e3d364e0d3fc193cc..cc44815071c3f56de71ac155c32cf7e15384e482 100644 (file)
@@ -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 |
 
index 3e738d6a114fc638daf722208ced00f1ed9df236..26343ecc8c873ab32827c08b061e00fca86f29fb 100644 (file)
@@ -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";
index ece2a86e994156b81129e5e90af752d26936a740..06ee5ff2367fb3f055155524b19a91e6c3ef829e 100644 (file)
@@ -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 (?<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;
 
@@ -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() {
index a88daa25eeef1da1b4effe07c965cbdf80a91881..39d18c33295f8f8ee8b435ef60ed1fb7ad7af042 100644 (file)
@@ -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;
+                }
         }
     }
 
index 77336ba900f9815840ef49b03262ff6900f4154b..3641be49d2b876231b3d163df3d712598e4ebddd 100644 (file)
@@ -10,6 +10,7 @@
                <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"/>
@@ -18,6 +19,7 @@
                        <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>