Skip to content

Commit 9114821

Browse files
jmlitfiTheCobraChickenskylot
authored
feat(debugger): add logcat output (#1411)(PR #1666)
* Adding logcatController class and writing adb / debugger panel information to the controller. * Finished parsing logcat binary output and writing an arraylist containing all events. * added highlighting of logcat output based on type. Added timestamp parsing. * Updated code to only get new log messages. * Added additional code for select all * Completed Check and uncheckall options. * Changed log highlighting to log color. Changed from JTextArea to JTextPane. Logcat pane will now autoscroll only if it is already scrolled to the bottom. Debugger exit will now stop logcat as well. * Moved labels into NLS rather than using hardcoded strings. * Implemented the ability to autoselect attached process. Changed the formatting of logcat messages. * Moved labels into NLS rather than using hardcoded strings. * updating to use info getter methods rather than directly accessing variable * Added Logcat Pause Button * Added Clear button * Updated clear icon * Cleaning warnings * cleaning * Changed behavior to only show logcat for debugged process to start with. * cleaning * cleaning * cleaning * applying spotless * Fixing bug with switch * fixed formatting issue * add missing localization strings Co-authored-by: green9317 <[email protected]> Co-authored-by: TheCobraChicken <[email protected]> Co-authored-by: Skylot <[email protected]>
1 parent 1195582 commit 9114821

File tree

13 files changed

+1056
-9
lines changed

13 files changed

+1056
-9
lines changed
Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
package jadx.gui.device.debugger;
2+
3+
import java.io.IOException;
4+
import java.nio.ByteBuffer;
5+
import java.nio.ByteOrder;
6+
import java.time.Instant;
7+
import java.time.ZoneId;
8+
import java.time.format.DateTimeFormatter;
9+
import java.util.ArrayList;
10+
import java.util.Timer;
11+
import java.util.TimerTask;
12+
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
import jadx.gui.device.protocol.ADBDevice;
17+
import jadx.gui.ui.panel.LogcatPanel;
18+
19+
public class LogcatController {
20+
private static final Logger LOG = LoggerFactory.getLogger(LogcatController.class);
21+
22+
private final ADBDevice adbDevice;
23+
private final LogcatPanel logcatPanel;
24+
private Timer timer;
25+
private final String timezone;
26+
private LogcatInfo recent = null;
27+
private ArrayList<LogcatInfo> events = new ArrayList<>();
28+
private LogcatFilter filter = new LogcatFilter(null, null);
29+
private String status = "null";
30+
31+
public LogcatController(LogcatPanel logcatPanel, ADBDevice adbDevice) throws IOException {
32+
this.adbDevice = adbDevice;
33+
this.logcatPanel = logcatPanel;
34+
this.timezone = adbDevice.getTimezone();
35+
this.startLogcat();
36+
}
37+
38+
public void startLogcat() {
39+
timer = new Timer();
40+
timer.schedule(new TimerTask() {
41+
@Override
42+
public void run() {
43+
getLog();
44+
}
45+
}, 0, 1000);
46+
this.status = "running";
47+
}
48+
49+
public void stopLogcat() {
50+
timer.cancel();
51+
this.status = "stopped";
52+
}
53+
54+
public String getStatus() {
55+
return this.status;
56+
}
57+
58+
public void clearLogcat() {
59+
try {
60+
adbDevice.clearLogcat();
61+
clearEvents();
62+
} catch (IOException e) {
63+
LOG.error("Failed to clear Logcat", e);
64+
}
65+
}
66+
67+
private void getLog() {
68+
if (!logcatPanel.isReady()) {
69+
return;
70+
}
71+
try {
72+
byte[] buf;
73+
if (recent == null) {
74+
buf = adbDevice.getBinaryLogcat();
75+
} else {
76+
buf = adbDevice.getBinaryLogcat(recent.getAfterTimestamp());
77+
}
78+
if (buf == null) {
79+
return;
80+
}
81+
ByteBuffer in = ByteBuffer.wrap(buf);
82+
in.order(ByteOrder.LITTLE_ENDIAN);
83+
while (in.remaining() > 20) {
84+
85+
LogcatInfo eInfo = null;
86+
byte[] msgBuf;
87+
short eLen = in.getShort();
88+
short eHdrLen = in.getShort();
89+
if (eLen + eHdrLen > in.remaining()) {
90+
return;
91+
}
92+
switch (eHdrLen) {
93+
case 20: // header length 20 == version 1
94+
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
95+
msgBuf = new byte[eLen];
96+
in.get(msgBuf, 0, eLen - 1);
97+
eInfo.setMsg(msgBuf);
98+
break;
99+
case 24: // header length 24 == version 2 / 3
100+
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
101+
msgBuf = new byte[eLen];
102+
in.get(msgBuf, 0, eLen - 1);
103+
eInfo.setMsg(msgBuf);
104+
break;
105+
case 28: // header length 28 == version 4
106+
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(),
107+
in.get());
108+
msgBuf = new byte[eLen];
109+
in.get(msgBuf, 0, eLen - 1);
110+
eInfo.setMsg(msgBuf);
111+
break;
112+
default:
113+
114+
break;
115+
}
116+
if (eInfo == null) {
117+
return;
118+
}
119+
if (recent == null) {
120+
recent = eInfo;
121+
} else if (recent.getInstant().isBefore(eInfo.getInstant())) {
122+
recent = eInfo;
123+
}
124+
125+
if (filter.doFilter(eInfo)) {
126+
logcatPanel.log(eInfo);
127+
}
128+
events.add(eInfo);
129+
}
130+
131+
} catch (Exception e) {
132+
LOG.error("Failed to get logcat message", e);
133+
}
134+
}
135+
136+
public boolean reload() {
137+
stopLogcat();
138+
boolean ok = logcatPanel.clearLogcatArea();
139+
if (ok) {
140+
events.forEach((eInfo) -> {
141+
if (filter.doFilter(eInfo)) {
142+
logcatPanel.log(eInfo);
143+
}
144+
});
145+
startLogcat();
146+
}
147+
return true;
148+
}
149+
150+
public void clearEvents() {
151+
this.recent = null;
152+
this.events = new ArrayList<>();
153+
}
154+
155+
public void exit() {
156+
stopLogcat();
157+
filter = new LogcatFilter(null, null);
158+
recent = null;
159+
}
160+
161+
public LogcatFilter getFilter() {
162+
return this.filter;
163+
}
164+
165+
public class LogcatFilter {
166+
private final ArrayList<Integer> pid;
167+
private ArrayList<Byte> msgType = new ArrayList<Byte>() {
168+
{
169+
add((byte) 1);
170+
add((byte) 2);
171+
add((byte) 3);
172+
add((byte) 4);
173+
add((byte) 5);
174+
add((byte) 6);
175+
add((byte) 7);
176+
add((byte) 8);
177+
}
178+
};
179+
180+
public LogcatFilter(ArrayList<Integer> pid, ArrayList<Byte> msgType) {
181+
if (pid != null) {
182+
this.pid = pid;
183+
} else {
184+
this.pid = new ArrayList<>();
185+
}
186+
187+
if (msgType != null) {
188+
this.msgType = msgType;
189+
}
190+
}
191+
192+
public void addPid(int pid) {
193+
194+
if (!this.pid.contains(pid)) {
195+
this.pid.add(pid);
196+
}
197+
}
198+
199+
public void removePid(int pid) {
200+
int pidPos = this.pid.indexOf(pid);
201+
if (pidPos >= 0) {
202+
this.pid.remove(pidPos);
203+
}
204+
}
205+
206+
public void togglePid(int pid, boolean state) {
207+
if (state) {
208+
addPid(pid);
209+
} else {
210+
removePid(pid);
211+
}
212+
}
213+
214+
public void addMsgType(byte msgType) {
215+
if (!this.msgType.contains(msgType)) {
216+
this.msgType.add(msgType);
217+
}
218+
}
219+
220+
public void removeMsgType(byte msgType) {
221+
int typePos = this.msgType.indexOf(msgType);
222+
if (typePos >= 0) {
223+
this.msgType.remove(typePos);
224+
}
225+
}
226+
227+
public void toggleMsgType(byte msgType, boolean state) {
228+
if (state) {
229+
addMsgType(msgType);
230+
} else {
231+
removeMsgType(msgType);
232+
}
233+
}
234+
235+
public boolean doFilter(LogcatInfo inInfo) {
236+
if (pid.contains(inInfo.getPid())) {
237+
return msgType.contains(inInfo.getMsgType());
238+
}
239+
return false;
240+
}
241+
242+
public ArrayList<LogcatInfo> getFilteredList(ArrayList<LogcatInfo> inInfoList) {
243+
ArrayList<LogcatInfo> outInfoList = new ArrayList<LogcatInfo>();
244+
inInfoList.forEach((inInfo) -> {
245+
if (doFilter(inInfo)) {
246+
outInfoList.add(inInfo);
247+
}
248+
});
249+
return outInfoList;
250+
}
251+
}
252+
253+
public class LogcatInfo {
254+
private String msg;
255+
private final byte msgType;
256+
private final int nsec;
257+
private final int pid;
258+
private final int sec;
259+
private final int tid;
260+
private final short hdrSize;
261+
private final short len;
262+
private final short version;
263+
private int lid;
264+
private int uid;
265+
266+
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, byte msgType) {
267+
this.hdrSize = hdrSize;
268+
this.len = len;
269+
this.msgType = msgType;
270+
this.nsec = nsec;
271+
this.pid = pid;
272+
this.sec = sec;
273+
this.tid = tid;
274+
this.version = 1;
275+
}
276+
277+
// Version 2 and 3 both have the same arguments
278+
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, byte msgType) {
279+
this.hdrSize = hdrSize;
280+
this.len = len;
281+
this.lid = lid;
282+
this.msgType = msgType;
283+
this.nsec = nsec;
284+
this.pid = pid;
285+
this.sec = sec;
286+
this.tid = tid;
287+
this.version = 3;
288+
}
289+
290+
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, int uid, byte msgType) {
291+
this.hdrSize = hdrSize;
292+
this.len = len;
293+
this.lid = lid;
294+
this.msgType = msgType;
295+
this.nsec = nsec;
296+
this.pid = pid;
297+
this.sec = sec;
298+
this.tid = tid;
299+
this.uid = uid;
300+
this.version = 4;
301+
}
302+
303+
public void setMsg(byte[] msg) {
304+
this.msg = new String(msg);
305+
}
306+
307+
public short getVersion() {
308+
return this.version;
309+
}
310+
311+
public short getLen() {
312+
return this.len;
313+
}
314+
315+
public short getHeaderLen() {
316+
return this.hdrSize;
317+
}
318+
319+
public int getPid() {
320+
return this.pid;
321+
}
322+
323+
public int getTid() {
324+
return this.tid;
325+
}
326+
327+
public int getSec() {
328+
return this.sec;
329+
}
330+
331+
public int getNSec() {
332+
return this.nsec;
333+
}
334+
335+
public int getLid() {
336+
return this.lid;
337+
}
338+
339+
public int getUid() {
340+
return this.uid;
341+
}
342+
343+
public Instant getInstant() {
344+
return Instant.ofEpochSecond(getSec(), getNSec());
345+
}
346+
347+
public String getTimestamp() {
348+
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
349+
return dtFormat.format(getInstant());
350+
}
351+
352+
public String getAfterTimestamp() {
353+
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
354+
return dtFormat.format(getInstant().plusMillis(1));
355+
}
356+
357+
public byte getMsgType() {
358+
return this.msgType;
359+
}
360+
361+
public String getMsgTypeString() {
362+
switch (getMsgType()) {
363+
case 0:
364+
return "Unknown";
365+
case 1:
366+
return "Default";
367+
case 2:
368+
return "Verbose";
369+
case 3:
370+
return "Debug";
371+
case 4:
372+
return "Info";
373+
case 5:
374+
return "Warn";
375+
case 6:
376+
return "Error";
377+
case 7:
378+
return "Fatal";
379+
case 8:
380+
return "Silent";
381+
default:
382+
return "Unknown";
383+
}
384+
}
385+
386+
public String getMsg() {
387+
return this.msg;
388+
}
389+
}
390+
}

0 commit comments

Comments
 (0)