diff --git a/src/se/vidstige/jadb/FrameBuffer.java b/src/se/vidstige/jadb/FrameBuffer.java new file mode 100644 index 0000000..47275aa --- /dev/null +++ b/src/se/vidstige/jadb/FrameBuffer.java @@ -0,0 +1,109 @@ +package se.vidstige.jadb; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class FrameBuffer { + public int version; + public int bpp; + public int colorSpace; + public int size; + public int width; + public int height; + public int red_offset; + public int red_length; + public int blue_offset; + public int blue_length; + public int green_offset; + public int green_length; + public int alpha_offset; + public int alpha_length; + public byte[] data; + + /** + * get bitmap format data as byte array + *

+ * this data can be saved as a BMP format file + *

+ * if you want a JPEG format file ,you can convert it like this: + *

+ * ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData); + *

+ * BufferedImage image = ImageIO.read(byteArrayInputStream); + *

+ * ImageIO.write(image, "jpeg", outputStream); + */ + public byte[] getBitmapData() { + if (data == null) { + return null; + } + byte[] fileHeader = new byte[14]; + byte[] infoHeader = new byte[40]; + int imagePixelSize = width * height; + int pixelByteCount = size / imagePixelSize; + int bitmapFileSize = fileHeader.length + infoHeader.length + size; + ByteBuffer buffer = ByteBuffer.wrap(fileHeader); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.put("BM".getBytes());//bfType + buffer.putInt(bitmapFileSize);//bfSize + buffer.putInt(0);//bfReserved + buffer.putInt(fileHeader.length + infoHeader.length);//bfOffBits + buffer = ByteBuffer.wrap(infoHeader); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(infoHeader.length);//biSize + buffer.putInt(width);//biWidth + buffer.putInt(height);//biHeight + buffer.putShort((short) 1);//biPlanes + buffer.putShort((short) 32);//biBitCount + buffer.putInt(0);//biCompression + buffer.putInt(0);//biSizeImage + buffer.putInt(0);//biXPelsPerMeter + buffer.putInt(0);//biYPelsPerMeter + buffer.putInt(0);//biClrUsed + buffer.putInt(0);//biClrImportant + //reverse row data in vertical direction and for each pixel convert RGBA to BGRA + for (int i = 0; i < height / 2; i++) { + for (int j = 0; j < width; j++) { + int id1 = (i * width + j) * pixelByteCount; + int id2 = ((height - i - 1) * width + j) * pixelByteCount; + byte r = data[id1]; + byte g = data[id1 + 1]; + byte b = data[id1 + 2]; + data[id1] = data[id2 + 2]; + data[id1 + 1] = data[id2 + 1]; + data[id1 + 2] = data[id2]; + data[id2] = b; + data[id2 + 1] = g; + data[id2 + 2] = r; + } + } + + byte[] imageData = new byte[bitmapFileSize]; + System.arraycopy(fileHeader, 0, imageData, 0, fileHeader.length); + System.arraycopy(infoHeader, 0, imageData, fileHeader.length, infoHeader.length); + System.arraycopy(data, 0, imageData, fileHeader.length + infoHeader.length, data.length); + return imageData; + } + + @Override + public String toString() { + return "FrameBuffer{" + + "version=" + version + + ", bpp=" + bpp + + ", colorSpace=" + colorSpace + + ", size=" + size + + ", width=" + width + + ", height=" + height + + ", red_offset=" + red_offset + + ", red_length=" + red_length + + ", blue_offset=" + blue_offset + + ", blue_length=" + blue_length + + ", green_offset=" + green_offset + + ", green_length=" + green_length + + ", alpha_offset=" + alpha_offset + + ", alpha_length=" + alpha_length + + '}'; + } + + +} diff --git a/src/se/vidstige/jadb/JadbConnection.java b/src/se/vidstige/jadb/JadbConnection.java index c8509e1..1cb9625 100644 --- a/src/se/vidstige/jadb/JadbConnection.java +++ b/src/se/vidstige/jadb/JadbConnection.java @@ -58,6 +58,15 @@ public List getDevices() throws IOException, JadbException { } } + public List getDevicesList() throws IOException, JadbException { + try (Transport transport = createTransport()) { + transport.send("host:devices-l"); + transport.verifyResponse(); + String body = transport.readString(); + return parseDevicesList(body); + } + } + public DeviceWatcher createDeviceWatcher(DeviceDetectionListener listener) throws IOException, JadbException { Transport transport = createTransport(); transport.send("host:track-devices"); @@ -77,6 +86,37 @@ public List parseDevices(String body) { return devices; } + public List parseDevicesList(String body) { + String[] lines = body.split("\n"); + ArrayList devices = new ArrayList<>(lines.length); + for (String line : lines) { + String[] parts = line.split(" "); + if (parts.length > 1) { + String serial = parts[0]; + String usb = null; + String product = null; + String model = null; + String device = null; + String transport_id = null; + for (String info : parts) { + if (info.startsWith("usb:")) { + usb = info.substring("usb:".length()); + } else if (info.startsWith("product:")) { + product = info.substring("product:".length()); + } else if (info.startsWith("model:")) { + model = info.substring("model:".length()); + } else if (info.startsWith("device:")) { + device = info.substring("device:".length()); + } else if (info.startsWith("transport_id:")) { + transport_id = info.substring("transport_id:".length()); + } + } + devices.add(new JadbDevice(serial, usb, product, model, device, transport_id, this)); // parts[1] is type + } + } + return devices; + } + public JadbDevice getAnyDevice() { return JadbDevice.createAny(this); } diff --git a/src/se/vidstige/jadb/JadbDevice.java b/src/se/vidstige/jadb/JadbDevice.java index f72d4b5..b5966d7 100644 --- a/src/se/vidstige/jadb/JadbDevice.java +++ b/src/se/vidstige/jadb/JadbDevice.java @@ -24,11 +24,30 @@ public enum State { //noinspection OctalInteger private static final int DEFAULT_MODE = 0664; private final String serial; + private final String usb; + private final String product; + private final String model; + private final String device; + private final String transport_id; private final ITransportFactory transportFactory; private static final int DEFAULT_TCPIP_PORT = 5555; + + private JadbDevice(ITransportFactory tFactory) { + this(null, null, null, null, null, null, tFactory); + } + JadbDevice(String serial, ITransportFactory tFactory) { + this(serial, null, null, null, null, null, tFactory); + } + + JadbDevice(String serial, String usb, String product, String model, String device, String transport_id, ITransportFactory tFactory) { this.serial = serial; + this.usb = usb; + this.product = product; + this.model = model; + this.device = device; + this.transport_id = transport_id; this.transportFactory = tFactory; } @@ -36,23 +55,28 @@ static JadbDevice createAny(JadbConnection connection) { return new JadbDevice(connection); } - private JadbDevice(ITransportFactory tFactory) { - serial = null; - this.transportFactory = tFactory; - } - private State convertState(String type) { switch (type) { - case "device": return State.Device; - case "offline": return State.Offline; - case "bootloader": return State.BootLoader; - case "recovery": return State.Recovery; - case "unauthorized": return State.Unauthorized; - case "authorizing" : return State.Authorizing; - case "connecting": return State.Connecting; - case "sideload": return State.Sideload; - case "rescue" : return State.Rescue; - default: return State.Unknown; + case "device": + return State.Device; + case "offline": + return State.Offline; + case "bootloader": + return State.BootLoader; + case "recovery": + return State.Recovery; + case "unauthorized": + return State.Unauthorized; + case "authorizing": + return State.Authorizing; + case "connecting": + return State.Connecting; + case "sideload": + return State.Sideload; + case "rescue": + return State.Rescue; + default: + return State.Unknown; } } @@ -61,8 +85,8 @@ private Transport getTransport() throws IOException, JadbException { // Do not use try-with-resources here. We want to return unclosed Transport and it is up to caller // to close it. Here we close it only in case of exception. try { - send(transport, serial == null ? "host:transport-any" : "host:transport:" + serial ); - } catch (IOException|JadbException e) { + send(transport, serial == null ? "host:transport-any" : "host:transport:" + serial); + } catch (IOException | JadbException e) { transport.close(); throw e; } @@ -73,6 +97,26 @@ public String getSerial() { return serial; } + public String getUsb() { + return usb; + } + + public String getProduct() { + return product; + } + + public String getModel() { + return model; + } + + public String getDevice() { + return device; + } + + public String getTransport_id() { + return transport_id; + } + public State getState() throws IOException, JadbException { try (Transport transport = transportFactory.createTransport()) { send(transport, serial == null ? "host:get-state" : "host-serial:" + serial + ":get-state"); @@ -80,12 +124,13 @@ public State getState() throws IOException, JadbException { } } - /**

Execute a shell command.

+ /** + *

Execute a shell command.

* *

For Lollipop and later see: {@link #execute(String, String...)}

* * @param command main command to run. E.g. "ls" - * @param args arguments to the command. + * @param args arguments to the command. * @return combined stdout/stderr stream. * @throws IOException * @throws JadbException @@ -98,7 +143,6 @@ public InputStream executeShell(String command, String... args) throws IOExcepti } /** - * * @deprecated Use InputStream executeShell(String command, String... args) method instead. Together with * Stream.copy(in, out), it is possible to achieve the same effect. */ @@ -115,14 +159,15 @@ public void executeShell(OutputStream output, String command, String... args) th } } - /**

Execute a command with raw binary output.

+ /** + *

Execute a command with raw binary output.

* *

Support for this command was added in Lollipop (Android 5.0), and is the recommended way to transmit binary * data with that version or later. For earlier versions of Android, use * {@link #executeShell(String, String...)}.

* * @param command main command to run, e.g. "screencap" - * @param args arguments to the command, e.g. "-p". + * @param args arguments to the command, e.g. "-p". * @return combined stdout/stderr stream. * @throws IOException * @throws JadbException @@ -138,7 +183,7 @@ public InputStream execute(String command, String... args) throws IOException, J * Builds a command line string from the command and its arguments. * * @param command the command. - * @param args the list of arguments. + * @param args the list of arguments. * @return the command line. */ private StringBuilder buildCmdLine(String command, String... args) { @@ -163,7 +208,6 @@ public void enableAdbOverTCP() throws IOException, JadbException { * Enable tcpip on a specific port * * @param port for the device to bind on - * * @return success or failure */ public void enableAdbOverTCP(int port) throws IOException, JadbException { @@ -218,6 +262,32 @@ public void pull(RemoteFile remote, File local) throws IOException, JadbExceptio } } + public FrameBuffer screenshot() throws IOException, JadbException { + Transport transport = getTransport(); + send(transport, "framebuffer:"); + FrameBuffer frameBuffer = new FrameBuffer(); + frameBuffer.version = transport.readInt(); + frameBuffer.bpp = transport.readInt(); + frameBuffer.colorSpace = transport.readInt(); + frameBuffer.size = transport.readInt(); + frameBuffer.width = transport.readInt(); + frameBuffer.height = transport.readInt(); + frameBuffer.red_offset = transport.readInt(); + frameBuffer.red_length = transport.readInt(); + frameBuffer.blue_offset = transport.readInt(); + frameBuffer.blue_length = transport.readInt(); + frameBuffer.green_offset = transport.readInt(); + frameBuffer.green_length = transport.readInt(); + frameBuffer.alpha_offset = transport.readInt(); + frameBuffer.alpha_length = transport.readInt(); + byte[] imageData = new byte[frameBuffer.size]; + DataInputStream inputStream = transport.getDataInputStream(); + inputStream.readFully(imageData); + inputStream.close(); + frameBuffer.data = imageData; + return frameBuffer; + } + private void send(Transport transport, String command) throws IOException, JadbException { transport.send(command); transport.verifyResponse(); diff --git a/src/se/vidstige/jadb/RemoteFile.java b/src/se/vidstige/jadb/RemoteFile.java index 48ef23c..0d1914a 100644 --- a/src/se/vidstige/jadb/RemoteFile.java +++ b/src/se/vidstige/jadb/RemoteFile.java @@ -1,19 +1,37 @@ package se.vidstige.jadb; +import java.io.File; + /** * Created by vidstige on 2014-03-20 */ public class RemoteFile { private final String path; - public RemoteFile(String path) { this.path = path; } + public RemoteFile(String path) { + this.path = path; + } + + public String getName() { + int var1 = path.lastIndexOf(File.separatorChar); + return var1 < 0 ? path : path.substring(var1 + 1); + } + + public int getSize() { + throw new UnsupportedOperationException(); + } - public String getName() { throw new UnsupportedOperationException(); } - public int getSize() { throw new UnsupportedOperationException(); } - public int getLastModified() { throw new UnsupportedOperationException(); } - public boolean isDirectory() { throw new UnsupportedOperationException(); } + public int getLastModified() { + throw new UnsupportedOperationException(); + } - public String getPath() { return path;} + public boolean isDirectory() { + throw new UnsupportedOperationException(); + } + + public String getPath() { + return path; + } @Override public boolean equals(Object o) { diff --git a/src/se/vidstige/jadb/Transport.java b/src/se/vidstige/jadb/Transport.java index 72a828c..006b76d 100644 --- a/src/se/vidstige/jadb/Transport.java +++ b/src/se/vidstige/jadb/Transport.java @@ -2,6 +2,8 @@ import java.io.*; import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; class Transport implements Closeable { @@ -36,6 +38,10 @@ public InputStream getInputStream() { return inputStream; } + public DataInputStream getDataInputStream() { + return dataInput; + } + public void verifyResponse() throws IOException, JadbException { String response = readString(4); if (!"OKAY".equals(response)) { @@ -50,6 +56,14 @@ public String readString(int length) throws IOException { return new String(responseBuffer, StandardCharsets.UTF_8); } + public int readInt() throws IOException { + byte[] responseBuffer = new byte[4]; + dataInput.readFully(responseBuffer); + ByteBuffer buffer = ByteBuffer.wrap(responseBuffer); + buffer.order(ByteOrder.LITTLE_ENDIAN); + return buffer.getInt(); + } + private String getCommandLength(String command) { return String.format("%04x", command.getBytes().length); } @@ -61,6 +75,11 @@ public void send(String command) throws IOException { writer.flush(); } + public void sendBytes(byte[] bytes) throws IOException { + outputStream.write(bytes); + outputStream.flush(); + } + public SyncTransport startSync() throws IOException, JadbException { send("sync:"); verifyResponse();