From ff778ab372cedf8d1e1e4a25894696132b27a6d5 Mon Sep 17 00:00:00 2001 From: "Jan S." Date: Wed, 4 Mar 2026 21:06:30 +0100 Subject: [PATCH] fix(gui): use UI thread for adding logcat messages (#2811) * fix(gui): use UI thread for adding logcat messages other minor/logging improvements for debugger and adb connection * checkstyle --- .../java/jadx/core/utils/log/LogUtils.java | 12 +-- .../jadx/core/utils/log/LogUtilsTest.java | 4 +- .../gui/device/debugger/LogcatController.java | 61 ++++---------- .../java/jadx/gui/device/protocol/ADB.java | 79 ++++++++++++------ .../jadx/gui/device/protocol/ADBDevice.java | 78 ++++++++++-------- .../java/jadx/gui/ui/dialog/ADBDialog.java | 1 + .../java/jadx/gui/ui/panel/LogcatPanel.java | 82 ++++++------------- 7 files changed, 155 insertions(+), 162 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/utils/log/LogUtils.java b/jadx-core/src/main/java/jadx/core/utils/log/LogUtils.java index 976e1d5d8..b9e9a157d 100644 --- a/jadx-core/src/main/java/jadx/core/utils/log/LogUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/log/LogUtils.java @@ -9,16 +9,18 @@ import java.util.regex.Pattern; */ public class LogUtils { - private static final Pattern ALFA_NUMERIC = Pattern.compile("\\w*"); + /** + * We replace everything except alphanumeric characters, underscore, dots, colon, semicolon, comma, + * spaces, minus + */ + private static final Pattern REPLACE_PATTERN = Pattern.compile("[^\\w\\.:;, -]"); public static String escape(String input) { if (input == null) { return "null"; } - if (ALFA_NUMERIC.matcher(input).matches()) { - return input; - } - return input.replaceAll("\\W", "."); + + return REPLACE_PATTERN.matcher(input).replaceAll("."); } public static String escape(byte[] input) { diff --git a/jadx-core/src/test/java/jadx/core/utils/log/LogUtilsTest.java b/jadx-core/src/test/java/jadx/core/utils/log/LogUtilsTest.java index 5b0e4653c..eae277555 100644 --- a/jadx-core/src/test/java/jadx/core/utils/log/LogUtilsTest.java +++ b/jadx-core/src/test/java/jadx/core/utils/log/LogUtilsTest.java @@ -8,6 +8,8 @@ class LogUtilsTest { @Test void escape() { - assertThat(LogUtils.escape("Guest'%0AUser:'Admin")).isEqualTo("Guest..0AUser..Admin"); + String src = "a.b,c:d;e disallowed\"a'b#c*d\te\rf\ng"; + String out = "a.b,c:d;e disallowed.a.b.c.d.e.f.g"; + assertThat(LogUtils.escape(src)).isEqualTo(out); } } diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/LogcatController.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/LogcatController.java index 347ea017e..f81ae6dac 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/LogcatController.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/LogcatController.java @@ -8,8 +8,10 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; +import java.util.TreeSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +28,7 @@ public class LogcatController { private final String timezone; private LogcatInfo recent = null; private List events = new ArrayList<>(); - private LogcatFilter filter = new LogcatFilter(null, null); + private LogcatFilter filter = new LogcatFilter(); private String status = "null"; public LogcatController(LogcatPanel logcatPanel, ADBDevice adbDevice) throws IOException { @@ -155,7 +157,7 @@ public class LogcatController { public void exit() { stopLogcat(); - filter = new LogcatFilter(null, null); + filter = new LogcatFilter(); recent = null; } @@ -164,44 +166,25 @@ public class LogcatController { } public class LogcatFilter { - private final List pid; - private List msgType = new ArrayList<>() { - { - add((byte) 1); - add((byte) 2); - add((byte) 3); - add((byte) 4); - add((byte) 5); - add((byte) 6); - add((byte) 7); - add((byte) 8); - } - }; - public LogcatFilter(ArrayList pid, ArrayList msgType) { - if (pid != null) { - this.pid = pid; - } else { - this.pid = new ArrayList<>(); - } + private final Set pid; + private final Set msgType; - if (msgType != null) { - this.msgType = msgType; - } + public LogcatFilter() { + this(new TreeSet<>(), new TreeSet<>(List.of((byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6, (byte) 7, (byte) 8))); + } + + public LogcatFilter(Set pid, Set msgType) { + this.pid = pid; + this.msgType = msgType; } public void addPid(int pid) { - - if (!this.pid.contains(pid)) { - this.pid.add(pid); - } + this.pid.add(pid); } public void removePid(int pid) { - int pidPos = this.pid.indexOf(pid); - if (pidPos >= 0) { - this.pid.remove(pidPos); - } + this.pid.remove(pid); } public void togglePid(int pid, boolean state) { @@ -213,16 +196,11 @@ public class LogcatController { } public void addMsgType(byte msgType) { - if (!this.msgType.contains(msgType)) { - this.msgType.add(msgType); - } + this.msgType.add(msgType); } public void removeMsgType(byte msgType) { - int typePos = this.msgType.indexOf(msgType); - if (typePos >= 0) { - this.msgType.remove(typePos); - } + this.msgType.remove(msgType); } public void toggleMsgType(byte msgType, boolean state) { @@ -234,10 +212,7 @@ public class LogcatController { } public boolean doFilter(LogcatInfo inInfo) { - if (pid.contains(inInfo.getPid())) { - return msgType.contains(inInfo.getMsgType()); - } - return false; + return (pid.contains(inInfo.getPid())) && msgType.contains(inInfo.getMsgType()); } public List getFilteredList(List inInfoList) { 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 e56b33893..6de1c58d5 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 @@ -6,14 +6,18 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -24,6 +28,9 @@ import jadx.core.utils.log.LogUtils; import jadx.gui.utils.IOUtils; public class ADB { + + public static final Charset ADB_CHARSET = StandardCharsets.UTF_8; + private static final Logger LOG = LoggerFactory.getLogger(ADB.class); private static final int DEFAULT_PORT = 5037; @@ -31,10 +38,10 @@ public class ADB { private static final String CMD_FEATURES = "000dhost:features"; private static final String CMD_TRACK_DEVICES = "0014host:track-devices-l"; - private static final byte[] OKAY = "OKAY".getBytes(); - private static final byte[] FAIL = "FAIL".getBytes(); + private static final byte[] OKAY = "OKAY".getBytes(ADB_CHARSET); + private static final byte[] FAIL = "FAIL".getBytes(ADB_CHARSET); - static boolean isOkay(InputStream stream) throws IOException { + static boolean isOkay(InputStream stream, String command) throws IOException { byte[] buf = IOUtils.readNBytes(stream, 4); if (Arrays.equals(buf, OKAY)) { return true; @@ -45,13 +52,13 @@ public class ADB { // int msgLen = Integer.parseInt(new String(IOUtils.readNBytes(stream, 4)), 16); // byte[] errorMsg = IOUtils.readNBytes(stream, msgLen); // LOG.error("isOkay failed: received error message: {}", new String(errorMsg)); - LOG.error("isOkay failed"); + LOG.error("isOkay failed for command: {}", command); return false; } if (buf == null) { throw new IOException("isOkay failed - steam ended"); } - throw new IOException("isOkay failed - unexpected response " + new String(buf)); + throw new IOException("isOkay failed - unexpected response " + new String(buf, ADB_CHARSET)); } public static byte[] exec(String cmd, OutputStream outputStream, InputStream inputStream) throws IOException { @@ -73,13 +80,13 @@ public class ADB { } static boolean execCommandAsync(OutputStream outputStream, InputStream inputStream, String cmd) throws IOException { - outputStream.write(cmd.getBytes()); - return isOkay(inputStream); + outputStream.write(cmd.getBytes(ADB_CHARSET)); + return isOkay(inputStream, "execCommandAsync"); } private static byte[] execCommandSync(OutputStream outputStream, InputStream inputStream, String cmd) throws IOException { - outputStream.write(cmd.getBytes()); - if (isOkay(inputStream)) { + outputStream.write(cmd.getBytes(ADB_CHARSET)); + if (isOkay(inputStream, "execCommandSync")) { return readServiceProtocol(inputStream); } return null; @@ -91,7 +98,7 @@ public class ADB { if (buf == null) { return null; } - int len = unhex(buf); + int len = hexToInt(buf); byte[] result; if (len == 0) { result = new byte[0]; @@ -111,13 +118,15 @@ public class ADB { } static boolean setSerial(String serial, OutputStream outputStream, InputStream inputStream) throws IOException { + checkSerial(serial); + LOG.trace("setSerial({})", serial); String setSerialCmd = String.format("host:tport:serial:%s", serial); setSerialCmd = String.format("%04x%s", setSerialCmd.length(), setSerialCmd); - outputStream.write(setSerialCmd.getBytes()); - boolean ok = isOkay(inputStream); + outputStream.write(setSerialCmd.getBytes(ADB_CHARSET)); + boolean ok = isOkay(inputStream, setSerialCmd); if (ok) { // skip the shell-state-id returned by ADB server, it's not important for the following actions. - IOUtils.readNBytes(inputStream, 8); + inputStream.readNBytes(8); } else { LOG.error("setSerial command {} failed", LogUtils.escape(setSerialCmd)); } @@ -127,8 +136,8 @@ public class ADB { 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()); - if (isOkay(inputStream)) { + outputStream.write(cmd.getBytes(ADB_CHARSET)); + if (isOkay(inputStream, cmd)) { return ShellProtocol.readStdout(inputStream); } return null; @@ -144,7 +153,7 @@ public class ADB { public static List getFeatures() throws IOException { byte[] rst = exec(CMD_FEATURES); if (rst != null) { - return Arrays.asList(new String(rst).trim().split(",")); + return Arrays.asList(new String(rst, ADB_CHARSET).trim().split(",")); } return Collections.emptyList(); } @@ -202,7 +211,7 @@ public class ADB { break; // socket disconnected } if (listener != null) { - String payload = new String(res); + String payload = new String(res, ADB_CHARSET); String[] deviceLines = payload.split("\n"); List deviceInfoList = new ArrayList<>(deviceLines.length); for (String deviceLine : deviceLines) { @@ -225,11 +234,11 @@ public class ADB { String cmd = "0011host:list-forward"; InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); - outputStream.write(cmd.getBytes()); - if (isOkay(inputStream)) { + outputStream.write(cmd.getBytes(ADB_CHARSET)); + if (isOkay(inputStream, "listForward")) { byte[] bytes = readServiceProtocol(inputStream); if (bytes != null) { - String[] forwards = new String(bytes).split("\n"); + String[] forwards = new String(bytes, ADB_CHARSET).split("\n"); return Stream.of(forwards).map(String::trim).collect(Collectors.toList()); } } @@ -244,8 +253,8 @@ public class ADB { InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); if (setSerial(serial, outputStream, inputStream)) { - outputStream.write(cmd.getBytes()); - return isOkay(inputStream) && isOkay(inputStream); + outputStream.write(cmd.getBytes(ADB_CHARSET)); + return isOkay(inputStream, "removeForward1") && isOkay(inputStream, "removeForward2"); } } return false; @@ -267,7 +276,21 @@ public class ADB { return rst; } - private static int unhex(byte[] hex) { + private static final Pattern SERIAL_PATTERN = Pattern.compile("^[\\w-]{10,20}$"); + + private static void checkSerial(String serial) { + if (!SERIAL_PATTERN.matcher(serial).matches()) { + throw new IllegalArgumentException("Invalid serial: " + serial); + } + } + + /** + * Convert 4 hex characters to int + * + * @param hex + * @return + */ + private static int hexToInt(byte[] hex) { int n = 0; byte b; for (int i = 0; i < 4; i++) { @@ -340,6 +363,16 @@ public class ADB { } return null; } + + @Override + public String toString() { + return new StringJoiner(", ", Process.class.getSimpleName() + "[", "]") + .add("user='" + user + "'") + .add("pid='" + pid + "'") + .add("ppid='" + ppid + "'") + .add("name='" + name + "'") + .toString(); + } } private static class ShellProtocol { 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 index 5fe82c24f..2e683e8e0 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java +++ b/jadx-gui/src/main/java/jadx/gui/device/protocol/ADBDevice.java @@ -9,7 +9,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -21,6 +20,8 @@ import jadx.core.utils.log.LogUtils; import jadx.gui.device.protocol.ADB.JDWPProcessListener; import jadx.gui.device.protocol.ADB.Process; +import static jadx.gui.device.protocol.ADB.ADB_CHARSET; + public class ADBDevice { private static final Logger LOG = LoggerFactory.getLogger(ADBDevice.class); @@ -65,16 +66,16 @@ public class ADBDevice { OutputStream outputStream = socket.getOutputStream(); ForwardResult rst; if (ADB.setSerial(info.getSerial(), outputStream, inputStream)) { - outputStream.write(cmd.getBytes()); - if (!ADB.isOkay(inputStream)) { + outputStream.write(cmd.getBytes(ADB_CHARSET)); + if (!ADB.isOkay(inputStream, "forwardJDWP1")) { rst = new ForwardResult(1, ADB.readServiceProtocol(inputStream)); - } else if (!ADB.isOkay(inputStream)) { + } else if (!ADB.isOkay(inputStream, "forwardJDWP2")) { rst = new ForwardResult(2, ADB.readServiceProtocol(inputStream)); } else { rst = new ForwardResult(0, null); } } else { - rst = new ForwardResult(1, "Unknown error.".getBytes()); + rst = new ForwardResult(1, "Unknown error.".getBytes(ADB_CHARSET)); } return rst; } @@ -89,7 +90,7 @@ public class ADBDevice { public ForwardResult(int state, byte[] desc) { if (desc != null) { - this.desc = new String(desc); + this.desc = new String(desc, ADB_CHARSET); } else { this.desc = ""; } @@ -109,7 +110,7 @@ public class ADBDevice { return -1; } } - String rst = new String(res).trim(); + String rst = new String(res, ADB_CHARSET).trim(); if (rst.startsWith("Starting: Intent {") && rst.endsWith(fullAppName + " }")) { Thread.sleep(40); String pkg = fullAppName.split("/")[0]; @@ -124,10 +125,10 @@ public class ADBDevice { * @return binary output of logcat */ public byte[] getBinaryLogcat() throws IOException { - - Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort()); - String cmd = "logcat -dB"; - return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); + try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) { + String cmd = "logcat -dB"; + return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); + } } /** @@ -135,32 +136,38 @@ public class ADBDevice { * Timestamp is in the format 09-08 02:18:03.131 */ public byte[] getBinaryLogcat(String timestamp) throws IOException { - Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort()); - Matcher matcher = TIMESTAMP_FORMAT.matcher(timestamp); - if (!matcher.find()) { - LOG.error("Invalid Logcat Timestamp " + timestamp); + try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) { + Matcher matcher = TIMESTAMP_FORMAT.matcher(timestamp); + if (!matcher.find()) { + LOG.error("Invalid Logcat Timestamp {}", timestamp); + } + String cmd = "logcat -dB -t \"" + timestamp + "\""; + return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); } - String cmd = "logcat -dB -t \"" + timestamp + "\""; - return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); } /** * Binary output of logcat -c */ public void clearLogcat() throws IOException { - Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort()); - String cmd = "logcat -c"; - ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); + try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) { + String cmd = "logcat -c"; + ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); + } } /** * @return Timezone for the attached android device */ public String getTimezone() throws IOException { - Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort()); - String cmd = "getprop persist.sys.timezone"; - byte[] tz = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); - return new String(tz).trim(); + try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) { + String cmd = "getprop persist.sys.timezone"; + byte[] tz = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); + if (tz == null) { + throw new IOException("Failed to get timezone"); + } + return new String(tz, ADB_CHARSET).trim(); + } } public String getAndroidReleaseVersion() { @@ -169,9 +176,10 @@ public class ADBDevice { } try { List list = getProp("ro.build.version.release"); - if (list.size() != 0) { - androidReleaseVer = list.get(0); + if (!list.isEmpty()) { + return list.get(0); } + LOG.error("Failed to get android release version - no result"); } catch (Exception e) { LOG.error("Failed to get android release version", e); androidReleaseVer = ""; @@ -180,6 +188,7 @@ public class ADBDevice { } public List getProp(String entry) throws IOException { + LOG.debug("ADB getProp({})", entry); try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) { List props = Collections.emptyList(); String cmd = "getprop"; @@ -190,7 +199,7 @@ public class ADBDevice { socket.getOutputStream(), socket.getInputStream()); if (payload != null) { props = new ArrayList<>(); - String[] lines = new String(payload).split("\n"); + String[] lines = new String(payload, ADB_CHARSET).split("\n"); for (String line : lines) { line = line.trim(); if (!line.isEmpty()) { @@ -198,6 +207,7 @@ public class ADBDevice { } } } + LOG.trace("ADB getProp({}) = {}", entry, props); return props; } } @@ -216,7 +226,8 @@ public class ADBDevice { byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream()); if (payload != null) { - String ps = new String(payload); + String ps = new String(payload, ADB_CHARSET); + // LOG.trace("ADB getProcessList({}) = {}", cmd, ps); String[] psLines = ps.split("\n"); for (String line : psLines) { line = line.trim(); @@ -244,12 +255,12 @@ public class ADBDevice { OutputStream outputStream = jdwpListenerSock.getOutputStream(); if (ADB.setSerial(info.getSerial(), outputStream, inputStream) && ADB.execCommandAsync(outputStream, inputStream, CMD_TRACK_JDWP)) { - Executors.newFixedThreadPool(1).execute(() -> { + new Thread(() -> { for (;;) { byte[] res = ADB.readServiceProtocol(inputStream); if (res != null) { if (listener != null) { - String payload = new String(res); + String payload = new String(res, ADB_CHARSET); String[] ids = payload.split("\n"); Set idList = new HashSet<>(ids.length); for (String id : ids) { @@ -257,6 +268,9 @@ public class ADBDevice { idList.add(id); } } + if (idList.isEmpty()) { + LOG.info("No debuggable app process found on device {}", info.getSerial()); + } listener.jdwpProcessOccurred(this, idList); } } else { // socket disconnected @@ -267,13 +281,13 @@ public class ADBDevice { this.jdwpListenerSock = null; listener.jdwpListenerClosed(this); } - }); + }).start(); + return true; } else { jdwpListenerSock.close(); jdwpListenerSock = null; return false; } - return true; } public void stopListenForJDWP() { 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 9e0612589..658fd6d39 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 @@ -279,6 +279,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J @Override public void onDeviceStatusChange(List deviceInfoList) { + LOG.debug("onDeviceStatusChange {}", deviceInfoList); List nodes = new ArrayList<>(deviceInfoList.size()); info_loop: for (ADBDeviceInfo info : deviceInfoList) { for (DeviceNode deviceNode : deviceNodes) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/LogcatPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/LogcatPanel.java index 7a177f4ed..064dbeff9 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/LogcatPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/LogcatPanel.java @@ -14,6 +14,7 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.swing.AbstractAction; @@ -61,6 +62,10 @@ public class LogcatPanel extends JPanel { private final AttributeSet fatalAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#d33682")); private final AttributeSet silentAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#002b36")); + private final Map asetMap = + Map.of((byte) 1, defaultAset, (byte) 2, verboseAset, (byte) 3, debugAset, (byte) 4, infoAset, (byte) 5, warningAset, + (byte) 6, errorAset, (byte) 7, fatalAset, (byte) 8, silentAset); + private static final ImageIcon ICON_PAUSE = UiUtils.openSvgIcon("debugger/threadFrozen"); private static final ImageIcon ICON_RUN = UiUtils.openSvgIcon("debugger/execute"); private static final ImageIcon CLEAR_LOGCAT = UiUtils.openSvgIcon("debugger/trash"); @@ -199,64 +204,29 @@ public class LogcatPanel extends JPanel { } public void log(LogcatController.LogcatInfo logcatInfo) { - boolean atBottom = false; - int len = logcatPane.getDocument().getLength(); JScrollBar scrollbar = logcatScroll.getVerticalScrollBar(); - if (isAtBottom(scrollbar)) { - atBottom = true; + boolean atBottom = isAtBottom(scrollbar); + + String logString = " > " + logcatInfo.getTimestamp() + " [pid: " + logcatInfo.getPid() + "] " + + logcatInfo.getMsgTypeString() + ": " + logcatInfo.getMsg() + "\n"; + + if (logcatInfo.getMsgType() == 0) { + return; // ignore unknown } - StringBuilder sb = new StringBuilder(); - sb.append(" > ") - .append(logcatInfo.getTimestamp()) - .append(" [pid: ") - .append(logcatInfo.getPid()) - .append("] ") - .append(logcatInfo.getMsgTypeString()) - .append(": ") - .append(logcatInfo.getMsg()) - .append("\n"); + AttributeSet attrSet = asetMap.get(logcatInfo.getMsgType()); - try { - switch (logcatInfo.getMsgType()) { - case 0: // Unknown - break; - case 1: // Default - logcatPane.getDocument().insertString(len, sb.toString(), defaultAset); - break; - case 2: // Verbose - logcatPane.getDocument().insertString(len, sb.toString(), verboseAset); - break; - case 3: // Debug - logcatPane.getDocument().insertString(len, sb.toString(), debugAset); - break; - case 4: // Info - logcatPane.getDocument().insertString(len, sb.toString(), infoAset); - break; - case 5: // Warn - logcatPane.getDocument().insertString(len, sb.toString(), warningAset); - break; - case 6: // Error - logcatPane.getDocument().insertString(len, sb.toString(), errorAset); - break; - case 7: // Fatal - logcatPane.getDocument().insertString(len, sb.toString(), fatalAset); - break; - case 8: // Silent - logcatPane.getDocument().insertString(len, sb.toString(), silentAset); - break; - default: - logcatPane.getDocument().insertString(len, sb.toString(), null); - break; + UiUtils.uiRun(() -> { + try { + logcatPane.getDocument().insertString(len, logString, attrSet); + } catch (Exception e) { + LOG.error("Failed to add logcat message", e); } - } catch (Exception e) { - LOG.error("Failed to write logcat message", e); - } - - if (atBottom) { - EventQueue.invokeLater(() -> scrollbar.setValue(scrollbar.getMaximum())); - } + if (atBottom) { + EventQueue.invokeLater(() -> scrollbar.setValue(scrollbar.getMaximum())); + } + }); } public void exit() { @@ -282,7 +252,7 @@ public class LogcatPanel extends JPanel { } public void actionPerformed(ActionEvent e) { - JComboBox cb = (JComboBox) e.getSource(); + JComboBox cb = (JComboBox) e.getSource(); CheckComboStore store = (CheckComboStore) cb.getSelectedItem(); CheckComboRenderer ccr = (CheckComboRenderer) cb.getRenderer(); store.state = !store.state; @@ -350,11 +320,7 @@ public class LogcatPanel extends JPanel { public void selectAllBut(int ind) { for (int i = 0; i < combo.getItemCount(); i++) { CheckComboStore ccs = combo.getItemAt(i); - if (i != ind) { - ccs.state = false; - } else { - ccs.state = true; - } + ccs.state = (i == ind); switch (type) { case 1: // process logcatController.getFilter().togglePid(ccs.index, ccs.state);