Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions src/se/vidstige/jadb/FrameBuffer.java
Original file line number Diff line number Diff line change
@@ -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
* <p>
* this data can be saved as a BMP format file
* <p>
* if you want a JPEG format file ,you can convert it like this:
* <p>
* ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageData);
* <p>
* BufferedImage image = ImageIO.read(byteArrayInputStream);
* <p>
* 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 +
'}';
}


}
40 changes: 40 additions & 0 deletions src/se/vidstige/jadb/JadbConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ public List<JadbDevice> getDevices() throws IOException, JadbException {
}
}

public List<JadbDevice> 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");
Expand All @@ -77,6 +86,37 @@ public List<JadbDevice> parseDevices(String body) {
return devices;
}

public List<JadbDevice> parseDevicesList(String body) {
String[] lines = body.split("\n");
ArrayList<JadbDevice> 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);
}
Expand Down
118 changes: 94 additions & 24 deletions src/se/vidstige/jadb/JadbDevice.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,59 @@ 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;
}

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;
}
}

Expand All @@ -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;
}
Expand All @@ -73,19 +97,40 @@ 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");
return convertState(transport.readString());
}
}

/** <p>Execute a shell command.</p>
/**
* <p>Execute a shell command.</p>
*
* <p>For Lollipop and later see: {@link #execute(String, String...)}</p>
*
* @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
Expand All @@ -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.
*/
Expand All @@ -115,14 +159,15 @@ public void executeShell(OutputStream output, String command, String... args) th
}
}

/** <p>Execute a command with raw binary output.</p>
/**
* <p>Execute a command with raw binary output.</p>
*
* <p>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...)}.</p>
*
* @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
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
Loading