[androiddebugbridge] add reboot and tap channels (#10497)
* [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>
This commit is contained in:
parent
fb1d6a283b
commit
5e3717c22a
|
@ -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 |
|
| key-event | String | Send key event to android device. Possible values listed below |
|
||||||
| text | String | Send text to android device |
|
| 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-volume | Dimmer | Set or get media volume level on android device |
|
||||||
| media-control | Player | Control media on android device |
|
| media-control | Player | Control media on android device |
|
||||||
| start-package | String | Run application by package name |
|
| start-package | String | Run application by package name |
|
||||||
| stop-package | String | Stop application by package name |
|
| stop-package | String | Stop application by package name |
|
||||||
| current-package | String | Package name of the top application in screen |
|
| 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 |
|
| wake-lock | Number | Power wake lock value |
|
||||||
| screen-state | Switch | Screen power state |
|
| screen-state | Switch | Screen power state |
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ public class AndroidDebugBridgeBindingConstants {
|
||||||
// List of all Channel ids
|
// List of all Channel ids
|
||||||
public static final String KEY_EVENT_CHANNEL = "key-event";
|
public static final String KEY_EVENT_CHANNEL = "key-event";
|
||||||
public static final String TEXT_CHANNEL = "text";
|
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_VOLUME_CHANNEL = "media-volume";
|
||||||
public static final String MEDIA_CONTROL_CHANNEL = "media-control";
|
public static final String MEDIA_CONTROL_CHANNEL = "media-control";
|
||||||
public static final String START_PACKAGE_CHANNEL = "start-package";
|
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 AWAKE_STATE_CHANNEL = "awake-state";
|
||||||
public static final String WAKE_LOCK_CHANNEL = "wake-lock";
|
public static final String WAKE_LOCK_CHANNEL = "wake-lock";
|
||||||
public static final String SCREEN_STATE_CHANNEL = "screen-state";
|
public static final String SCREEN_STATE_CHANNEL = "screen-state";
|
||||||
|
public static final String SHUTDOWN_CHANNEL = "shutdown";
|
||||||
|
|
||||||
// List of all Parameters
|
// List of all Parameters
|
||||||
public static final String PARAMETER_IP = "ip";
|
public static final String PARAMETER_IP = "ip";
|
||||||
public static final String PARAMETER_PORT = "port";
|
public static final String PARAMETER_PORT = "port";
|
||||||
|
|
|
@ -24,11 +24,8 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -55,6 +52,9 @@ public class AndroidDebugBridgeDevice {
|
||||||
private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeDevice.class);
|
private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeDevice.class);
|
||||||
private static final Pattern VOLUME_PATTERN = Pattern
|
private static final Pattern VOLUME_PATTERN = Pattern
|
||||||
.compile("volume is (?<current>\\d.*) in range \\[(?<min>\\d.*)\\.\\.(?<max>\\d.*)]");
|
.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 static @Nullable AdbCrypto adbCrypto;
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ public class AndroidDebugBridgeDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
|
private final ReentrantLock commandLock = new ReentrantLock();
|
||||||
|
|
||||||
private String ip = "127.0.0.1";
|
private String ip = "127.0.0.1";
|
||||||
private int port = 5555;
|
private int port = 5555;
|
||||||
|
@ -101,8 +102,21 @@ public class AndroidDebugBridgeDevice {
|
||||||
runAdbShell("input", "text", URLEncoder.encode(text, StandardCharsets.UTF_8));
|
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)
|
public void startPackage(String packageName)
|
||||||
throws InterruptedException, AndroidDebugBridgeDeviceException, TimeoutException, ExecutionException {
|
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");
|
var out = runAdbShell("monkey", "--pct-syskeys", "0", "-p", packageName, "-v", "1");
|
||||||
if (out.contains("monkey aborted")) {
|
if (out.contains("monkey aborted")) {
|
||||||
throw new AndroidDebugBridgeDeviceException("Unable to open package");
|
throw new AndroidDebugBridgeDeviceException("Unable to open package");
|
||||||
|
@ -111,6 +125,10 @@ public class AndroidDebugBridgeDevice {
|
||||||
|
|
||||||
public void stopPackage(String packageName)
|
public void stopPackage(String packageName)
|
||||||
throws AndroidDebugBridgeDeviceException, InterruptedException, TimeoutException, ExecutionException {
|
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);
|
runAdbShell("am", "force-stop", packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +258,24 @@ public class AndroidDebugBridgeDevice {
|
||||||
return volumeInfo;
|
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() {
|
public boolean isConnected() {
|
||||||
var currentSocket = socket;
|
var currentSocket = socket;
|
||||||
return currentSocket != null && currentSocket.isConnected();
|
return currentSocket != null && currentSocket.isConnected();
|
||||||
|
@ -281,25 +317,35 @@ public class AndroidDebugBridgeDevice {
|
||||||
if (adb == null) {
|
if (adb == null) {
|
||||||
throw new AndroidDebugBridgeDeviceException("Device not connected");
|
throw new AndroidDebugBridgeDeviceException("Device not connected");
|
||||||
}
|
}
|
||||||
var commandFuture = scheduler.submit(() -> {
|
try {
|
||||||
var byteArrayOutputStream = new ByteArrayOutputStream();
|
commandLock.lock();
|
||||||
String cmd = String.join(" ", args);
|
var commandFuture = scheduler.submit(() -> {
|
||||||
logger.debug("{} - shell:{}", ip, cmd);
|
var byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
try {
|
String cmd = String.join(" ", args);
|
||||||
AdbStream stream = adb.open("shell:" + cmd);
|
logger.debug("{} - shell:{}", ip, cmd);
|
||||||
do {
|
try {
|
||||||
byteArrayOutputStream.writeBytes(stream.read());
|
AdbStream stream = adb.open("shell:" + cmd);
|
||||||
} while (!stream.isClosed());
|
do {
|
||||||
} catch (IOException e) {
|
byteArrayOutputStream.writeBytes(stream.read());
|
||||||
String message = e.getMessage();
|
} while (!stream.isClosed());
|
||||||
if (message != null && !message.equals("Stream closed")) {
|
} catch (IOException e) {
|
||||||
throw 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);
|
commandLock.unlock();
|
||||||
});
|
}
|
||||||
this.commandFuture = commandFuture;
|
|
||||||
return commandFuture.get(timeoutSec, TimeUnit.SECONDS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AdbBase64 getBase64Impl() {
|
private static AdbBase64 getBase64Impl() {
|
||||||
|
|
|
@ -58,6 +58,8 @@ public class AndroidDebugBridgeHandler extends BaseThingHandler {
|
||||||
public static final String KEY_EVENT_PREVIOUS = "88";
|
public static final String KEY_EVENT_PREVIOUS = "88";
|
||||||
public static final String KEY_EVENT_MEDIA_REWIND = "89";
|
public static final String KEY_EVENT_MEDIA_REWIND = "89";
|
||||||
public static final String KEY_EVENT_MEDIA_FAST_FORWARD = "90";
|
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 static final Gson GSON = new Gson();
|
||||||
private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(AndroidDebugBridgeHandler.class);
|
||||||
private final AndroidDebugBridgeDevice adbConnection;
|
private final AndroidDebugBridgeDevice adbConnection;
|
||||||
|
@ -111,6 +113,9 @@ public class AndroidDebugBridgeHandler extends BaseThingHandler {
|
||||||
case TEXT_CHANNEL:
|
case TEXT_CHANNEL:
|
||||||
adbConnection.sendText(command.toFullString());
|
adbConnection.sendText(command.toFullString());
|
||||||
break;
|
break;
|
||||||
|
case TAP_CHANNEL:
|
||||||
|
adbConnection.sendTap(command.toFullString());
|
||||||
|
break;
|
||||||
case MEDIA_VOLUME_CHANNEL:
|
case MEDIA_VOLUME_CHANNEL:
|
||||||
handleMediaVolume(channelUID, command);
|
handleMediaVolume(channelUID, command);
|
||||||
break;
|
break;
|
||||||
|
@ -154,6 +159,17 @@ public class AndroidDebugBridgeHandler extends BaseThingHandler {
|
||||||
updateState(channelUID, OnOffType.from(screenState));
|
updateState(channelUID, OnOffType.from(screenState));
|
||||||
}
|
}
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
<channels>
|
<channels>
|
||||||
<channel id="key-event" typeId="key-event-channel"/>
|
<channel id="key-event" typeId="key-event-channel"/>
|
||||||
<channel id="text" typeId="text-channel"/>
|
<channel id="text" typeId="text-channel"/>
|
||||||
|
<channel id="tap" typeId="tap-channel"/>
|
||||||
<channel id="media-volume" typeId="system.volume"/>
|
<channel id="media-volume" typeId="system.volume"/>
|
||||||
<channel id="media-control" typeId="system.media-control"/>
|
<channel id="media-control" typeId="system.media-control"/>
|
||||||
<channel id="start-package" typeId="start-package-channel"/>
|
<channel id="start-package" typeId="start-package-channel"/>
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
<channel id="current-package" typeId="current-package-channel"/>
|
<channel id="current-package" typeId="current-package-channel"/>
|
||||||
<channel id="wake-lock" typeId="wake-lock-channel"/>
|
<channel id="wake-lock" typeId="wake-lock-channel"/>
|
||||||
<channel id="screen-state" typeId="screen-state-channel"/>
|
<channel id="screen-state" typeId="screen-state-channel"/>
|
||||||
|
<channel id="shutdown" typeId="shutdown-channel"/>
|
||||||
<channel id="awake-state" typeId="awake-state-channel"/>
|
<channel id="awake-state" typeId="awake-state-channel"/>
|
||||||
</channels>
|
</channels>
|
||||||
<representation-property>serial</representation-property>
|
<representation-property>serial</representation-property>
|
||||||
|
@ -355,6 +357,12 @@
|
||||||
<description>Send text to android device</description>
|
<description>Send text to android device</description>
|
||||||
</channel-type>
|
</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">
|
<channel-type id="start-package-channel">
|
||||||
<item-type>String</item-type>
|
<item-type>String</item-type>
|
||||||
<label>Start Package</label>
|
<label>Start Package</label>
|
||||||
|
@ -380,6 +388,18 @@
|
||||||
<state readOnly="true"/>
|
<state readOnly="true"/>
|
||||||
</channel-type>
|
</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">
|
<channel-type id="wake-lock-channel" advanced="true">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Wake Lock</label>
|
<label>Wake Lock</label>
|
||||||
|
|
Loading…
Reference in New Issue