diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java
index b4c1dddac..756cf9170 100644
--- a/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java
+++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java
@@ -33,7 +33,6 @@ import jadx.gui.device.debugger.SmaliDebugger.RuntimeField;
import jadx.gui.device.debugger.SmaliDebugger.RuntimeRegister;
import jadx.gui.device.debugger.SmaliDebugger.RuntimeValue;
import jadx.gui.device.debugger.SmaliDebugger.RuntimeVarInfo;
-import jadx.gui.device.debugger.SmaliDebugger.SmaliDebuggerException;
import jadx.gui.device.debugger.smali.Smali;
import jadx.gui.device.debugger.smali.SmaliRegister;
import jadx.gui.treemodel.JClass;
@@ -42,8 +41,6 @@ import jadx.gui.ui.panel.JDebuggerPanel;
import jadx.gui.ui.panel.JDebuggerPanel.IListElement;
import jadx.gui.ui.panel.JDebuggerPanel.ValueTreeNode;
-import static jadx.gui.device.debugger.SmaliDebugger.RuntimeType;
-
public final class DebugController implements SmaliDebugger.SuspendListener, IDebugController {
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
@@ -359,7 +356,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
}
@Override
- public void onSuspendEvent(SmaliDebugger.SuspendInfo info) {
+ public void onSuspendEvent(SuspendInfo info) {
if (!isDebugging()) {
return;
}
diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/EventListenerAdapter.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/EventListenerAdapter.java
new file mode 100644
index 000000000..aad2ebeed
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/EventListenerAdapter.java
@@ -0,0 +1,76 @@
+package jadx.gui.device.debugger;
+
+import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent;
+import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent;
+import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent;
+import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent;
+import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent;
+import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent;
+import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent;
+import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent;
+import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
+import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent;
+import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent;
+import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent;
+import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent;
+import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent;
+import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent;
+import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent;
+import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent;
+import io.github.hqktech.JDWP.Event.Composite.VMStartEvent;
+
+abstract class EventListenerAdapter {
+ void onVMStart(VMStartEvent event) {
+ }
+
+ void onVMDeath(VMDeathEvent event) {
+ }
+
+ void onSingleStep(SingleStepEvent event) {
+ }
+
+ void onBreakpoint(BreakpointEvent event) {
+ }
+
+ void onMethodEntry(MethodEntryEvent event) {
+ }
+
+ void onMethodExit(MethodExitEvent event) {
+ }
+
+ void onMethodExitWithReturnValue(MethodExitWithReturnValueEvent event) {
+ }
+
+ void onMonitorContendedEnter(MonitorContendedEnterEvent event) {
+ }
+
+ void onMonitorContendedEntered(MonitorContendedEnteredEvent event) {
+ }
+
+ void onMonitorWait(MonitorWaitEvent event) {
+ }
+
+ void onMonitorWaited(MonitorWaitedEvent event) {
+ }
+
+ void onException(ExceptionEvent event) {
+ }
+
+ void onThreadStart(ThreadStartEvent event) {
+ }
+
+ void onThreadDeath(ThreadDeathEvent event) {
+ }
+
+ void onClassPrepare(ClassPrepareEvent event) {
+ }
+
+ void onClassUnload(ClassUnloadEvent event) {
+ }
+
+ void onFieldAccess(FieldAccessEvent event) {
+ }
+
+ void onFieldModification(FieldModificationEvent event) {
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/RuntimeType.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/RuntimeType.java
new file mode 100644
index 000000000..6833085bd
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/RuntimeType.java
@@ -0,0 +1,84 @@
+package jadx.gui.device.debugger;
+
+import io.github.hqktech.JDWP;
+
+public enum RuntimeType {
+ ARRAY(91, "[]"),
+ BYTE(66, "byte"),
+ CHAR(67, "char"),
+ OBJECT(76, "object"),
+ FLOAT(70, "float"),
+ DOUBLE(68, "double"),
+ INT(73, "int"),
+ LONG(74, "long"),
+ SHORT(83, "short"),
+ VOID(86, "void"),
+ BOOLEAN(90, "boolean"),
+ STRING(115, "string"),
+ THREAD(116, "thread"),
+ THREAD_GROUP(103, "thread_group"),
+ CLASS_LOADER(108, "class_loader"),
+ CLASS_OBJECT(99, "class_object");
+
+ private final int jdwpTag;
+ private final String desc;
+
+ RuntimeType(int tag, String desc) {
+ this.jdwpTag = tag;
+ this.desc = desc;
+ }
+
+ public int getTag() {
+ return jdwpTag;
+ }
+
+ public String getDesc() {
+ return this.desc;
+ }
+
+ /**
+ * Converts a JDWP.Tag to a {@link RuntimeType}
+ *
+ * @param tag
+ * @return
+ * @throws SmaliDebuggerException
+ */
+ public static RuntimeType fromJdwpTag(int tag) throws SmaliDebuggerException {
+ switch (tag) {
+ case JDWP.Tag.ARRAY:
+ return RuntimeType.ARRAY;
+ case JDWP.Tag.BYTE:
+ return RuntimeType.BYTE;
+ case JDWP.Tag.CHAR:
+ return RuntimeType.CHAR;
+ case JDWP.Tag.OBJECT:
+ return RuntimeType.OBJECT;
+ case JDWP.Tag.FLOAT:
+ return RuntimeType.FLOAT;
+ case JDWP.Tag.DOUBLE:
+ return RuntimeType.DOUBLE;
+ case JDWP.Tag.INT:
+ return RuntimeType.INT;
+ case JDWP.Tag.LONG:
+ return RuntimeType.LONG;
+ case JDWP.Tag.SHORT:
+ return RuntimeType.SHORT;
+ case JDWP.Tag.VOID:
+ return RuntimeType.VOID;
+ case JDWP.Tag.BOOLEAN:
+ return RuntimeType.BOOLEAN;
+ case JDWP.Tag.STRING:
+ return RuntimeType.STRING;
+ case JDWP.Tag.THREAD:
+ return RuntimeType.THREAD;
+ case JDWP.Tag.THREAD_GROUP:
+ return RuntimeType.THREAD_GROUP;
+ case JDWP.Tag.CLASS_LOADER:
+ return RuntimeType.CLASS_LOADER;
+ case JDWP.Tag.CLASS_OBJECT:
+ return RuntimeType.CLASS_OBJECT;
+ default:
+ throw new SmaliDebuggerException("Unexpected value: " + tag);
+ }
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebugger.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebugger.java
index 1674860df..263d1faaa 100644
--- a/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebugger.java
+++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebugger.java
@@ -75,6 +75,7 @@ import io.reactivex.annotations.NonNull;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.gui.device.debugger.smali.RegisterInfo;
+import jadx.gui.utils.IOUtils;
import jadx.gui.utils.ObjectPool;
// TODO: Finish error notification, inner errors should be logged let user notice.
@@ -83,9 +84,9 @@ public class SmaliDebugger {
private static final Logger LOG = LoggerFactory.getLogger(SmaliDebugger.class);
private final JDWP jdwp;
- private int localTcpPort;
- private InputStream inputStream;
- private OutputStream outputStream;
+ private final int localTcpPort;
+ private final InputStream inputStream;
+ private final OutputStream outputStream;
// All event callbacks will be called in this queue, e.g. class prepare/unload
private static final Executor EVENT_LISTENER_QUEUE = Executors.newSingleThreadExecutor();
@@ -118,10 +119,13 @@ public class SmaliDebugger {
private static final ICommandResult SKIP_RESULT = res -> {
};
- private SmaliDebugger(SuspendListener suspendListener, int localTcpPort, JDWP jdwp) {
+ private SmaliDebugger(SuspendListener suspendListener, int localTcpPort, JDWP jdwp, InputStream inputStream,
+ OutputStream outputStream) {
this.jdwp = jdwp;
this.localTcpPort = localTcpPort;
this.suspendListener = suspendListener;
+ this.inputStream = inputStream;
+ this.outputStream = outputStream;
oneOffEventReq = jdwp.eventRequest().cmdSet().newCountRequest();
oneOffEventReq.count = 1;
@@ -135,6 +139,7 @@ public class SmaliDebugger {
try {
byte[] bytes = JDWP.IDSizes.encode().getBytes();
JDWP.setPacketID(bytes, 1);
+ LOG.debug("Connecting to ADB {}:{}", host, port);
Socket socket = new Socket(host, port);
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
@@ -143,9 +148,7 @@ public class SmaliDebugger {
JDWP jdwp = initJDWP(outputStream, inputStream);
socket.setSoTimeout(0); // set back to 0 so the decodingLoop won't break for timeout.
- SmaliDebugger debugger = new SmaliDebugger(suspendListener, port, jdwp);
- debugger.inputStream = inputStream;
- debugger.outputStream = outputStream;
+ SmaliDebugger debugger = new SmaliDebugger(suspendListener, port, jdwp, inputStream, outputStream);
debugger.decodingLoop();
debugger.listenClassUnloadEvent();
@@ -262,7 +265,7 @@ public class SmaliDebugger {
for (int i = 0; i < values.size(); i++) {
ObjectReference.GetValues.GetValuesReplyDataValues value = values.get(i);
flds.get(i).setValue(value.value.idOrValue)
- .setType(getType(value.value.tag));
+ .setType(RuntimeType.fromJdwpTag(value.value.tag));
}
}
@@ -502,7 +505,7 @@ public class SmaliDebugger {
ObjectReference.GetValues.GetValuesReplyData data =
jdwp.objectReference().cmdGetValues().decode(res.getBuf(), JDWP.PACKET_HEADER_SIZE);
fld.setValue(data.values.get(0).value.idOrValue)
- .setType(getType(data.values.get(0).value.tag));
+ .setType(RuntimeType.fromJdwpTag(data.values.get(0).value.tag));
}
private long createString(String localStr) throws SmaliDebuggerException {
@@ -636,7 +639,7 @@ public class SmaliDebugger {
byte[] buf;
try {
outputStream.write(JDWP.encodeHandShakePacket());
- buf = readBytes(inputStream, 14);
+ buf = IOUtils.readNBytes(inputStream, 14);
} catch (Exception e) {
throw new SmaliDebuggerException("jdwp handshake failed", e);
}
@@ -1128,7 +1131,7 @@ public class SmaliDebugger {
@Nullable
private static Packet readPacket(InputStream inputStream) throws SmaliDebuggerException {
try {
- byte[] header = readBytes(inputStream, JDWP.PACKET_HEADER_SIZE);
+ byte[] header = IOUtils.readNBytes(inputStream, JDWP.PACKET_HEADER_SIZE);
if (header == null) {
// stream ended
return null;
@@ -1137,7 +1140,7 @@ public class SmaliDebugger {
if (bodyLength <= 0) {
return Packet.make(header);
}
- byte[] body = readBytes(inputStream, bodyLength);
+ byte[] body = IOUtils.readNBytes(inputStream, bodyLength);
if (body == null) {
throw new SmaliDebuggerException("Stream truncated");
}
@@ -1147,21 +1150,6 @@ public class SmaliDebugger {
}
}
- private static byte[] readBytes(InputStream inputStream, int len) throws IOException {
- byte[] payload = new byte[len];
- int readSize = 0;
- while (true) {
- int read = inputStream.read(payload, readSize, len - readSize);
- if (read == -1) {
- return null;
- }
- readSize += read;
- if (readSize == len) {
- return payload;
- }
- }
- }
-
private static byte[] concatBytes(byte[] buf1, byte[] buf2) {
byte[] tempBuf = new byte[buf1.length + buf2.length];
System.arraycopy(buf1, 0, tempBuf, 0, buf1.length);
@@ -1183,62 +1171,6 @@ public class SmaliDebugger {
void onCommandReply(Packet res) throws SmaliDebuggerException;
}
- private abstract class EventListenerAdapter {
- void onVMStart(VMStartEvent event) {
- }
-
- void onVMDeath(VMDeathEvent event) {
- }
-
- void onSingleStep(SingleStepEvent event) {
- }
-
- void onBreakpoint(BreakpointEvent event) {
- }
-
- void onMethodEntry(MethodEntryEvent event) {
- }
-
- void onMethodExit(MethodExitEvent event) {
- }
-
- void onMethodExitWithReturnValue(MethodExitWithReturnValueEvent event) {
- }
-
- void onMonitorContendedEnter(MonitorContendedEnterEvent event) {
- }
-
- void onMonitorContendedEntered(MonitorContendedEnteredEvent event) {
- }
-
- void onMonitorWait(MonitorWaitEvent event) {
- }
-
- void onMonitorWaited(MonitorWaitedEvent event) {
- }
-
- void onException(ExceptionEvent event) {
- }
-
- void onThreadStart(ThreadStartEvent event) {
- }
-
- void onThreadDeath(ThreadDeathEvent event) {
- }
-
- void onClassPrepare(ClassPrepareEvent event) {
- }
-
- void onClassUnload(ClassUnloadEvent event) {
- }
-
- void onFieldAccess(FieldAccessEvent event) {
- }
-
- void onFieldModification(FieldModificationEvent event) {
- }
- }
-
public static class RuntimeField extends RuntimeValue {
private final String name;
private final String fldType;
@@ -1296,46 +1228,7 @@ public class SmaliDebugger {
}
private RuntimeRegister buildRegister(int num, int tag, ByteBuffer buf) throws SmaliDebuggerException {
- return new RuntimeRegister(num, getType(tag), buf);
- }
-
- private RuntimeType getType(int tag) throws SmaliDebuggerException {
- switch (tag) {
- case JDWP.Tag.ARRAY:
- return RuntimeType.ARRAY;
- case JDWP.Tag.BYTE:
- return RuntimeType.BYTE;
- case JDWP.Tag.CHAR:
- return RuntimeType.CHAR;
- case JDWP.Tag.OBJECT:
- return RuntimeType.OBJECT;
- case JDWP.Tag.FLOAT:
- return RuntimeType.FLOAT;
- case JDWP.Tag.DOUBLE:
- return RuntimeType.DOUBLE;
- case JDWP.Tag.INT:
- return RuntimeType.INT;
- case JDWP.Tag.LONG:
- return RuntimeType.LONG;
- case JDWP.Tag.SHORT:
- return RuntimeType.SHORT;
- case JDWP.Tag.VOID:
- return RuntimeType.VOID;
- case JDWP.Tag.BOOLEAN:
- return RuntimeType.BOOLEAN;
- case JDWP.Tag.STRING:
- return RuntimeType.STRING;
- case JDWP.Tag.THREAD:
- return RuntimeType.THREAD;
- case JDWP.Tag.THREAD_GROUP:
- return RuntimeType.THREAD_GROUP;
- case JDWP.Tag.CLASS_LOADER:
- return RuntimeType.CLASS_LOADER;
- case JDWP.Tag.CLASS_OBJECT:
- return RuntimeType.CLASS_OBJECT;
- default:
- throw new SmaliDebuggerException("Unexpected value: " + tag);
- }
+ return new RuntimeRegister(num, RuntimeType.fromJdwpTag(tag), buf);
}
public static class RuntimeValue {
@@ -1428,41 +1321,6 @@ public class SmaliDebugger {
}
}
- public enum RuntimeType {
- ARRAY(91, "[]"),
- BYTE(66, "byte"),
- CHAR(67, "char"),
- OBJECT(76, "object"),
- FLOAT(70, "float"),
- DOUBLE(68, "double"),
- INT(73, "int"),
- LONG(74, "long"),
- SHORT(83, "short"),
- VOID(86, "void"),
- BOOLEAN(90, "boolean"),
- STRING(115, "string"),
- THREAD(116, "thread"),
- THREAD_GROUP(103, "thread_group"),
- CLASS_LOADER(108, "class_loader"),
- CLASS_OBJECT(99, "class_object");
-
- private final int jdwpTag;
- private final String desc;
-
- RuntimeType(int tag, String desc) {
- this.jdwpTag = tag;
- this.desc = desc;
- }
-
- private int getTag() {
- return jdwpTag;
- }
-
- public String getDesc() {
- return this.desc;
- }
- }
-
public static class Frame {
private final long id;
private final long clsID;
@@ -1503,35 +1361,6 @@ public class SmaliDebugger {
void onUnloaded(String cls);
}
- public static class SmaliDebuggerException extends Exception {
- private final int errCode;
- private static final long serialVersionUID = -1111111202102191403L;
-
- public SmaliDebuggerException(Exception e) {
- super(e);
- errCode = -1;
- }
-
- public SmaliDebuggerException(String msg) {
- super(msg);
- this.errCode = -1;
- }
-
- public SmaliDebuggerException(String message, Throwable cause) {
- super(message, cause);
- this.errCode = -1;
- }
-
- public SmaliDebuggerException(String msg, int errCode) {
- super(msg);
- this.errCode = errCode;
- }
-
- public int getErrCode() {
- return errCode;
- }
- }
-
/**
* Listener for breakpoint, watch, step, etc.
*/
@@ -1543,99 +1372,4 @@ public class SmaliDebugger {
void onSuspendEvent(SuspendInfo current);
}
- public static class SuspendInfo {
- private boolean terminated;
- private boolean newRound;
- private final InfoSetter updater = new InfoSetter();
-
- public long getThreadID() {
- return updater.thread;
- }
-
- public long getClassID() {
- return updater.clazz;
- }
-
- public long getMethodID() {
- return updater.method;
- }
-
- public long getOffset() {
- return updater.offset;
- }
-
- private InfoSetter update() {
- updater.changed = false;
- updater.nextRound(newRound);
- this.newRound = false;
- return updater;
- }
-
- // called by decodingLoop, to tell the updater even though the values are the same,
- // they are decoded from another packet, they should be treated as new.
- private void nextRound() {
- newRound = true;
- }
-
- // according to JDWP document it's legal to fire two or more events on a same location,
- // e.g. one for single step and the other for breakpoint, so when this happened we only
- // want one of them.
- private boolean isAnythingChanged() {
- return updater.changed;
- }
-
- public boolean isTerminated() {
- return terminated;
- }
-
- private void setTerminated() {
- terminated = true;
- }
-
- private static class InfoSetter {
- private long thread;
- private long clazz;
- private long method;
- private long offset; // code offset;
- private boolean changed;
-
- private void nextRound(boolean newRound) {
- if (!changed) {
- changed = newRound;
- }
- }
-
- private InfoSetter updateThread(long thread) {
- if (!changed) {
- changed = this.thread != thread;
- }
- this.thread = thread;
- return this;
- }
-
- private InfoSetter updateClass(long clazz) {
- if (!changed) {
- changed = this.clazz != clazz;
- }
- this.clazz = clazz;
- return this;
- }
-
- private InfoSetter updateMethod(long method) {
- if (!changed) {
- changed = this.method != method;
- }
- this.method = method;
- return this;
- }
-
- private InfoSetter updateOffset(long offset) {
- if (!changed) {
- changed = this.offset != offset;
- }
- this.offset = offset;
- return this;
- }
- }
- }
}
diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebuggerException.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebuggerException.java
new file mode 100644
index 000000000..87b0594d8
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/SmaliDebuggerException.java
@@ -0,0 +1,30 @@
+package jadx.gui.device.debugger;
+
+public class SmaliDebuggerException extends Exception {
+ private final int errCode;
+ private static final long serialVersionUID = -1111111202102191403L;
+
+ public SmaliDebuggerException(Exception e) {
+ super(e);
+ errCode = -1;
+ }
+
+ public SmaliDebuggerException(String msg) {
+ super(msg);
+ this.errCode = -1;
+ }
+
+ public SmaliDebuggerException(String msg, Exception e) {
+ super(msg, e);
+ errCode = -1;
+ }
+
+ public SmaliDebuggerException(String msg, int errCode) {
+ super(msg);
+ this.errCode = errCode;
+ }
+
+ public int getErrCode() {
+ return errCode;
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/SuspendInfo.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/SuspendInfo.java
new file mode 100644
index 000000000..58e79ebe8
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/SuspendInfo.java
@@ -0,0 +1,97 @@
+package jadx.gui.device.debugger;
+
+public class SuspendInfo {
+ private boolean terminated;
+ private boolean newRound;
+ private final InfoSetter updater = new InfoSetter();
+
+ public long getThreadID() {
+ return updater.thread;
+ }
+
+ public long getClassID() {
+ return updater.clazz;
+ }
+
+ public long getMethodID() {
+ return updater.method;
+ }
+
+ public long getOffset() {
+ return updater.offset;
+ }
+
+ InfoSetter update() {
+ updater.changed = false;
+ updater.nextRound(newRound);
+ this.newRound = false;
+ return updater;
+ }
+
+ // called by decodingLoop, to tell the updater even though the values are the same,
+ // they are decoded from another packet, they should be treated as new.
+ void nextRound() {
+ newRound = true;
+ }
+
+ // according to JDWP document it's legal to fire two or more events on a same location,
+ // e.g. one for single step and the other for breakpoint, so when this happened we only
+ // want one of them.
+ boolean isAnythingChanged() {
+ return updater.changed;
+ }
+
+ public boolean isTerminated() {
+ return terminated;
+ }
+
+ void setTerminated() {
+ terminated = true;
+ }
+
+ static class InfoSetter {
+ private long thread;
+ private long clazz;
+ private long method;
+ private long offset; // code offset;
+ private boolean changed;
+
+ void nextRound(boolean newRound) {
+ if (!changed) {
+ changed = newRound;
+ }
+ }
+
+ InfoSetter updateThread(long thread) {
+ if (!changed) {
+ changed = this.thread != thread;
+ }
+ this.thread = thread;
+ return this;
+ }
+
+ InfoSetter updateClass(long clazz) {
+ if (!changed) {
+ changed = this.clazz != clazz;
+ }
+ this.clazz = clazz;
+ return this;
+ }
+
+ InfoSetter updateMethod(long method) {
+ if (!changed) {
+ changed = this.method != method;
+ }
+ this.method = method;
+ return this;
+ }
+
+ InfoSetter updateOffset(long offset) {
+ if (!changed) {
+ changed = this.offset != offset;
+ }
+ this.offset = offset;
+ return this;
+ }
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java
index d08d3c817..1c20428c5 100644
--- a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java
+++ b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADB.java
@@ -1,5 +1,6 @@
package jadx.gui.device.protocol;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -7,19 +8,17 @@ import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import io.reactivex.annotations.NonNull;
-
-import jadx.core.utils.StringUtils;
+import jadx.gui.utils.IOUtils;
public class ADB {
private static final Logger LOG = LoggerFactory.getLogger(ADB.class);
@@ -28,13 +27,11 @@ public class ADB {
private static final String DEFAULT_ADDR = "localhost";
private static final String CMD_FEATURES = "000dhost:features";
- private static final String CMD_TRACK_JDWP = "000atrack-jdwp";
private static final String CMD_TRACK_DEVICES = "0014host:track-devices-l";
private static final byte[] OKAY = "OKAY".getBytes();
- private static boolean isOkay(InputStream stream) throws IOException {
- byte[] buf = new byte[4];
- stream.read(buf, 0, 4);
+ static boolean isOkay(InputStream stream) throws IOException {
+ byte[] buf = IOUtils.readNBytes(stream, 4);
return Arrays.equals(buf, OKAY);
}
@@ -44,9 +41,9 @@ public class ADB {
public static byte[] exec(String cmd) throws IOException {
byte[] res;
- Socket socket = connect();
- res = exec(cmd, socket.getOutputStream(), socket.getInputStream());
- socket.close();
+ try (Socket socket = connect()) {
+ res = exec(cmd, socket.getOutputStream(), socket.getInputStream());
+ }
return res;
}
@@ -58,7 +55,7 @@ public class ADB {
return new Socket(host, port);
}
- private static boolean execCommandAsync(OutputStream outputStream,
+ static boolean execCommandAsync(OutputStream outputStream,
InputStream inputStream, String cmd) throws IOException {
outputStream.write(cmd.getBytes());
return isOkay(inputStream);
@@ -73,29 +70,21 @@ public class ADB {
return null;
}
- private static byte[] readServiceProtocol(InputStream stream) {
- byte[] bytes = null;
- byte[] buf = new byte[4];
+ static byte[] readServiceProtocol(InputStream stream) {
try {
- int len = stream.read(buf, 0, 4);
- if (len == 4) {
- len = unhex(buf);
- if (len == 0) {
- return new byte[0];
- }
- if (len != -1) {
- buf = new byte[len];
- if (stream.read(buf, 0, len) == len) {
- bytes = buf;
- }
- }
+ byte[] buf = IOUtils.readNBytes(stream, 4);
+ int len = unhex(buf);
+ if (len == 0) {
+ return new byte[0];
}
- } catch (IOException ignore) {
+ return IOUtils.readNBytes(stream, len);
+ } catch (IOException e) {
+ LOG.error("Failed to read readServiceProtocol: {}", e.toString());
+ return null;
}
- return bytes;
}
- private static boolean setSerial(String serial, OutputStream outputStream, InputStream inputStream) throws IOException {
+ static boolean setSerial(String serial, OutputStream outputStream, InputStream inputStream) throws IOException {
String setSerialCmd = String.format("host:tport:serial:%s", serial);
setSerialCmd = String.format("%04x%s", setSerialCmd.length(), setSerialCmd);
outputStream.write(setSerialCmd.getBytes());
@@ -107,9 +96,7 @@ public class ADB {
return ok;
}
- private static byte[] execShellCommandRaw(String cmd,
- OutputStream outputStream, InputStream inputStream) throws IOException {
-
+ private static byte[] execShellCommandRaw(String cmd, OutputStream outputStream, InputStream inputStream) throws IOException {
cmd = String.format("shell,v2,TERM=xterm-256color,raw:%s", cmd);
cmd = String.format("%04x%s", cmd.length(), cmd);
outputStream.write(cmd.getBytes());
@@ -119,7 +106,7 @@ public class ADB {
return null;
}
- private static byte[] execShellCommandRaw(String serial, String cmd,
+ static byte[] execShellCommandRaw(String serial, String cmd,
OutputStream outputStream, InputStream inputStream) throws IOException {
if (setSerial(serial, outputStream, inputStream)) {
return execShellCommandRaw(cmd, outputStream, inputStream);
@@ -148,17 +135,19 @@ public class ADB {
proc.destroyForcibly();
return false;
}
- InputStream is = proc.getInputStream();
- int size = is.available();
- byte[] bytes = new byte[size];
- is.read(bytes, 0, size);
- return new String(bytes).contains(tcpPort);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try (InputStream in = proc.getInputStream()) {
+ int read;
+ byte[] buf = new byte[1024];
+ while ((read = in.read(buf)) >= 0) {
+ out.write(buf, 0, read);
+ }
+ }
+ return new String(out.toByteArray()).contains(tcpPort);
}
public static boolean isServerRunning(String host, int port) {
- try {
- Socket sock = new Socket(host, port);
- sock.close();
+ try (Socket sock = new Socket(host, port)) {
return true;
} catch (Exception e) {
return false;
@@ -184,10 +173,10 @@ public class ADB {
if (listener != null) {
String payload = new String(res);
String[] deviceLines = payload.split("\n");
- List deviceInfoList = new ArrayList<>(deviceLines.length);
+ List deviceInfoList = new ArrayList<>(deviceLines.length);
for (String deviceLine : deviceLines) {
if (!deviceLine.trim().isEmpty()) {
- deviceInfoList.add(DeviceInfo.make(deviceLine, host, port));
+ deviceInfoList.add(ADBDeviceInfo.make(deviceLine, host, port));
}
}
listener.onDeviceStatusChange(deviceInfoList);
@@ -213,10 +202,7 @@ public class ADB {
byte[] bytes = readServiceProtocol(inputStream);
if (bytes != null) {
String[] forwards = new String(bytes).split("\n");
- List forwardList = new ArrayList<>(forwards.length);
- for (String forward : forwards) {
- forwardList.add(forward.trim());
- }
+ List forwardList = Arrays.stream(forwards).map(s -> s.trim()).collect(Collectors.toList());
socket.close();
return forwardList;
}
@@ -300,287 +286,17 @@ public class ADB {
}
public interface JDWPProcessListener {
- void jdwpProcessOccurred(Device device, Set id);
+ void jdwpProcessOccurred(ADBDevice device, Set id);
- void jdwpListenerClosed(Device device);
+ void jdwpListenerClosed(ADBDevice device);
}
public interface DeviceStateListener {
- void onDeviceStatusChange(List deviceInfoList);
+ void onDeviceStatusChange(List deviceInfoList);
void adbDisconnected();
}
- public static class Device {
- DeviceInfo info;
- String androidReleaseVer;
- volatile Socket jdwpListenerSock;
-
- public Device(DeviceInfo info) {
- this.info = info;
- }
-
- public DeviceInfo getDeviceInfo() {
- return info;
- }
-
- public boolean updateDeviceInfo(DeviceInfo info) {
- boolean matched = this.info.serial.equals(info.serial);
- if (matched) {
- this.info = info;
- }
- return matched;
- }
-
- public String getSerial() {
- return info.serial;
- }
-
- public boolean removeForward(String localPort) throws IOException {
- return ADB.removeForward(info.adbHost, info.adbPort, info.serial, localPort);
- }
-
- public ForwardResult forwardJDWP(String localPort, String jdwpPid) throws IOException {
- Socket socket = connect(info.adbHost, info.adbPort);
- String cmd = String.format("host:forward:tcp:%s;jdwp:%s", localPort, jdwpPid);
- cmd = String.format("%04x%s", cmd.length(), cmd);
- InputStream inputStream = socket.getInputStream();
- OutputStream outputStream = socket.getOutputStream();
- ForwardResult rst;
- if (setSerial(info.serial, outputStream, inputStream)) {
- outputStream.write(cmd.getBytes());
- if (!isOkay(inputStream)) {
- rst = new ForwardResult(1, readServiceProtocol(inputStream));
- } else if (!isOkay(inputStream)) {
- rst = new ForwardResult(2, readServiceProtocol(inputStream));
- } else {
- rst = new ForwardResult(0, null);
- }
- } else {
- rst = new ForwardResult(1, "Unknown error.".getBytes());
- }
- socket.close();
- return rst;
- }
-
- public static class ForwardResult {
- /**
- * 0 for success, 1 for failed at binding to local tcp, 2 for failed at remote.
- */
- public int state;
- public String desc;
-
- public ForwardResult(int state, byte[] desc) {
- if (desc != null) {
- this.desc = new String(desc);
- } else {
- this.desc = "";
- }
- this.state = state;
- }
- }
-
- /**
- * @return pid otherwise -1
- */
- public int launchApp(String fullAppName) throws IOException, InterruptedException {
- Socket socket = connect(info.adbHost, info.adbPort);
- String cmd = "am start -D -n " + fullAppName;
- byte[] res = execShellCommandRaw(info.serial, cmd, socket.getOutputStream(), socket.getInputStream());
- socket.close();
- String rst = new String(res).trim();
- if (rst.startsWith("Starting: Intent {") && rst.endsWith(fullAppName + " }")) {
- Thread.sleep(40);
- String pkg = fullAppName.split("/")[0];
- for (Process process : getProcessByPkg(pkg)) {
- return Integer.parseInt(process.pid);
- }
- }
- return -1;
- }
-
- public String getAndroidReleaseVersion() {
- if (!StringUtils.isEmpty(androidReleaseVer)) {
- return androidReleaseVer;
- }
- try {
- List list = getProp("ro.build.version.release");
- if (list.size() != 0) {
- androidReleaseVer = list.get(0);
- }
- } catch (Exception e) {
- LOG.error("Failed to get android release version", e);
- androidReleaseVer = "";
- }
- return androidReleaseVer;
- }
-
- public List getProp(String entry) throws IOException {
- Socket socket = connect(info.adbHost, info.adbPort);
- List props = Collections.emptyList();
- String cmd = "getprop";
- if (!StringUtils.isEmpty(entry)) {
- cmd += " " + entry;
- }
- byte[] payload = execShellCommandRaw(info.serial, cmd,
- socket.getOutputStream(), socket.getInputStream());
- if (payload != null) {
- props = new ArrayList<>();
- String[] lines = new String(payload).split("\n");
- for (String line : lines) {
- line = line.trim();
- if (!line.isEmpty()) {
- props.add(line.trim());
- }
- }
- }
- socket.close();
- return props;
- }
-
- public List getProcessByPkg(String pkg) throws IOException {
- return getProcessList("ps | grep " + pkg, 0);
- }
-
- @NonNull
- public List getProcessList() throws IOException {
- return getProcessList("ps", 1);
- }
-
- private List getProcessList(String cmd, int index) throws IOException {
- Socket socket = connect(info.adbHost, info.adbPort);
- List procs = Collections.emptyList();
- byte[] payload = execShellCommandRaw(info.serial, cmd,
- socket.getOutputStream(), socket.getInputStream());
- if (payload != null) {
- String ps = new String(payload);
- String[] psLines = ps.split("\n");
- for (int i = index; i < psLines.length; i++) {
- Process proc = Process.make(psLines[i]);
- if (proc != null) {
- if (procs.isEmpty()) {
- procs = new ArrayList<>();
- }
- procs.add(proc);
- }
- }
- }
- socket.close();
- return procs;
- }
-
- public boolean listenForJDWP(JDWPProcessListener listener) throws IOException {
- if (this.jdwpListenerSock != null) {
- return false;
- }
- jdwpListenerSock = connect(this.info.adbHost, this.info.adbPort);
- InputStream inputStream = jdwpListenerSock.getInputStream();
- OutputStream outputStream = jdwpListenerSock.getOutputStream();
- if (setSerial(info.serial, outputStream, inputStream)
- && execCommandAsync(outputStream, inputStream, CMD_TRACK_JDWP)) {
- Executors.newFixedThreadPool(1).execute(() -> {
- for (;;) {
- byte[] res = readServiceProtocol(inputStream);
- if (res != null) {
- if (listener != null) {
- String payload = new String(res);
- String[] ids = payload.split("\n");
- Set idList = new HashSet<>(ids.length);
- for (String id : ids) {
- if (!id.trim().isEmpty()) {
- idList.add(id);
- }
- }
- listener.jdwpProcessOccurred(this, idList);
- }
- } else { // socket disconnected
- break;
- }
- }
- if (listener != null) {
- this.jdwpListenerSock = null;
- listener.jdwpListenerClosed(this);
- }
- });
- } else {
- jdwpListenerSock.close();
- jdwpListenerSock = null;
- return false;
- }
- return true;
- }
-
- public void stopListenForJDWP() {
- if (jdwpListenerSock != null) {
- try {
- jdwpListenerSock.close();
- } catch (Exception e) {
- LOG.error("JDWP socket close failed", e);
- }
- }
- this.jdwpListenerSock = null;
- }
-
- @Override
- public int hashCode() {
- return info.serial.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof Device) {
- return ((Device) obj).getDeviceInfo().serial.equals(info.serial);
- }
- return false;
- }
-
- @Override
- public String toString() {
- return info.allInfo;
- }
- }
-
- public static class DeviceInfo {
- public String adbHost;
- public int adbPort;
- public String serial;
- public String state;
- public String model;
- public String allInfo;
-
- public boolean isOnline() {
- return state.equals("device");
- }
-
- @Override
- public String toString() {
- return allInfo;
- }
-
- static DeviceInfo make(String info, String host, int port) {
- DeviceInfo deviceInfo = new DeviceInfo();
- String[] infoFields = info.trim().split("\\s+");
- deviceInfo.allInfo = String.join(" ", infoFields);
- if (infoFields.length > 2) {
- deviceInfo.serial = infoFields[0];
- deviceInfo.state = infoFields[1];
- }
- int pos = info.indexOf("model:");
- if (pos != -1) {
- int spacePos = info.indexOf(" ", pos);
- if (spacePos != -1) {
- deviceInfo.model = info.substring(pos + "model:".length(), spacePos);
- }
- }
- if (deviceInfo.model == null || deviceInfo.model.equals("")) {
- deviceInfo.model = deviceInfo.serial;
- }
- deviceInfo.adbHost = host;
- deviceInfo.adbPort = port;
- return deviceInfo;
- }
- }
-
public static class Process {
public String user;
public String pid;
@@ -619,23 +335,23 @@ public class ADB {
public static byte[] readStdout(InputStream inputStream) throws IOException {
byte[] header = new byte[5];
- byte[] payload = new byte[0];
- byte[] tempBuf = new byte[0];
+ ByteArrayOutputStream payload = new ByteArrayOutputStream();
+ byte[] tempBuf = new byte[1024];
for (boolean exit = false; !exit;) {
- if (inputStream.read(header, 0, 5) == 5) {
- exit = header[0] == ID_EXIT;
- int payloadSize = readInt(header, 1);
- if (tempBuf.length < payloadSize) {
- tempBuf = new byte[payloadSize];
- }
- int readSize = inputStream.read(tempBuf, 0, payloadSize);
- if (readSize != payloadSize) {
- return null; // we don't want corrupted data.
- }
- payload = appendBytes(payload, tempBuf, readSize);
+ IOUtils.read(inputStream, header);
+ exit = header[0] == ID_EXIT;
+ int payloadSize = readInt(header, 1);
+ if (tempBuf.length < payloadSize) {
+ tempBuf = new byte[payloadSize];
}
+ int readSize = IOUtils.read(inputStream, tempBuf, 0, payloadSize);
+ if (readSize != payloadSize) {
+ LOG.error("Failed to read ShellProtocol data");
+ return null; // we don't want corrupted data.
+ }
+ payload.write(tempBuf, 0, readSize);
}
- return payload;
+ return payload.toByteArray();
}
}
}
diff --git a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java
new file mode 100644
index 000000000..2c06a5754
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java
@@ -0,0 +1,252 @@
+package jadx.gui.device.protocol;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.reactivex.annotations.NonNull;
+
+import jadx.core.utils.StringUtils;
+import jadx.gui.device.protocol.ADB.JDWPProcessListener;
+import jadx.gui.device.protocol.ADB.Process;
+
+public class ADBDevice {
+ private static final Logger LOG = LoggerFactory.getLogger(ADBDevice.class);
+
+ private static final String CMD_TRACK_JDWP = "000atrack-jdwp";
+
+ ADBDeviceInfo info;
+ String androidReleaseVer;
+ volatile Socket jdwpListenerSock;
+
+ public ADBDevice(ADBDeviceInfo info) {
+ this.info = info;
+ }
+
+ public ADBDeviceInfo getDeviceInfo() {
+ return info;
+ }
+
+ public boolean updateDeviceInfo(ADBDeviceInfo info) {
+ boolean matched = this.info.serial.equals(info.serial);
+ if (matched) {
+ this.info = info;
+ }
+ return matched;
+ }
+
+ public String getSerial() {
+ return info.serial;
+ }
+
+ public boolean removeForward(String localPort) throws IOException {
+ return ADB.removeForward(info.adbHost, info.adbPort, info.serial, localPort);
+ }
+
+ public ForwardResult forwardJDWP(String localPort, String jdwpPid) throws IOException {
+ try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
+ String cmd = String.format("host:forward:tcp:%s;jdwp:%s", localPort, jdwpPid);
+ cmd = String.format("%04x%s", cmd.length(), cmd);
+ InputStream inputStream = socket.getInputStream();
+ OutputStream outputStream = socket.getOutputStream();
+ ForwardResult rst;
+ if (ADB.setSerial(info.serial, outputStream, inputStream)) {
+ outputStream.write(cmd.getBytes());
+ if (!ADB.isOkay(inputStream)) {
+ rst = new ForwardResult(1, ADB.readServiceProtocol(inputStream));
+ } else if (!ADB.isOkay(inputStream)) {
+ rst = new ForwardResult(2, ADB.readServiceProtocol(inputStream));
+ } else {
+ rst = new ForwardResult(0, null);
+ }
+ } else {
+ rst = new ForwardResult(1, "Unknown error.".getBytes());
+ }
+ return rst;
+ }
+ }
+
+ public static class ForwardResult {
+ /**
+ * 0 for success, 1 for failed at binding to local tcp, 2 for failed at remote.
+ */
+ public int state;
+ public String desc;
+
+ public ForwardResult(int state, byte[] desc) {
+ if (desc != null) {
+ this.desc = new String(desc);
+ } else {
+ this.desc = "";
+ }
+ this.state = state;
+ }
+ }
+
+ /**
+ * @return pid otherwise -1
+ */
+ public int launchApp(String fullAppName) throws IOException, InterruptedException {
+ byte[] res;
+ try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
+ String cmd = "am start -D -n " + fullAppName;
+ res = ADB.execShellCommandRaw(info.serial, cmd, socket.getOutputStream(), socket.getInputStream());
+ }
+ String rst = new String(res).trim();
+ if (rst.startsWith("Starting: Intent {") && rst.endsWith(fullAppName + " }")) {
+ Thread.sleep(40);
+ String pkg = fullAppName.split("/")[0];
+ for (Process process : getProcessByPkg(pkg)) {
+ return Integer.parseInt(process.pid);
+ }
+ }
+ return -1;
+ }
+
+ public String getAndroidReleaseVersion() {
+ if (!StringUtils.isEmpty(androidReleaseVer)) {
+ return androidReleaseVer;
+ }
+ try {
+ List list = getProp("ro.build.version.release");
+ if (list.size() != 0) {
+ androidReleaseVer = list.get(0);
+ }
+ } catch (Exception e) {
+ LOG.error("Failed to get android release version", e);
+ androidReleaseVer = "";
+ }
+ return androidReleaseVer;
+ }
+
+ public List getProp(String entry) throws IOException {
+ try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
+ List props = Collections.emptyList();
+ String cmd = "getprop";
+ if (!StringUtils.isEmpty(entry)) {
+ cmd += " " + entry;
+ }
+ byte[] payload = ADB.execShellCommandRaw(info.serial, cmd,
+ socket.getOutputStream(), socket.getInputStream());
+ if (payload != null) {
+ props = new ArrayList<>();
+ String[] lines = new String(payload).split("\n");
+ for (String line : lines) {
+ line = line.trim();
+ if (!line.isEmpty()) {
+ props.add(line.trim());
+ }
+ }
+ }
+ return props;
+ }
+ }
+
+ public List getProcessByPkg(String pkg) throws IOException {
+ return getProcessList("ps | grep " + pkg, 0);
+ }
+
+ @NonNull
+ public List getProcessList() throws IOException {
+ return getProcessList("ps", 1);
+ }
+
+ private List getProcessList(String cmd, int index) throws IOException {
+ try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
+ List procs = new ArrayList<>();
+ byte[] payload = ADB.execShellCommandRaw(info.serial, cmd,
+ socket.getOutputStream(), socket.getInputStream());
+ if (payload != null) {
+ String ps = new String(payload);
+ String[] psLines = ps.split("\n");
+ for (int i = index; i < psLines.length; i++) {
+ Process proc = Process.make(psLines[i]);
+ if (proc != null) {
+ procs.add(proc);
+ }
+ }
+ }
+ return procs;
+ }
+ }
+
+ public boolean listenForJDWP(JDWPProcessListener listener) throws IOException {
+ if (this.jdwpListenerSock != null) {
+ return false;
+ }
+ jdwpListenerSock = ADB.connect(this.info.adbHost, this.info.adbPort);
+ InputStream inputStream = jdwpListenerSock.getInputStream();
+ OutputStream outputStream = jdwpListenerSock.getOutputStream();
+ if (ADB.setSerial(info.serial, outputStream, inputStream)
+ && ADB.execCommandAsync(outputStream, inputStream, CMD_TRACK_JDWP)) {
+ Executors.newFixedThreadPool(1).execute(() -> {
+ for (;;) {
+ byte[] res = ADB.readServiceProtocol(inputStream);
+ if (res != null) {
+ if (listener != null) {
+ String payload = new String(res);
+ String[] ids = payload.split("\n");
+ Set idList = new HashSet<>(ids.length);
+ for (String id : ids) {
+ if (!id.trim().isEmpty()) {
+ idList.add(id);
+ }
+ }
+ listener.jdwpProcessOccurred(this, idList);
+ }
+ } else { // socket disconnected
+ break;
+ }
+ }
+ if (listener != null) {
+ this.jdwpListenerSock = null;
+ listener.jdwpListenerClosed(this);
+ }
+ });
+ } else {
+ jdwpListenerSock.close();
+ jdwpListenerSock = null;
+ return false;
+ }
+ return true;
+ }
+
+ public void stopListenForJDWP() {
+ if (jdwpListenerSock != null) {
+ try {
+ jdwpListenerSock.close();
+ } catch (Exception e) {
+ LOG.error("JDWP socket close failed", e);
+ }
+ }
+ this.jdwpListenerSock = null;
+ }
+
+ @Override
+ public int hashCode() {
+ return info.serial.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ADBDevice) {
+ return ((ADBDevice) obj).getDeviceInfo().serial.equals(info.serial);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return info.allInfo;
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDeviceInfo.java b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDeviceInfo.java
new file mode 100644
index 000000000..b61ae143a
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDeviceInfo.java
@@ -0,0 +1,42 @@
+package jadx.gui.device.protocol;
+
+public class ADBDeviceInfo {
+ public String adbHost;
+ public int adbPort;
+ public String serial;
+ public String state;
+ public String model;
+ public String allInfo;
+
+ public boolean isOnline() {
+ return state.equals("device");
+ }
+
+ @Override
+ public String toString() {
+ return allInfo;
+ }
+
+ static ADBDeviceInfo make(String info, String host, int port) {
+ ADBDeviceInfo deviceInfo = new ADBDeviceInfo();
+ String[] infoFields = info.trim().split("\\s+");
+ deviceInfo.allInfo = String.join(" ", infoFields);
+ if (infoFields.length > 2) {
+ deviceInfo.serial = infoFields[0];
+ deviceInfo.state = infoFields[1];
+ }
+ int pos = info.indexOf("model:");
+ if (pos != -1) {
+ int spacePos = info.indexOf(" ", pos);
+ if (spacePos != -1) {
+ deviceInfo.model = info.substring(pos + "model:".length(), spacePos);
+ }
+ }
+ if (deviceInfo.model == null || deviceInfo.model.equals("")) {
+ deviceInfo.model = deviceInfo.serial;
+ }
+ deviceInfo.adbHost = host;
+ deviceInfo.adbPort = port;
+ return deviceInfo;
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java
index 2afd6f5af..ee38e4492 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java
@@ -42,6 +42,8 @@ import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.device.debugger.DbgUtils;
import jadx.gui.device.protocol.ADB;
+import jadx.gui.device.protocol.ADBDevice;
+import jadx.gui.device.protocol.ADBDeviceInfo;
import jadx.gui.treemodel.JClass;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.panel.IDebugController;
@@ -49,7 +51,7 @@ import jadx.gui.utils.NLS;
import jadx.gui.utils.SystemInfo;
import jadx.gui.utils.UiUtils;
-import static jadx.gui.device.protocol.ADB.Device.ForwardResult;
+import static jadx.gui.device.protocol.ADBDevice.ForwardResult;
public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.JDWPProcessListener {
private static final Logger LOG = LoggerFactory.getLogger(ADBDialog.class);
@@ -273,9 +275,9 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
}
@Override
- public void onDeviceStatusChange(List deviceInfoList) {
+ public void onDeviceStatusChange(List deviceInfoList) {
List nodes = new ArrayList<>(deviceInfoList.size());
- info_loop: for (ADB.DeviceInfo info : deviceInfoList) {
+ info_loop: for (ADBDeviceInfo info : deviceInfoList) {
for (DeviceNode deviceNode : deviceNodes) {
if (deviceNode.device.updateDeviceInfo(info)) {
deviceNode.refresh();
@@ -283,7 +285,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
continue info_loop;
}
}
- ADB.Device device = new ADB.Device(info);
+ ADBDevice device = new ADBDevice(info);
device.getAndroidReleaseVersion();
nodes.add(new DeviceNode(device));
listenJDWP(device);
@@ -402,7 +404,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
return null;
}
- private DeviceNode getDeviceNode(ADB.Device device) {
+ private DeviceNode getDeviceNode(ADBDevice device) {
for (DeviceNode deviceNode : deviceNodes) {
if (deviceNode.device.equals(device)) {
return deviceNode;
@@ -411,7 +413,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
throw new JadxRuntimeException("Unexpected device: " + device);
}
- private void listenJDWP(ADB.Device device) {
+ private void listenJDWP(ADBDevice device) {
try {
device.listenForJDWP(this);
} catch (Exception e) {
@@ -441,7 +443,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
}
@Override
- public void jdwpProcessOccurred(ADB.Device device, Set id) {
+ public void jdwpProcessOccurred(ADBDevice device, Set id) {
List procs;
try {
Thread.sleep(40); /*
@@ -514,7 +516,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
return;
}
String fullName = pkg + "/" + cls.getCls().getClassNode().getClassInfo().getFullName();
- ADB.Device device = deviceNodes.get(0).device; // TODO: if multiple devices presented should let user select the one they desire.
+ ADBDevice device = deviceNodes.get(0).device; // TODO: if multiple devices presented should let user select the one they desire.
if (device != null) {
try {
device.launchApp(fullName);
@@ -547,7 +549,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
}
@Override
- public void jdwpListenerClosed(ADB.Device device) {
+ public void jdwpListenerClosed(ADBDevice device) {
}
@@ -556,27 +558,29 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
}
private static class DeviceNode {
- ADB.Device device;
+ ADBDevice device;
DeviceTreeNode tNode;
- DeviceNode(ADB.Device adbDevice) {
+ DeviceNode(ADBDevice adbDevice) {
this.device = adbDevice;
tNode = new DeviceTreeNode();
refresh();
}
void refresh() {
- ADB.DeviceInfo info = device.getDeviceInfo();
+ ADBDeviceInfo info = device.getDeviceInfo();
String text = info.model;
- if (!text.equals(info.serial)) {
- text += String.format(" [serial: %s]", info.serial);
+ if (text != null) {
+ if (!text.equals(info.serial)) {
+ text += String.format(" [serial: %s]", info.serial);
+ }
+ text += String.format(" [state: %s]", info.isOnline() ? "online" : "offline");
+ tNode.setUserObject(text);
}
- text += String.format(" [state: %s]", info.isOnline() ? "online" : "offline");
- tNode.setUserObject(text);
}
}
- private boolean setupArgs(ADB.Device device, String pid, String name) {
+ private boolean setupArgs(ADBDevice device, String pid, String name) {
String ver = device.getAndroidReleaseVersion();
if (StringUtils.isEmpty(ver)) {
if (JOptionPane.showConfirmDialog(mainWindow,
@@ -605,12 +609,12 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
private String ver;
private String pid;
private String name;
- private ADB.Device device;
+ private ADBDevice device;
private int forwardTcpPort = FORWARD_TCP_PORT;
private String expectPkg = "";
private boolean autoAttachPkg = false;
- private void set(ADB.Device device, String ver, String pid, String name) {
+ private void set(ADBDevice device, String ver, String pid, String name) {
this.ver = ver;
this.pid = pid;
this.name = name;
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/IOUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/IOUtils.java
new file mode 100644
index 000000000..cfe783ef1
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/utils/IOUtils.java
@@ -0,0 +1,47 @@
+package jadx.gui.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class IOUtils {
+
+ /**
+ * This method can be deleted once Jadx is Java11+
+ *
+ * @param inputStream
+ * @param len
+ * @return
+ * @throws IOException
+ */
+ public static byte[] readNBytes(InputStream inputStream, int len) throws IOException {
+ byte[] payload = new byte[len];
+ int readSize = 0;
+ while (true) {
+ int read = inputStream.read(payload, readSize, len - readSize);
+ if (read == -1) {
+ return null;
+ }
+ readSize += read;
+ if (readSize == len) {
+ return payload;
+ }
+ }
+ }
+
+ public static int read(InputStream inputStream, byte[] buf) throws IOException {
+ return read(inputStream, buf, 0, buf.length);
+ }
+
+ public static int read(InputStream inputStream, byte[] buf, int off, int len) throws IOException {
+ int remainingBytes = len;
+ while (remainingBytes > 0) {
+ int start = len - remainingBytes;
+ int bytesRead = inputStream.read(buf, off + start, remainingBytes);
+ if (bytesRead == -1) {
+ break;
+ }
+ remainingBytes -= bytesRead;
+ }
+ return len - remainingBytes;
+ }
+}