未验证 提交 9114821f 编写于 作者: T The Cobra Chicken 提交者: GitHub

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: Ngreen9317 <38409554+green9317@users.noreply.github.com>
Co-authored-by: NTheCobraChicken <jeffmlitfi@gmail.com>
Co-authored-by: NSkylot <skylot@gmail.com>
上级 1195582d
package jadx.gui.device.debugger;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.device.protocol.ADBDevice;
import jadx.gui.ui.panel.LogcatPanel;
public class LogcatController {
private static final Logger LOG = LoggerFactory.getLogger(LogcatController.class);
private final ADBDevice adbDevice;
private final LogcatPanel logcatPanel;
private Timer timer;
private final String timezone;
private LogcatInfo recent = null;
private ArrayList<LogcatInfo> events = new ArrayList<>();
private LogcatFilter filter = new LogcatFilter(null, null);
private String status = "null";
public LogcatController(LogcatPanel logcatPanel, ADBDevice adbDevice) throws IOException {
this.adbDevice = adbDevice;
this.logcatPanel = logcatPanel;
this.timezone = adbDevice.getTimezone();
this.startLogcat();
}
public void startLogcat() {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
getLog();
}
}, 0, 1000);
this.status = "running";
}
public void stopLogcat() {
timer.cancel();
this.status = "stopped";
}
public String getStatus() {
return this.status;
}
public void clearLogcat() {
try {
adbDevice.clearLogcat();
clearEvents();
} catch (IOException e) {
LOG.error("Failed to clear Logcat", e);
}
}
private void getLog() {
if (!logcatPanel.isReady()) {
return;
}
try {
byte[] buf;
if (recent == null) {
buf = adbDevice.getBinaryLogcat();
} else {
buf = adbDevice.getBinaryLogcat(recent.getAfterTimestamp());
}
if (buf == null) {
return;
}
ByteBuffer in = ByteBuffer.wrap(buf);
in.order(ByteOrder.LITTLE_ENDIAN);
while (in.remaining() > 20) {
LogcatInfo eInfo = null;
byte[] msgBuf;
short eLen = in.getShort();
short eHdrLen = in.getShort();
if (eLen + eHdrLen > in.remaining()) {
return;
}
switch (eHdrLen) {
case 20: // header length 20 == version 1
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
case 24: // header length 24 == version 2 / 3
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
case 28: // header length 28 == version 4
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(),
in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
default:
break;
}
if (eInfo == null) {
return;
}
if (recent == null) {
recent = eInfo;
} else if (recent.getInstant().isBefore(eInfo.getInstant())) {
recent = eInfo;
}
if (filter.doFilter(eInfo)) {
logcatPanel.log(eInfo);
}
events.add(eInfo);
}
} catch (Exception e) {
LOG.error("Failed to get logcat message", e);
}
}
public boolean reload() {
stopLogcat();
boolean ok = logcatPanel.clearLogcatArea();
if (ok) {
events.forEach((eInfo) -> {
if (filter.doFilter(eInfo)) {
logcatPanel.log(eInfo);
}
});
startLogcat();
}
return true;
}
public void clearEvents() {
this.recent = null;
this.events = new ArrayList<>();
}
public void exit() {
stopLogcat();
filter = new LogcatFilter(null, null);
recent = null;
}
public LogcatFilter getFilter() {
return this.filter;
}
public class LogcatFilter {
private final ArrayList<Integer> pid;
private ArrayList<Byte> msgType = new ArrayList<Byte>() {
{
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<Integer> pid, ArrayList<Byte> msgType) {
if (pid != null) {
this.pid = pid;
} else {
this.pid = new ArrayList<>();
}
if (msgType != null) {
this.msgType = msgType;
}
}
public void addPid(int pid) {
if (!this.pid.contains(pid)) {
this.pid.add(pid);
}
}
public void removePid(int pid) {
int pidPos = this.pid.indexOf(pid);
if (pidPos >= 0) {
this.pid.remove(pidPos);
}
}
public void togglePid(int pid, boolean state) {
if (state) {
addPid(pid);
} else {
removePid(pid);
}
}
public void addMsgType(byte msgType) {
if (!this.msgType.contains(msgType)) {
this.msgType.add(msgType);
}
}
public void removeMsgType(byte msgType) {
int typePos = this.msgType.indexOf(msgType);
if (typePos >= 0) {
this.msgType.remove(typePos);
}
}
public void toggleMsgType(byte msgType, boolean state) {
if (state) {
addMsgType(msgType);
} else {
removeMsgType(msgType);
}
}
public boolean doFilter(LogcatInfo inInfo) {
if (pid.contains(inInfo.getPid())) {
return msgType.contains(inInfo.getMsgType());
}
return false;
}
public ArrayList<LogcatInfo> getFilteredList(ArrayList<LogcatInfo> inInfoList) {
ArrayList<LogcatInfo> outInfoList = new ArrayList<LogcatInfo>();
inInfoList.forEach((inInfo) -> {
if (doFilter(inInfo)) {
outInfoList.add(inInfo);
}
});
return outInfoList;
}
}
public class LogcatInfo {
private String msg;
private final byte msgType;
private final int nsec;
private final int pid;
private final int sec;
private final int tid;
private final short hdrSize;
private final short len;
private final short version;
private int lid;
private int uid;
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.version = 1;
}
// Version 2 and 3 both have the same arguments
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.lid = lid;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.version = 3;
}
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, int uid, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.lid = lid;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.uid = uid;
this.version = 4;
}
public void setMsg(byte[] msg) {
this.msg = new String(msg);
}
public short getVersion() {
return this.version;
}
public short getLen() {
return this.len;
}
public short getHeaderLen() {
return this.hdrSize;
}
public int getPid() {
return this.pid;
}
public int getTid() {
return this.tid;
}
public int getSec() {
return this.sec;
}
public int getNSec() {
return this.nsec;
}
public int getLid() {
return this.lid;
}
public int getUid() {
return this.uid;
}
public Instant getInstant() {
return Instant.ofEpochSecond(getSec(), getNSec());
}
public String getTimestamp() {
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
return dtFormat.format(getInstant());
}
public String getAfterTimestamp() {
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
return dtFormat.format(getInstant().plusMillis(1));
}
public byte getMsgType() {
return this.msgType;
}
public String getMsgTypeString() {
switch (getMsgType()) {
case 0:
return "Unknown";
case 1:
return "Default";
case 2:
return "Verbose";
case 3:
return "Debug";
case 4:
return "Info";
case 5:
return "Warn";
case 6:
return "Error";
case 7:
return "Fatal";
case 8:
return "Silent";
default:
return "Unknown";
}
}
public String getMsg() {
return this.msg;
}
}
}
......@@ -10,6 +10,8 @@ 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -25,7 +27,7 @@ public class ADBDevice {
private static final Logger LOG = LoggerFactory.getLogger(ADBDevice.class);
private static final String CMD_TRACK_JDWP = "000atrack-jdwp";
private static final Pattern TIMESTAMP_FORMAT = Pattern.compile("^[0-9]{2}\\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}$");
ADBDeviceInfo info;
String androidReleaseVer;
volatile Socket jdwpListenerSock;
......@@ -120,6 +122,49 @@ public class ADBDevice {
return -1;
}
/**
* @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());
}
/**
* @Return binary output of logcat after provided timestamp
* 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);
}
String cmd = "logcat -dB -t \"" + timestamp + "\"";
return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
}
/**
* @Return 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());
}
/**
* @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();
}
public String getAndroidReleaseVersion() {
if (!StringUtils.isEmpty(androidReleaseVer)) {
return androidReleaseVer;
......@@ -160,15 +205,15 @@ public class ADBDevice {
}
public List<Process> getProcessByPkg(String pkg) throws IOException {
return getProcessList("ps | grep " + pkg, 0);
return getProcessList("ps | grep " + pkg);
}
@NonNull
public List<Process> getProcessList() throws IOException {
return getProcessList("ps", 1);
return getProcessList("ps");
}
private List<Process> getProcessList(String cmd, int index) throws IOException {
private List<Process> getProcessList(String cmd) throws IOException {
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
List<Process> procs = new ArrayList<>();
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
......
......@@ -361,7 +361,9 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
debugSetter.name,
debugSetter.device.getDeviceInfo().getAdbHost(),
debugSetter.forwardTcpPort,
debugSetter.ver);
debugSetter.ver,
debugSetter.device,
debugSetter.pid);
} catch (Exception e) {
LOG.error("Failed to attach to process", e);
return false;
......
......@@ -29,6 +29,7 @@ import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.JTree;
......@@ -39,10 +40,14 @@ import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.reactivex.annotations.Nullable;
import jadx.core.utils.StringUtils;
import jadx.gui.device.debugger.DebugController;
import jadx.gui.device.protocol.ADBDevice;
import jadx.gui.treemodel.JClass;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.SmaliArea;
......@@ -53,6 +58,7 @@ import jadx.gui.utils.UiUtils;
public class JDebuggerPanel extends JPanel {
private static final long serialVersionUID = -1111111202102181631L;
private static final Logger LOG = LoggerFactory.getLogger(LogcatPanel.class);
private static final ImageIcon ICON_RUN = UiUtils.openSvgIcon("debugger/execute");
private static final ImageIcon ICON_RERUN = UiUtils.openSvgIcon("debugger/rerun");
......@@ -76,6 +82,7 @@ public class JDebuggerPanel extends JPanel {
private final transient JSplitPane rightSplitter;
private final transient JSplitPane leftSplitter;
private final transient IDebugController controller;
private final LogcatPanel logcatPanel;
private final transient VarTreePopupMenu varTreeMenu;
private transient KeyEventDispatcher controllerShortCutDispatcher;
......@@ -131,11 +138,14 @@ public class JDebuggerPanel extends JPanel {
varTreeMenu = new VarTreePopupMenu(mainWindow);
JPanel loggerPanel = new JPanel(new CardLayout());
JTabbedPane loggerPanel = new JTabbedPane();
logger = new JTextArea();
logger.setEditable(false);
logger.setLineWrap(true);
loggerPanel.add(new JScrollPane(logger));
JScrollPane loggerScroll = new JScrollPane(logger);
loggerPanel.addTab("Debugger Log", null, loggerScroll, null);
this.logcatPanel = new LogcatPanel(this);
loggerPanel.addTab(NLS.str("logcat.logcat"), null, logcatPanel, null);
leftSplitter.setLeftComponent(stackFramePanel);
leftSplitter.setRightComponent(rightSplitter);
......@@ -159,6 +169,7 @@ public class JDebuggerPanel extends JPanel {
JOptionPane.OK_CANCEL_OPTION);
if (what == JOptionPane.OK_OPTION) {
controller.exit();
logcatPanel.exit();
} else {
return;
}
......@@ -373,10 +384,16 @@ public class JDebuggerPanel extends JPanel {
}
}
public boolean showDebugger(String procName, String host, int port, int androidVer) {
public boolean showDebugger(String procName, String host, int port, int androidVer, ADBDevice device, String pid) {
boolean ok = controller.startDebugger(this, host, port, androidVer);
if (ok) {
log(String.format("Attached %s %s:%d", procName, host, port));
try {
logcatPanel.init(device, pid);
} catch (Exception e) {
log(NLS.str("logcat.error_fail_start"));
LOG.error("Logcat failed to start", e);
}
leftSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerStackFrameSplitterLoc());
rightSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerVarTreeSplitterLoc());
mainWindow.showDebuggerPanel();
......
package jadx.gui.ui.panel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoundedRangeModel;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.ListCellRenderer;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.device.debugger.LogcatController;
import jadx.gui.device.protocol.ADB;
import jadx.gui.device.protocol.ADBDevice;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
public class LogcatPanel extends JPanel {
private static final Logger LOG = LoggerFactory.getLogger(LogcatPanel.class);
StyleContext sc = StyleContext.getDefaultStyleContext();
private final AttributeSet defaultAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#6c71c4"));
private final AttributeSet verboseAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#2aa198"));
private final AttributeSet debugAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#859900"));
private final AttributeSet infoAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#586e75"));
private final AttributeSet warningAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#b58900"));
private final AttributeSet errorAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#dc322f"));
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 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");
private transient JTextPane logcatPane;
private final transient JDebuggerPanel debugPanel;
private LogcatController logcatController;
private boolean ready = false;
private List<ADB.Process> procs;
public LogcatPanel(JDebuggerPanel debugPanel) {
this.debugPanel = debugPanel;
}
private ArrayList<Integer> pids;
private JScrollPane logcatScroll;
private int pid;
private final AbstractAction pauseButton = new AbstractAction(NLS.str("logcat.pause"), ICON_PAUSE) {
@Override
public void actionPerformed(ActionEvent e) {
toggleLogcat();
}
};
private final AbstractAction clearButton = new AbstractAction(NLS.str("logcat.clear"), CLEAR_LOGCAT) {
@Override
public void actionPerformed(ActionEvent e) {
clearLogcat();
}
};
public boolean showLogcat() {
ArrayList<String> pkgs = new ArrayList<>();
pids = new ArrayList<>();
JPanel procBox;
for (ADB.Process proc : procs.subList(1, procs.size())) { // skipping first element because it contains the column label
pkgs.add(String.format("[pid: %-6s] %s", proc.pid, proc.name));
pids.add(Integer.valueOf(proc.pid));
}
String[] msgTypes = {
NLS.str("logcat.default"),
NLS.str("logcat.verbose"),
NLS.str("logcat.debug"),
NLS.str("logcat.info"),
NLS.str("logcat.warn"),
NLS.str("logcat.error"),
NLS.str("logcat.fatal"),
NLS.str("logcat.silent") };
Integer[] msgIndex = { 1, 2, 3, 4, 5, 6, 7, 8 };
this.setLayout(new BorderLayout());
logcatPane = new JTextPane();
logcatPane.setEditable(false);
logcatScroll = new JScrollPane(logcatPane);
JToolBar menuPanel = new JToolBar();
CheckCombo procObj = new CheckCombo(NLS.str("logcat.process"), 1, pids.toArray(new Integer[0]), pkgs.toArray(new String[0]));
procBox = procObj.getContent();
procObj.selectAllBut(this.pids.indexOf(this.pid));
JPanel msgTypeBox = new CheckCombo(NLS.str("logcat.level"), 2, msgIndex, msgTypes).getContent();
menuPanel.add(procBox);
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
menuPanel.add(msgTypeBox);
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
menuPanel.add(pauseButton);
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
menuPanel.add(clearButton);
this.add(menuPanel, BorderLayout.NORTH);
this.add(logcatScroll, BorderLayout.CENTER);
return true;
}
public boolean clearLogcatArea() {
logcatPane.setText("");
return true;
}
public boolean init(ADBDevice device, String pid) {
this.pid = Integer.parseInt(pid);
try {
this.logcatController = new LogcatController(this, device);
this.procs = device.getProcessList();
if (!this.showLogcat()) {
debugPanel.log(NLS.str("logcat.error_fail_start"));
}
} catch (Exception e) {
this.ready = false;
LOG.error("Failed to start logcat", e);
return false;
}
this.ready = true;
return true;
}
private void toggleLogcat() {
if (Objects.equals(this.logcatController.getStatus(), "running")) {
this.logcatController.stopLogcat();
this.pauseButton.putValue(Action.SMALL_ICON, ICON_RUN);
this.pauseButton.putValue(Action.NAME, NLS.str("logcat.start"));
} else if (Objects.equals(this.logcatController.getStatus(), "stopped")) {
this.logcatController.startLogcat();
this.pauseButton.putValue(Action.SMALL_ICON, ICON_PAUSE);
this.pauseButton.putValue(Action.NAME, NLS.str("logcat.pause"));
}
}
private void clearLogcat() {
boolean running = false;
if (Objects.equals(this.logcatController.getStatus(), "running")) {
this.logcatController.stopLogcat();
running = true;
}
this.logcatController.clearLogcat();
clearLogcatArea();
this.debugPanel.log(this.logcatController.getStatus());
if (running) {
this.logcatController.startLogcat();
}
}
public boolean isReady() {
return this.ready;
}
private boolean isAtBottom(JScrollBar scrollbar) {
BoundedRangeModel model = scrollbar.getModel();
return (model.getExtent() + model.getValue()) == model.getMaximum();
}
public void log(LogcatController.LogcatInfo logcatInfo) {
boolean atBottom = false;
int len = logcatPane.getDocument().getLength();
JScrollBar scrollbar = logcatScroll.getVerticalScrollBar();
if (isAtBottom(scrollbar)) {
atBottom = true;
}
StringBuilder sb = new StringBuilder();
sb.append(" > ")
.append(logcatInfo.getTimestamp())
.append(" [pid: ")
.append(logcatInfo.getPid())
.append("] ")
.append(logcatInfo.getMsgTypeString())
.append(": ")
.append(logcatInfo.getMsg())
.append("\n");
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;
}
} catch (Exception e) {
LOG.error("Failed to write logcat message", e);
}
if (atBottom) {
EventQueue.invokeLater(() -> scrollbar.setValue(scrollbar.getMaximum()));
}
}
public void exit() {
logcatController.exit();
clearLogcatArea();
this.logcatController.clearEvents();
}
class CheckCombo implements ActionListener {
private final String[] ids;
private final int type;
private final String label;
private final Integer[] index;
private JComboBox<CheckComboStore> combo;
public CheckCombo(String label, int type, Integer[] index, String[] ids) {
this.ids = ids;
this.type = type;
this.label = label;
this.index = index;
}
public void actionPerformed(ActionEvent e) {
JComboBox cb = (JComboBox) e.getSource();
CheckComboStore store = (CheckComboStore) cb.getSelectedItem();
CheckComboRenderer ccr = (CheckComboRenderer) cb.getRenderer();
store.state = !store.state;
ccr.checkBox.setSelected(store.state);
switch (this.type) {
case 1: // process
logcatController.getFilter().togglePid(store.index, store.state);
logcatController.reload();
break;
case 2: // label
logcatController.getFilter().toggleMsgType((byte) store.index, store.state);
logcatController.reload();
break;
default:
LOG.error("Invalid Logcat Filter Type");
break;
}
}
public JPanel getContent() {
JLabel label = new JLabel(this.label + ": ");
CheckComboStore[] stores = new CheckComboStore[ids.length];
for (int j = 0; j < ids.length; j++) {
stores[j] = new CheckComboStore(index[j], ids[j], Boolean.TRUE);
}
combo = new JComboBox<>(stores);
combo.setRenderer(new CheckComboRenderer());
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.weightx = 0;
c.gridwidth = 1;
c.insets = new Insets(0, 1, 0, 1);
panel.add(label, c);
c.weightx = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
c.anchor = GridBagConstraints.WEST;
c.insets = new Insets(0, 1, 0, 1);
panel.add(combo, c);
combo.addActionListener(this);
combo.addMouseListener(new FilterClickListener(this));
return panel;
}
public void toggleAll(boolean checked) {
for (int i = 0; i < combo.getItemCount(); i++) {
CheckComboStore ccs = combo.getItemAt(i);
ccs.state = checked;
switch (type) {
case 1: // process
logcatController.getFilter().togglePid(ccs.index, checked);
break;
case 2: // level
logcatController.getFilter().toggleMsgType((byte) ccs.index, checked);
break;
default:
LOG.error("Invalid Logcat Toggle Filter Encountered");
break;
}
}
logcatController.reload();
}
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;
}
switch (type) {
case 1: // process
logcatController.getFilter().togglePid(ccs.index, ccs.state);
break;
case 2: // level
logcatController.getFilter().toggleMsgType((byte) ccs.index, ccs.state);
break;
default:
LOG.error("Invalid Logcat selectAllBut filter encountered");
break;
}
}
logcatController.reload();
}
}
class CheckComboRenderer implements ListCellRenderer {
JCheckBox checkBox;
ArrayList<JCheckBox> boxes = new ArrayList<>();
public CheckComboRenderer() {
checkBox = new JCheckBox();
}
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
CheckComboStore store = (CheckComboStore) value;
checkBox.setText(store.id);
checkBox.setSelected(store.state);
boxes.add(checkBox);
return checkBox;
}
}
static class CheckComboStore {
String id;
Boolean state;
int index;
public CheckComboStore(int index, String id, Boolean state) {
this.id = id;
this.state = state;
this.index = index;
}
}
class FilterClickListener extends MouseAdapter {
CheckCombo combo;
public FilterClickListener(CheckCombo combo) {
this.combo = combo;
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
doPop(e);
}
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
doPop(e);
}
}
private void doPop(MouseEvent e) {
FilterPopup menu = new FilterPopup(combo);
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
class FilterPopup extends JPopupMenu {
CheckCombo combo;
JMenuItem selectAll;
JMenuItem unselectAll;
JMenuItem selectAttached;
public FilterPopup(CheckCombo combo) {
this.combo = combo;
selectAll = new JMenuItem(NLS.str("logcat.select_all"));
selectAll.addActionListener(actionEvent -> combo.toggleAll(true));
unselectAll = new JMenuItem(NLS.str("logcat.unselect_all"));
unselectAll.addActionListener(actionEvent -> combo.toggleAll(false));
if (combo.type == 1) {
selectAttached = new JMenuItem(NLS.str("logcat.select_attached"));
selectAttached.addActionListener(actionEvent -> combo.selectAllBut(pids.indexOf(pid)));
add(selectAttached);
}
add(selectAll);
add(unselectAll);
}
}
}
......@@ -288,6 +288,26 @@ debugger.popup_change_to_zero=Wechsel zu 0
debugger.popup_change_to_one=Wechsel zu 1
debugger.popup_copy_value=Wert kopieren
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
set_value_dialog.label_value=Wert
set_value_dialog.btn_set=Wert einstellen
set_value_dialog.title=Wert Einstellen
......
......@@ -12,7 +12,7 @@ menu.alwaysSelectOpened=Always Select Opened File/Class
menu.navigation=Navigation
menu.text_search=Text search
menu.class_search=Class search
menu.comment_search=Comment search
menu.comment_search=Comment searchF
menu.tools=Tools
menu.deobfuscation=Deobfuscation
menu.log=Log Viewer
......@@ -288,6 +288,26 @@ debugger.popup_change_to_zero=Change to 0
debugger.popup_change_to_one=Change to 1
debugger.popup_copy_value=Copy Value
logcat.pause=Pause Logcat
logcat.start=Resume Logcat
logcat.clear=Clear Logcat
logcat.error_fail_start=Failed to start logcat
logcat.process=Process
logcat.level=Level
logcat.default=Default
logcat.verbose=Verbose
logcat.debug=Debug
logcat.info=Info
logcat.warn=Warn
logcat.error=Error
logcat.fatal=Fatal
logcat.silent=Silent
logcat.logcat=Logcat
logcat.select_attached=Select Attached
logcat.select_all=Select All
logcat.unselect_all=Unselect All
set_value_dialog.label_value=Value
set_value_dialog.btn_set=Set Value
set_value_dialog.title=Set Value
......
......@@ -288,6 +288,26 @@ certificate.serialPubKeyY=Y
#debugger.popup_change_to_one=Change to 1
#debugger.popup_copy_value=Copy Value
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
#set_value_dialog.label_value=Value
#set_value_dialog.btn_set=Set Value
#set_value_dialog.title=Set Value
......
......@@ -288,6 +288,26 @@ debugger.popup_change_to_zero=0으로 변경
debugger.popup_change_to_one=1로 변경
debugger.popup_copy_value=값 복사
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
set_value_dialog.label_value=
set_value_dialog.btn_set=값 설정
set_value_dialog.title=값 설정
......
......@@ -288,6 +288,26 @@ debugger.popup_change_to_zero=Alterar para 0
debugger.popup_change_to_one=Alterar para 1
debugger.popup_copy_value=Copiar valor
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
set_value_dialog.label_value=Valor
set_value_dialog.btn_set=Definir Valor
set_value_dialog.title=Definir Valor
......
......@@ -288,6 +288,26 @@ debugger.popup_change_to_zero=修改为0
debugger.popup_change_to_one=修改为1
debugger.popup_copy_value=复制值
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
set_value_dialog.label_value=
set_value_dialog.btn_set=修改
set_value_dialog.title=设置新值
......
......@@ -288,6 +288,26 @@ debugger.popup_change_to_zero=修改為 0
debugger.popup_change_to_one=修改為 1
debugger.popup_copy_value=複製數值
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
set_value_dialog.label_value=數值
set_value_dialog.btn_set=設置數值
set_value_dialog.title=設置數值
......
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0002 3H12.0002H13.0002V4H12.0002V13L11.0002 14H4.00024L3.00024 13V4H2.00024V3H5.00024V2C5.00024 1.73478 5.10555 1.48038 5.29309 1.29285C5.48063 1.10531 5.73503 1 6.00024 1H9.00024C9.26546 1 9.51986 1.10531 9.7074 1.29285C9.89493 1.48038 10.0002 1.73478 10.0002 2V3ZM9.00024 2H6.00024V3H9.00024V2ZM4.00024 13H11.0002V4H4.00024V13ZM6.00024 5H5.00024V12H6.00024V5ZM7.00024 5H8.00024V12H7.00024V5ZM9.00024 5H10.0002V12H9.00024V5Z" fill="#424242"/>
</svg>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册