This program is developed by MajeurAndroid (majeur.android@gmail.com).
This program is Open Source. Source code can be found on GitHub.
Copyright 2020 MajeurAndroid
Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this
file except in compliance with the License."; 31 | } 32 | 33 | public static final class Adb { 34 | 35 | public static final Object[][] INPUT_KEY_MAP = {{0, "UNKNOWN"}, {1, "MENU"}, {2, "SOFT_RIGHT"}, 36 | {3, "HOME"}, {4, "BACK"}, {5, "CALL"}, {6, "ENDCALL"}, {7, "0"}, {8, "1"}, {9, "2"}, 37 | {10, "3"}, {11, "4"}, {12, "5"}, {13, "6"}, {14, "7"}, {15, "8"}, {16, "9"}, 38 | {17, "STAR"}, {18, "POUND"}, {19, "DPAD_UP"}, {20, "DPAD_DOWN"}, {21, "DPAD_LEFT"}, 39 | {22, "DPAD_RIGHT"}, {23, "DPAD_CENTER"}, {24, "VOLUME_UP"}, {25, "VOLUME_DOWN"}, 40 | {26, "POWER"}, {27, "CAMERA"}, {28, "CLEAR"}, {29, "A"}, {30, "B"}, {31, "C"}, {32, "D"}, 41 | {33, "E"}, {34, "F"}, {35, "G"}, {36, "H"}, {37, "I"}, {38, "J"}, {39, "K"}, {40, "L"}, 42 | {41, "M"}, {42, "N"}, {43, "O"}, {44, "P"}, {45, "Q"}, {46, "R"}, {47, "S"}, {48, "T"}, 43 | {49, "U"}, {50, "V"}, {51, "W"}, {52, "X"}, {53, "Y"}, {54, "Z"}, {55, "COMMA"}, 44 | {56, "PERIOD"}, {57, "ALT_LEFT"}, {58, "ALT_RIGHT"}, {59, "SHIFT_LEFT"}, {60, "SHIFT_RIGHT"}, 45 | {61, "TAB"}, {62, "SPACE"}, {63, "SYM"}, {64, "EXPLORER"}, {65, "ENVELOPE"}, {66, "ENTER"}, 46 | {67, "DEL"}, {68, "GRAVE"}, {69, "MINUS"}, {70, "EQUALS"}, {71, "LEFT_BRACKET"}, 47 | {72, "RIGHT_BRACKET"}, {73, "BACKSLASH"}, {74, "SEMICOLON"}, {75, "APOSTROPHE"}, 48 | {76, "SLASH"}, {77, "AT"}, {78, "NUM"}, {79, "HEADSETHOOK"}, {80, "FOCUS"}, {81, "PLUS"}, 49 | {82, "MENU"}, {83, "NOTIFICATION"}, {84, "SEARCH"}, {85, "TAG_LAST_KEYCODE"}}; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/InputKeysDialog.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars; 2 | 3 | import com.majeur.ars.adb.AdbHelper; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.awt.event.ActionEvent; 8 | import java.awt.event.ActionListener; 9 | import java.net.URL; 10 | 11 | public class InputKeysDialog extends JDialog implements ActionListener { 12 | 13 | private AdbHelper mAdbHelper; 14 | 15 | public InputKeysDialog(Frame owner, AdbHelper adbHelper) { 16 | super(owner, Constants.Strings.WINDOW_TILE_INPUT); 17 | setVisible(true); 18 | setSize(800, 400); 19 | setResizable(false); 20 | setLocationRelativeTo(owner); 21 | URL iconURL = getClass().getResource("/app_icon.png"); 22 | setIconImage(new ImageIcon(iconURL).getImage()); 23 | setLayout(new GridLayout(15, 4)); 24 | 25 | mAdbHelper = adbHelper; 26 | 27 | for (int i = 0; i < Constants.Adb.INPUT_KEY_MAP.length; i++) { 28 | JButton jButton = new JButton((String) Constants.Adb.INPUT_KEY_MAP[i][1]); 29 | jButton.setName(String.valueOf(i)); 30 | jButton.addActionListener(this); 31 | add(jButton); 32 | } 33 | } 34 | 35 | @Override 36 | public void actionPerformed(ActionEvent e) { 37 | int index = Integer.parseInt(((JButton) e.getSource()).getName()); 38 | mAdbHelper.performInputKey((Integer) Constants.Adb.INPUT_KEY_MAP[index][0]); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/JRadioButtonMenu.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars; 2 | 3 | import javax.swing.*; 4 | 5 | public class JRadioButtonMenu extends JMenu { 6 | 7 | private ButtonGroup mButtonGroup; 8 | 9 | public JRadioButtonMenu(String title) { 10 | super(title); 11 | mButtonGroup = new ButtonGroup(); 12 | } 13 | 14 | @Override 15 | public void removeAll() { 16 | super.removeAll(); 17 | mButtonGroup = new ButtonGroup(); 18 | } 19 | 20 | public void add(JRadioButtonMenuItem menuItem) { 21 | super.add(menuItem); 22 | mButtonGroup.add(menuItem); 23 | } 24 | 25 | public void setSelectedIndex(int index) { 26 | mButtonGroup.clearSelection(); 27 | ((JRadioButtonMenuItem) getMenuComponent(index)).setSelected(true); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/LogDialog.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.awt.event.WindowEvent; 6 | import java.awt.event.WindowListener; 7 | import java.net.URL; 8 | 9 | public class LogDialog extends JDialog implements WindowListener { 10 | 11 | public LogDialog(Frame owner) { 12 | super(owner, Constants.Strings.WINDOW_TILE_LOG); 13 | setVisible(true); 14 | setSize(650, 450); 15 | setLocationRelativeTo(owner); 16 | URL iconURL = getClass().getResource("/app_icon.png"); 17 | setIconImage(new ImageIcon(iconURL).getImage()); 18 | 19 | addWindowListener(this); 20 | 21 | JTextPane textArea = new JTextPane(); 22 | Logger.bindUI(textArea); 23 | 24 | getContentPane().add(new JScrollPane(textArea)); 25 | } 26 | 27 | @Override 28 | public void windowOpened(WindowEvent e) { 29 | 30 | } 31 | 32 | @Override 33 | public void windowClosing(WindowEvent e) { 34 | Logger.unbindUI(); 35 | } 36 | 37 | @Override 38 | public void windowClosed(WindowEvent e) { 39 | 40 | } 41 | 42 | @Override 43 | public void windowIconified(WindowEvent e) { 44 | 45 | } 46 | 47 | @Override 48 | public void windowDeiconified(WindowEvent e) { 49 | 50 | } 51 | 52 | @Override 53 | public void windowActivated(WindowEvent e) { 54 | 55 | } 56 | 57 | @Override 58 | public void windowDeactivated(WindowEvent e) { 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/Logger.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars; 2 | 3 | import javax.swing.*; 4 | import java.lang.ref.WeakReference; 5 | import java.util.Calendar; 6 | 7 | public class Logger { 8 | 9 | private static final StringBuilder sStringBuilder = new StringBuilder(""); 10 | private static WeakReferencesTextAreaRef = new WeakReference<>(null); 11 | 12 | static { 13 | begin(); 14 | } 15 | 16 | private static void begin() { 17 | log(new LogEntry(LogEntry.TYPE_BEGIN, "Beginning of LOG")); 18 | } 19 | 20 | public static void d(String msg) { 21 | log(new LogEntry(LogEntry.TYPE_DEBUG, msg)); 22 | updateUI(); 23 | } 24 | 25 | public static void i(String msg, Object... args) { 26 | i(String.format(msg, args)); 27 | } 28 | 29 | public static void i(String msg) { 30 | log(new LogEntry(LogEntry.TYPE_INFO, msg)); 31 | updateUI(); 32 | } 33 | 34 | public static void w(String msg, Object... args) { 35 | w(String.format(msg, args)); 36 | } 37 | 38 | public static void w(String msg) { 39 | log(new LogEntry(LogEntry.TYPE_WARN, msg)); 40 | updateUI(); 41 | } 42 | 43 | public static void e(String msg, Object... args) { 44 | e(String.format(msg, args)); 45 | } 46 | 47 | public static void e(String msg) { 48 | log(new LogEntry(LogEntry.TYPE_ERR, msg)); 49 | updateUI(); 50 | } 51 | 52 | private static void log(LogEntry entry) { 53 | sStringBuilder.append("") 56 | .append(entry.timeStamp) 57 | .append(" ") 58 | .append(entry.readableType()) 59 | .append(": ") 60 | .append(entry.message) 61 | .append("") 62 | .append("
"); 63 | } 64 | 65 | private static void updateUI() { 66 | JTextPane textArea; 67 | if ((textArea = sTextAreaRef.get()) != null) { 68 | textArea.setText(sStringBuilder.toString() + ""); 69 | 70 | JScrollPane scrollPane; 71 | JScrollBar scrollBar; 72 | if (textArea.getParent() instanceof JScrollPane && (scrollPane = (JScrollPane) textArea.getParent()) != null 73 | && (scrollBar = scrollPane.getVerticalScrollBar()) != null) 74 | scrollBar.setValue(scrollBar.getMaximum()); 75 | } 76 | } 77 | 78 | static void bindUI(JTextPane textArea) { 79 | textArea.setContentType("text/html"); 80 | sTextAreaRef = new WeakReference<>(textArea); 81 | updateUI(); 82 | } 83 | 84 | static void unbindUI() { 85 | sTextAreaRef.clear(); 86 | } 87 | 88 | private static class LogEntry { 89 | static final int TYPE_INFO = 0; 90 | static final int TYPE_WARN = 1; 91 | static final int TYPE_ERR = 2; 92 | static final int TYPE_DEBUG = 3; 93 | static final int TYPE_BEGIN = 4; 94 | 95 | final String message; 96 | final String timeStamp; 97 | final int type; 98 | 99 | LogEntry(int type, String message) { 100 | this.type = type; 101 | this.message = message; 102 | Calendar calendar = Calendar.getInstance(); 103 | this.timeStamp = String.format("%02d:%02d:%02d.%03d", calendar.get(Calendar.HOUR), 104 | calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND), calendar.get(Calendar.MILLISECOND)); 105 | } 106 | 107 | String readableType() { 108 | switch (type) { 109 | case TYPE_INFO: 110 | return "INFO"; 111 | case TYPE_WARN: 112 | return "WARNING"; 113 | case TYPE_ERR: 114 | return "ERROR"; 115 | case TYPE_DEBUG: 116 | return "DEBUG"; 117 | case TYPE_BEGIN: 118 | return "LOG"; 119 | default: 120 | return null; 121 | } 122 | } 123 | 124 | String htmlColor() { 125 | switch (type) { 126 | case TYPE_INFO: 127 | return "black"; 128 | case TYPE_WARN: 129 | return "orange"; 130 | case TYPE_ERR: 131 | return "red"; 132 | case TYPE_DEBUG: 133 | return "green"; 134 | case TYPE_BEGIN: 135 | return "blue"; 136 | default: 137 | return null; 138 | } 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/MainFrame.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars; 2 | 3 | import com.majeur.ars.adb.AdbDevice; 4 | import com.majeur.ars.adb.AdbHelper; 5 | import com.majeur.ars.adb.AdbHelper.OnAttachedDevicesChangedListener; 6 | 7 | import javax.swing.*; 8 | import java.awt.event.ActionEvent; 9 | import java.awt.event.ActionListener; 10 | import java.awt.event.WindowAdapter; 11 | import java.awt.event.WindowEvent; 12 | import java.net.URL; 13 | import java.util.List; 14 | 15 | public class MainFrame extends JFrame implements OnAttachedDevicesChangedListener { 16 | 17 | private AdbHelper mAdbHelper; 18 | private AdbDeviceScreenComponent mScreenPanel; 19 | private TitleManager mTitleManager; 20 | 21 | private JRadioButtonMenu mDevicesMenu; 22 | 23 | private JDialog mInputKeysDialog; 24 | private JDialog mLogDialog; 25 | 26 | public MainFrame(final AdbHelper adbHelper) { 27 | setSize(200, 500); 28 | setResizable(false); 29 | setLocationRelativeTo(null); 30 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 31 | setVisible(true); 32 | URL iconURL = getClass().getResource("/app_icon.png"); 33 | setIconImage(new ImageIcon(iconURL).getImage()); 34 | initMenu(); 35 | 36 | mTitleManager = new TitleManager(this); 37 | 38 | mAdbHelper = adbHelper; 39 | mAdbHelper.setAttachedDevicesChangedListener(this); 40 | 41 | mScreenPanel = new AdbDeviceScreenComponent(adbHelper); 42 | getContentPane().add(mScreenPanel); 43 | 44 | pack(); 45 | } 46 | 47 | TitleManager getTitleManger() { 48 | return mTitleManager; 49 | } 50 | 51 | private void setCurrentDevice(int deviceIndex) { 52 | if (deviceIndex == AdbHelper.INVALID_DEVICE_INDEX) { 53 | Logger.i("Selected device: None"); 54 | 55 | if (mScreenPanel.isRendering()) 56 | mScreenPanel.stopUpdate(); 57 | 58 | mAdbHelper.setCurrentDevice(AdbHelper.INVALID_DEVICE_INDEX); 59 | mDevicesMenu.setSelectedIndex(0); // None menuItem index 60 | mTitleManager.toggleMode(false); 61 | 62 | } else { 63 | mAdbHelper.setCurrentDevice(deviceIndex); 64 | AdbDevice device = mAdbHelper.getCurrentDevice(); 65 | Logger.i("Selected device: " + device.getReadableName()); 66 | mTitleManager.postDeviceName(device.getReadableName()); 67 | mTitleManager.toggleMode(true); 68 | 69 | mDevicesMenu.setSelectedIndex(deviceIndex + 1); // Offset to match menuItem index 70 | mScreenPanel.startUpdate(); 71 | mScreenPanel.setScale(0.5); 72 | } 73 | } 74 | 75 | @Override 76 | public void onAttachedDevicesChanged() { 77 | Logger.i("Attached devices changed"); 78 | MenuSelectionManager.defaultManager().clearSelectedPath(); 79 | ButtonGroup buttonGroup = new ButtonGroup(); 80 | mDevicesMenu.removeAll(); 81 | JMenuItem noneMenuItem = new JRadioButtonMenuItem("None"); 82 | buttonGroup.add(noneMenuItem); 83 | mDevicesMenu.add(noneMenuItem); 84 | noneMenuItem.addActionListener(e -> setCurrentDevice(AdbHelper.INVALID_DEVICE_INDEX)); 85 | 86 | Listdevices = mAdbHelper.getAttachedDevices(); 87 | for (int i = 0; i < devices.size(); i++) { 88 | AdbDevice device = devices.get(i); 89 | JMenuItem menuItem = new JRadioButtonMenuItem(device.getReadableName()); 90 | menuItem.setEnabled(device.isAvailable()); 91 | buttonGroup.add(menuItem); 92 | mDevicesMenu.add(menuItem); 93 | final int deviceIndex = i; 94 | menuItem.addActionListener(e -> setCurrentDevice(deviceIndex)); 95 | Logger.i(String.format("Device #%d: ", i) + device.toString()); 96 | } 97 | setCurrentDevice(AdbHelper.INVALID_DEVICE_INDEX); 98 | } 99 | 100 | private void initMenu() { 101 | JMenuBar menuBar = new JMenuBar(); 102 | 103 | menuBar.add(buildDevicesMenu()); 104 | menuBar.add(buildInputMenu()); 105 | menuBar.add(buildViewMenu()); 106 | menuBar.add(buildAboutMenu()); 107 | setJMenuBar(menuBar); 108 | } 109 | 110 | private JMenu buildDevicesMenu() { 111 | mDevicesMenu = new JRadioButtonMenu("Devices"); 112 | return mDevicesMenu; 113 | } 114 | 115 | private JMenu buildInputMenu() { 116 | JMenu inputMenu = new JMenu("Inputs"); 117 | 118 | JMenuItem menuItem = new JMenuItem("Send KeyInputs"); 119 | menuItem.addActionListener(e -> { 120 | if (mInputKeysDialog == null) { 121 | mInputKeysDialog = new InputKeysDialog(MainFrame.this, mAdbHelper); 122 | mInputKeysDialog.addWindowListener(new WindowAdapter() { 123 | @Override 124 | public void windowClosing(WindowEvent windowEvent) { 125 | mInputKeysDialog.dispose(); 126 | mInputKeysDialog = null; 127 | } 128 | }); 129 | } else { 130 | mInputKeysDialog.toFront(); 131 | } 132 | }); 133 | inputMenu.add(menuItem); 134 | return inputMenu; 135 | } 136 | 137 | private JMenu buildViewMenu() { 138 | JMenu viewMenu = new JMenu("View"); 139 | JMenu scaleSubMenu = new JMenu("Scale"); 140 | for (final Object[] entry : Constants.VIEW_SCALE_KEY_MAP) { 141 | JMenuItem scaleMenuItem = new JMenuItem((String) entry[1]); 142 | scaleMenuItem.addActionListener(e -> mScreenPanel.setScale((double) entry[0])); 143 | scaleSubMenu.add(scaleMenuItem); 144 | } 145 | viewMenu.add(scaleSubMenu); 146 | return viewMenu; 147 | } 148 | 149 | private JMenu buildAboutMenu() { 150 | JMenu helpMenu = new JMenu("Help"); 151 | 152 | JMenuItem logMenuItem = new JMenuItem("Debugging LOG"); 153 | logMenuItem.addActionListener(e -> { 154 | if (mLogDialog == null) { 155 | mLogDialog = new LogDialog(MainFrame.this); 156 | mLogDialog.addWindowListener(new WindowAdapter() { 157 | @Override 158 | public void windowClosing(WindowEvent windowEvent) { 159 | mLogDialog.dispose(); 160 | mLogDialog = null; 161 | } 162 | }); 163 | } else { 164 | mLogDialog.toFront(); 165 | } 166 | }); 167 | helpMenu.add(logMenuItem); 168 | helpMenu.addSeparator(); 169 | 170 | JMenuItem aboutMenuItem = new JMenuItem("About Adb Remote Screen"); 171 | aboutMenuItem.addActionListener(new ActionListener() { 172 | @Override 173 | public void actionPerformed(ActionEvent e) { 174 | URL iconURL = getClass().getResource("/app_icon.png"); 175 | JOptionPane.showMessageDialog(MainFrame.this, Constants.Strings.ABOUT_MESSAGE, "About", 176 | JOptionPane.PLAIN_MESSAGE, new ImageIcon(iconURL)); 177 | } 178 | }); 179 | helpMenu.add(aboutMenuItem); 180 | 181 | return helpMenu; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/TitleManager.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars; 2 | 3 | import com.majeur.ars.Constants.Strings; 4 | 5 | import javax.swing.*; 6 | 7 | public class TitleManager { 8 | 9 | private JFrame mFrame; 10 | private boolean toggled; 11 | private String mDeviceName; 12 | private double mFps; 13 | 14 | public TitleManager(JFrame frame) { 15 | mFrame = frame; 16 | update(); 17 | } 18 | 19 | private void update() { 20 | if (toggled) 21 | mFrame.setTitle(String.format(Strings.WINDOW_TILE_DEVICE, mDeviceName, mFps)); 22 | else 23 | mFrame.setTitle(Strings.WINDOW_TILE_REGULAR); 24 | } 25 | 26 | void toggleMode(boolean b) { 27 | toggled = b; 28 | update(); 29 | } 30 | 31 | void postDeviceName(String deviceName) { 32 | mDeviceName = deviceName; 33 | update(); 34 | } 35 | 36 | void postFpsCount(double fpsCount) { 37 | mFps = fpsCount; 38 | update(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/Utils.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.net.URI; 8 | import java.net.URISyntaxException; 9 | import java.util.Collection; 10 | import java.util.List; 11 | import java.util.Locale; 12 | import java.util.Scanner; 13 | 14 | public class Utils { 15 | 16 | public static void openUrlInBrowser(String url) { 17 | Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; 18 | if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { 19 | try { 20 | desktop.browse(new URI(url)); 21 | } catch (IOException | URISyntaxException exception) { 22 | exception.printStackTrace(); 23 | } 24 | } 25 | } 26 | 27 | static String streamToString(java.io.InputStream is) { 28 | java.util.Scanner s = new Scanner(is).useDelimiter("\\A"); 29 | return s.hasNext() ? s.next() : ""; 30 | } 31 | 32 | static String getRunningJarPath() { 33 | try { 34 | String path = Utils.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath(); 35 | File classFile = new File(path); 36 | return classFile.getParent(); 37 | 38 | } catch (URISyntaxException e) { 39 | e.printStackTrace(); 40 | return null; 41 | } 42 | } 43 | 44 | public static void executeOnUiThread(Runnable r) { 45 | SwingUtilities.invokeLater(r); 46 | } 47 | 48 | public static boolean isEmpty(String str) { 49 | for (Character c : str.toCharArray()) 50 | if (Character.isLetterOrDigit(c)) 51 | return false; 52 | return true; 53 | } 54 | 55 | public static String toString(double d) { 56 | return String.format(Locale.US, "%.3f", d); 57 | } 58 | 59 | public static boolean equalsOrder(List c1, List c2) { 60 | if (c1 == null || c2 == null || (c1.size() != c2.size())) 61 | return false; 62 | for (int i = 0; i < c1.size(); i++) 63 | if (!c1.get(i).equals(c2.get(i))) 64 | return false; 65 | return true; 66 | } 67 | 68 | public static String toString(Collection c) { 69 | StringBuilder builder = new StringBuilder("("); 70 | for (T t : c) { 71 | builder.append(t.toString()); 72 | builder.append(", "); 73 | } 74 | builder.delete(builder.length() - 3, builder.length() - 1); 75 | builder.append(")"); 76 | return builder.toString(); 77 | } 78 | 79 | public static void sleep(long t) { 80 | try { 81 | Thread.sleep(t); 82 | } catch (InterruptedException e) { 83 | e.printStackTrace(); 84 | } 85 | } 86 | 87 | public static class OS { 88 | 89 | public enum OSName { 90 | WINDOWS, LINUX, MAC 91 | } 92 | 93 | public static OSName name; 94 | 95 | static { 96 | String os = System.getProperty("os.name").toLowerCase(); 97 | if (os.contains("win")) 98 | name = OSName.WINDOWS; 99 | else if (os.contains("osx")) 100 | name = OSName.MAC; 101 | else if (os.contains("nix") || os.contains("aix") || os.contains("nux")) 102 | name = OSName.LINUX; 103 | } 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/adb/AdbCommandBuilder.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars.adb; 2 | 3 | import com.majeur.ars.Utils; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | public class AdbCommandBuilder { 9 | 10 | private final AdbHelper mAdbHelper; 11 | 12 | public AdbCommandBuilder(AdbHelper adbHelper) { 13 | mAdbHelper = adbHelper; 14 | } 15 | 16 | public List buildTapCommand(double x, double y) { 17 | List command = buildInputCommand(); 18 | command.add("tap"); 19 | command.add(Utils.toString(x)); 20 | command.add(Utils.toString(y)); 21 | return command; 22 | } 23 | 24 | public List buildKeyEventCommand(int keyCode) { 25 | List command = buildInputCommand(); 26 | command.add("keyevent"); 27 | command.add(Integer.toString(keyCode)); 28 | return command; 29 | } 30 | 31 | public List buildSwipeCommand(double x1, double y1, double x2, double y2, long dt) { 32 | List command = buildInputCommand(); 33 | command.add("swipe"); 34 | command.add(Utils.toString(x1)); 35 | command.add(Utils.toString(y1)); 36 | command.add(Utils.toString(x2)); 37 | command.add(Utils.toString(y2)); 38 | command.add(Long.toString(dt)); 39 | return command; 40 | } 41 | 42 | public List buildInputCommand() { 43 | List command = buildShellCommand(); 44 | command.add("input"); 45 | return command; 46 | } 47 | 48 | public List buildShellCommand() { 49 | List command = buildDeviceSpecificCommand(); 50 | command.add("shell"); 51 | return command; 52 | } 53 | 54 | public List buildScreencapCommand() { 55 | List command = buildDeviceSpecificCommand(); 56 | command.add("exec-out"); 57 | command.add("screencap"); 58 | command.add("-p"); 59 | return command; 60 | } 61 | 62 | public List buildDeviceSpecificCommand() { 63 | List command = buildAdbCommand(); 64 | command.add("-s"); 65 | command.add(mAdbHelper.getCurrentDevice().serial); 66 | return command; 67 | } 68 | 69 | public List buildDevicesCommand() { 70 | List command = buildAdbCommand(); 71 | command.add("devices"); 72 | command.add("-l"); 73 | return command; 74 | } 75 | 76 | public List buildKillServerCommand() { 77 | List command = buildAdbCommand(); 78 | command.add("kill-server"); 79 | command.add("-l"); 80 | return command; 81 | } 82 | 83 | public List buildAdbCommand() { 84 | List command = new LinkedList<>(); 85 | command.add(mAdbHelper.getAdbPath()); 86 | return command; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/adb/AdbDevice.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars.adb; 2 | 3 | import com.majeur.ars.Utils; 4 | 5 | public class AdbDevice implements Comparable { 6 | 7 | String serial; 8 | boolean available; 9 | 10 | String product; 11 | String model; 12 | String device; 13 | 14 | AdbDevice(String line) { 15 | String[] array = line.split(" "); 16 | serial = array[0]; 17 | 18 | for (int i = 1; i < array.length; i++) { 19 | if (!Utils.isEmpty(array[i])) { 20 | available = "device".equals(array[i]); 21 | break; 22 | } 23 | } 24 | 25 | for (String s : array) { 26 | if (s.startsWith("product:")) 27 | product = s.substring("product:".length()); 28 | else if (s.startsWith("model:")) 29 | model = s.substring("model:".length()); 30 | else if (s.startsWith("device:")) 31 | device = s.substring("device:".length()); 32 | } 33 | 34 | if (!available) 35 | model = serial.substring(0, Math.min(serial.length(), 4)) + "... (UNAVAILABLE)"; 36 | } 37 | 38 | public String getReadableName() { 39 | return model; 40 | } 41 | 42 | public boolean isAvailable() { 43 | return available; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object object) { 48 | if (object instanceof AdbDevice) 49 | return serial.equals(((AdbDevice) object).serial) && available == ((AdbDevice) object).available; 50 | return false; 51 | } 52 | 53 | @Override 54 | public int compareTo(AdbDevice device) { 55 | return serial.compareTo(device.serial); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return available ? model : "unavailable" + " [" + serial + "]"; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/adb/AdbDevicesWatcher.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars.adb; 2 | 3 | import com.majeur.ars.Utils; 4 | 5 | import javax.swing.*; 6 | import java.util.Collections; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | public class AdbDevicesWatcher implements Runnable { 11 | 12 | private final AdbHelper mAdbHelper; 13 | private Thread mWatchThread; 14 | private List mPreviouslyConnectedDevices; 15 | 16 | public AdbDevicesWatcher(AdbHelper adbHelper) { 17 | mAdbHelper = adbHelper; 18 | } 19 | 20 | void startWatch() { 21 | mWatchThread = new Thread(this); 22 | mWatchThread.start(); 23 | } 24 | 25 | void stopWatch() { 26 | mWatchThread.interrupt(); 27 | mWatchThread = null; 28 | } 29 | 30 | @Override 31 | public void run() { 32 | while (!Thread.interrupted()) { 33 | 34 | final List devices = new LinkedList<>(); 35 | 36 | AdbCommandBuilder commandBuilder = mAdbHelper.getCommandBuilder(); 37 | byte[] data = CommandExec.exec(commandBuilder.buildDevicesCommand()); 38 | if (data != null) { 39 | String[] lines = new String(data).split("\\n"); 40 | for (String line : lines) { 41 | if (line.startsWith("*") || line.startsWith("List") || Utils.isEmpty(line)) // line.startsWith("adb 42 | // server") 43 | continue; 44 | devices.add(new AdbDevice(line)); 45 | } 46 | } 47 | 48 | Collections.sort(devices); 49 | if (!Utils.equalsOrder(devices, mPreviouslyConnectedDevices)) { 50 | mPreviouslyConnectedDevices = devices; 51 | SwingUtilities.invokeLater(() -> mAdbHelper.onDevicesChanged(devices)); 52 | } 53 | 54 | Utils.sleep(5000); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/adb/AdbHelper.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars.adb; 2 | 3 | import com.majeur.ars.Logger; 4 | 5 | import javax.imageio.ImageIO; 6 | import java.awt.image.BufferedImage; 7 | import java.io.ByteArrayInputStream; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class AdbHelper { 14 | 15 | public static final int INVALID_DEVICE_INDEX = -1; 16 | 17 | private final String mAdbPath; 18 | private final AdbCommandBuilder mCommandBuilder; 19 | private final AdbDevicesWatcher mDevicesWatcher; 20 | 21 | private OnAttachedDevicesChangedListener mAttachedDevicesChangedListener; 22 | private List mAttachedDevices; 23 | private int mCurrentDeviceIndex; 24 | 25 | public AdbHelper(File adbFile) { 26 | mAdbPath = adbFile.getAbsolutePath(); 27 | mCommandBuilder = new AdbCommandBuilder(this); 28 | 29 | Runtime.getRuntime().addShutdownHook(new Thread(() -> executeCommand(mCommandBuilder.buildKillServerCommand()))); 30 | 31 | mDevicesWatcher = new AdbDevicesWatcher(this); 32 | mDevicesWatcher.startWatch(); 33 | } 34 | 35 | public void release() { 36 | mDevicesWatcher.stopWatch(); 37 | } 38 | 39 | public AdbDevice getCurrentDevice() { 40 | return mAttachedDevices.get(mCurrentDeviceIndex); 41 | } 42 | 43 | public List getAttachedDevices() { 44 | return Collections.unmodifiableList(mAttachedDevices); 45 | } 46 | 47 | public void setAttachedDevicesChangedListener(OnAttachedDevicesChangedListener listener) { 48 | mAttachedDevicesChangedListener = listener; 49 | } 50 | 51 | public void setCurrentDevice(int index) { 52 | mCurrentDeviceIndex = index; 53 | } 54 | 55 | String getAdbPath() { 56 | return mAdbPath; 57 | } 58 | 59 | AdbCommandBuilder getCommandBuilder() { 60 | return mCommandBuilder; 61 | } 62 | 63 | void onDevicesChanged(List newDevices) { 64 | mCurrentDeviceIndex = INVALID_DEVICE_INDEX; 65 | mAttachedDevices = newDevices; 66 | if (mAttachedDevicesChangedListener != null) 67 | mAttachedDevicesChangedListener.onAttachedDevicesChanged(); 68 | } 69 | 70 | public void performInputKey(int keyCode) { 71 | if (mCurrentDeviceIndex == INVALID_DEVICE_INDEX) 72 | return; 73 | 74 | Logger.i("Key pressed (code: %d)", keyCode); 75 | executeCommand(mCommandBuilder.buildKeyEventCommand(keyCode)); 76 | } 77 | 78 | public void performClick(double x, double y) { 79 | if (mCurrentDeviceIndex == INVALID_DEVICE_INDEX) 80 | return; 81 | 82 | Logger.i("Click at %.1fx%.1f", x, y); 83 | executeCommand(mCommandBuilder.buildTapCommand(x, y)); 84 | } 85 | 86 | public void performSwipe(double x1, double y1, double x2, double y2, long duration) { 87 | if (mCurrentDeviceIndex == INVALID_DEVICE_INDEX) 88 | return; 89 | 90 | Logger.i("Swipe from %.1fx%.1f to %.1fx%.1f during %d ms", x1, y1, x2, y2, duration); 91 | executeCommand(mCommandBuilder.buildSwipeCommand(x1, y1, x2, y2, duration)); 92 | } 93 | 94 | public BufferedImage retrieveScreenShot() throws IOException { 95 | if (mCurrentDeviceIndex == INVALID_DEVICE_INDEX) { 96 | Logger.e("No device selected, screenshot aborted"); 97 | return null; 98 | } 99 | 100 | byte[] data = CommandExec.exec(mCommandBuilder.buildScreencapCommand()); 101 | if (data == null) { 102 | Logger.e("Fatal error retrieving screenshot"); 103 | return null; 104 | } 105 | ByteArrayInputStream inputStream = new ByteArrayInputStream(data); 106 | // No need to call close() for ByteArrayInputStream 107 | return ImageIO.read(inputStream); 108 | } 109 | 110 | private void executeCommand(List command) { 111 | CommandExec.execAsync(command, null); 112 | } 113 | 114 | public interface OnAttachedDevicesChangedListener { 115 | void onAttachedDevicesChanged(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/majeur/ars/adb/CommandExec.java: -------------------------------------------------------------------------------- 1 | package com.majeur.ars.adb; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.List; 7 | 8 | public class CommandExec { 9 | 10 | interface Callback { 11 | void callback(byte[] data); 12 | } 13 | 14 | static void execAsync(final List command, final Callback callback) { 15 | new Thread(() -> { 16 | byte[] data = exec(command); 17 | if (callback != null) 18 | callback.callback(data); 19 | }).start(); 20 | } 21 | 22 | static byte[] exec(List command) { 23 | //Logger.d(Arrays.toString(command.toArray())); 24 | ProcessBuilder builder = new ProcessBuilder(command); 25 | builder.redirectErrorStream(true); 26 | Process process; 27 | try { 28 | process = builder.start(); 29 | 30 | InputStream inputStream = process.getInputStream(); 31 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 32 | 33 | int nRead; 34 | byte[] dataChunk = new byte[16384]; 35 | while ((nRead = inputStream.read(dataChunk, 0, dataChunk.length)) != -1) 36 | buffer.write(dataChunk, 0, nRead); 37 | buffer.flush(); 38 | 39 | byte[] data = buffer.toByteArray(); 40 | 41 | buffer.close(); 42 | inputStream.close(); 43 | 44 | return data; 45 | 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | return null; 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/resources/adb_2701_linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MajeurAndroid/java-adb-remote-screen/195903dbd25daf2ec30c386d3d2a7a33fc18f3e6/src/main/resources/adb_2701_linux -------------------------------------------------------------------------------- /src/main/resources/adb_2701_win: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MajeurAndroid/java-adb-remote-screen/195903dbd25daf2ec30c386d3d2a7a33fc18f3e6/src/main/resources/adb_2701_win -------------------------------------------------------------------------------- /src/main/resources/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MajeurAndroid/java-adb-remote-screen/195903dbd25daf2ec30c386d3d2a7a33fc18f3e6/src/main/resources/app_icon.png -------------------------------------------------------------------------------- /web_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MajeurAndroid/java-adb-remote-screen/195903dbd25daf2ec30c386d3d2a7a33fc18f3e6/web_demo.png --------------------------------------------------------------------------------