Skip to content

Commit a7b9ae1

Browse files
authoredDec 25, 2024··
Merge pull request #30 from jpage4500/feature/12-10
- download apps (apk) from phone, use property for column, auto-resize all
2 parents cb09082 + 8c0cc4f commit a7b9ae1

16 files changed

+382
-170
lines changed
 

‎src/main/java/com/jpage4500/devicemanager/MainApplication.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ private void setupLogging() {
6969
File deviceManagerFolder = Utils.getDeviceManagerFolder();
7070
logger.setFileLog(new File(deviceManagerFolder, "device_manager_log.txt"));
7171

72-
boolean isDebugMode = PreferenceUtils.getPreference(PreferenceUtils.PrefBoolean.PREF_DEBUG_MODE, false);
73-
logger.setFileLogLevel(isDebugMode ? Log.DEBUG : Log.INFO);
72+
int logLevel = PreferenceUtils.getPreference(PreferenceUtils.PrefInt.PREF_LOG_LEVEL, Log.INFO);
73+
logger.setFileLogLevel(logLevel);
7474
} else {
7575
System.out.println("ERROR: no logger found: " + iLoggerFactory.getClass().getSimpleName());
7676
}

‎src/main/java/com/jpage4500/devicemanager/logging/AppLoggerFactory.java

+7
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ public void setFileLogLevel(int fileLogLevel) {
9797
this.fileLogLevel = fileLogLevel;
9898
}
9999

100+
/**
101+
* @return log level which app is logging at
102+
*/
103+
public int getFileLogLevel() {
104+
return fileLogLevel;
105+
}
106+
100107
@Override
101108
public org.slf4j.Logger getLogger(final String name) {
102109
AppLogger appLogger = this.nameToLogMap.get(name);

‎src/main/java/com/jpage4500/devicemanager/logging/Log.java

+1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ public final class Log {
3131
* Priority constant for the println method.
3232
*/
3333
public static final int ASSERT = 7;
34+
3435
}

‎src/main/java/com/jpage4500/devicemanager/manager/DeviceManager.java

+42-19
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,8 @@ private void fetchDeviceDetails(Device device, boolean fullRefresh, DeviceListen
292292
// -- disk free space --
293293
fetchFreeDiskSpace(device);
294294

295-
// -- version of installed apps --
296-
fetchInstalledAppVersions(device);
295+
// -- custom apps --
296+
fetchCustomColumns(device);
297297

298298
// -- battery level, charging status, etc --
299299
fetchBatteryInfo(device);
@@ -369,12 +369,32 @@ private void fetchBatteryInfo(Device device) {
369369
}
370370
}
371371

372-
private void fetchInstalledAppVersions(Device device) {
373-
List<String> customApps = SettingsDialog.getCustomApps();
374-
for (String customApp : customApps) {
375-
String versionName = getAppVersion(device, customApp);
376-
if (device.customAppVersionList == null) device.customAppVersionList = new HashMap<>();
377-
device.customAppVersionList.put(customApp, versionName);
372+
private void fetchCustomColumns(Device device) {
373+
List<String> entryList = SettingsDialog.getCustomColumns();
374+
for (String entry : entryList) {
375+
if (TextUtils.isEmpty(entry) || TextUtils.startsWithAny(entry, false, "#", "//")) continue;
376+
String[] entryArr = entry.split(":");
377+
String label = entryArr.length >= 1 ? entryArr[0].trim() : entry;
378+
String type = entryArr.length >= 2 ? entryArr[1].trim() : "VER";
379+
String val = entryArr.length >= 3 ? entryArr[2].trim() : null;
380+
381+
String value = null;
382+
if (TextUtils.equalsIgnoreCase(type, "VER")) {
383+
value = getAppVersion(device, val);
384+
} else if (TextUtils.equalsIgnoreCase(type, "PROP")) {
385+
ShellResult result = runShell(device, "getprop " + val);
386+
log.trace("fetchCustomColumns: {} -> {}", val, result);
387+
if (result.isSuccess) {
388+
value = result.getResult(0);
389+
}
390+
} else {
391+
log.trace("fetchCustomColumns: unknown type:{}", type);
392+
}
393+
394+
if (value != null) {
395+
if (device.customAppVersionList == null) device.customAppVersionList = new HashMap<>();
396+
device.customAppVersionList.put(label, value);
397+
}
378398
}
379399
}
380400

@@ -479,8 +499,8 @@ public List<Device> getDevices() {
479499
}
480500

481501
public static class ShellResult {
482-
boolean isSuccess;
483-
List<String> resultList;
502+
public boolean isSuccess;
503+
public List<String> resultList;
484504

485505
public String getResult(int index) {
486506
if (resultList != null && resultList.size() > index) return resultList.get(index);
@@ -697,7 +717,7 @@ public void installApp(Device device, File file, TaskListener listener) {
697717
packageManager.install(file);
698718
if (listener != null) listener.onTaskComplete(true, null);
699719
} catch (Exception e) {
700-
log.error("installApp: {}, {}", file.getAbsolutePath(), e.getMessage());
720+
log.error("installApp: ERROR: {}, file:{}", e.getMessage(), file.getAbsolutePath());
701721
device.status = "failed: " + e.getMessage();
702722
if (listener != null) listener.onTaskComplete(false, e.getMessage());
703723
}
@@ -750,17 +770,12 @@ public void restartDevice(Device device, TaskListener listener) {
750770
});
751771
}
752772

753-
public void runCustomCommand(Device device, String customCommand, TaskListener listener) {
773+
public void runCustomCommand(Device device, String customCommand, CommandListener listener) {
754774
commandExecutorService.submit(() -> {
755775
ShellResult result = runShell(device, customCommand);
756776
boolean isSuccess = result.isSuccess;
757777
log.trace("runCustomCommand: DONE: success:{}, {}", isSuccess, GsonHelper.toJson(result.resultList));
758-
String displayStr = TextUtils.join(result.resultList, "\n");
759-
// check if command runs but fails
760-
if (TextUtils.containsIgnoreCase(displayStr, "inaccessible or not found")) {
761-
isSuccess = false;
762-
}
763-
if (listener != null) listener.onTaskComplete(isSuccess, displayStr);
778+
if (listener != null) listener.onTaskComplete(result);
764779
});
765780
}
766781

@@ -847,9 +862,17 @@ public interface TaskListener {
847862
void onTaskComplete(boolean isSuccess, String error);
848863
}
849864

865+
public interface CommandListener {
866+
void onTaskComplete(ShellResult result);
867+
}
868+
850869
public void downloadFile(Device device, String path, DeviceFile file, File saveFile, TaskListener listener) {
851870
log.debug("downloadFile: {}/{} -> {}", path, file.name, saveFile.getAbsolutePath());
852-
commandExecutorService.submit(() -> downloadFileInternal(device, path, file, saveFile));
871+
commandExecutorService.submit(() -> {
872+
downloadFileInternal(device, path, file, saveFile);
873+
// test if file was created
874+
listener.onTaskComplete(saveFile.exists(), null);
875+
});
853876
}
854877

855878
/**

‎src/main/java/com/jpage4500/devicemanager/table/DeviceTableModel.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class DeviceTableModel extends AbstractTableModel {
1414
private static final Logger log = LoggerFactory.getLogger(DeviceTableModel.class);
1515

1616
private final List<Device> deviceList;
17-
private final List<String> appList;
17+
private final List<String> customColumnList;
1818
private Columns[] visibleColumns;
1919

2020
public enum Columns {
@@ -44,7 +44,7 @@ public String toString() {
4444

4545
public DeviceTableModel() {
4646
deviceList = new ArrayList<>();
47-
appList = new ArrayList<>();
47+
customColumnList = new ArrayList<>();
4848
setHiddenColumns(null);
4949
}
5050

@@ -125,17 +125,17 @@ public int getRowForDevice(Device device) {
125125
return -1;
126126
}
127127

128-
public void setAppList(List<String> appList) {
129-
if (this.appList.equals(appList)) return;
130-
this.appList.clear();
131-
this.appList.addAll(appList);
128+
public void setCustomColumnList(List<String> appList) {
129+
if (this.customColumnList.equals(appList)) return;
130+
this.customColumnList.clear();
131+
this.customColumnList.addAll(appList);
132132

133133
// update columns
134134
fireTableStructureChanged();
135135
}
136136

137137
public int getColumnCount() {
138-
return visibleColumns.length + appList.size();
138+
return visibleColumns.length + customColumnList.size();
139139
}
140140

141141
@Override
@@ -148,7 +148,7 @@ public String getColumnName(int i) {
148148
Columns colType = visibleColumns[i];
149149
return colType.toString();
150150
} else {
151-
return appList.get(i - visibleColumns.length);
151+
return customColumnList.get(i - visibleColumns.length);
152152
}
153153
}
154154

@@ -181,9 +181,9 @@ public String deviceValue(Device device, int column) {
181181
case CUSTOM2 -> device.getCustomProperty(Device.CUST_PROP_2);
182182
};
183183
} else {
184-
// custom app version
184+
// custom columns
185185
if (device.customAppVersionList != null) {
186-
String appName = appList.get(column - visibleColumns.length);
186+
String appName = customColumnList.get(column - visibleColumns.length);
187187
return device.customAppVersionList.get(appName);
188188
} else {
189189
return null;

‎src/main/java/com/jpage4500/devicemanager/table/utils/TableColumnAdjuster.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,12 @@ private int getCellDataWidth(int row, int column) {
142142
*/
143143
private void updateTableColumn(int column, int width) {
144144
final TableColumn tableColumn = table.getColumnModel().getColumn(column);
145-
log.trace("updateTableColumn: col:{}, wid:{}", column, width);
145+
//log.trace("updateTableColumn: col:{}, wid:{}", column, width);
146146
if (!tableColumn.getResizable()) return;
147147

148148
width += spacing;
149149

150150
// Don't shrink the column width
151-
152151
if (isOnlyAdjustLarger) {
153152
width = Math.max(width, tableColumn.getPreferredWidth());
154153
}

‎src/main/java/com/jpage4500/devicemanager/ui/BaseScreen.java

+6-8
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,12 @@ public void windowClosed(WindowEvent e) {
5353
});
5454

5555
// TODO: handle window resizing
56-
if (PreferenceUtils.getPreference(PreferenceUtils.PrefBoolean.PREF_DEBUG_MODE)) {
57-
addComponentListener(new ComponentAdapter() {
58-
@Override
59-
public void componentResized(ComponentEvent componentEvent) {
60-
log.trace("componentResized: {}: W:{}, H:{}", prefKey, getWidth(), getHeight());
61-
}
62-
});
63-
}
56+
//addComponentListener(new ComponentAdapter() {
57+
// @Override
58+
// public void componentResized(ComponentEvent componentEvent) {
59+
// log.trace("componentResized: {}: W:{}, H:{}", prefKey, getWidth(), getHeight());
60+
// }
61+
//});
6462

6563
// NOTE: this breaks dragging the scrollbar on Mac
6664
// getRootPane().putClientProperty("apple.awt.draggableWindowBackground", true);

‎src/main/java/com/jpage4500/devicemanager/ui/DeviceScreen.java

+126-34
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.jpage4500.devicemanager.MainApplication;
44
import com.jpage4500.devicemanager.data.Device;
5+
import com.jpage4500.devicemanager.data.DeviceFile;
56
import com.jpage4500.devicemanager.data.GithubRelease;
67
import com.jpage4500.devicemanager.logging.AppLoggerFactory;
78
import com.jpage4500.devicemanager.manager.DeviceManager;
@@ -51,6 +52,7 @@ public class DeviceScreen extends BaseScreen implements DeviceManager.DeviceList
5152
// update check for github releases
5253
public static final String UPDATE_SOURCE_GITHUB = "https://api.github.com/repos/jpage4500/AndroidDeviceManager/releases";
5354
public static final String URL_GITHUB = "https://github.com/jpage4500/AndroidDeviceManager/releases";
55+
public static final String PACKAGE_PREFIX = "package:";
5456

5557
public CustomTable table;
5658
public DeviceTableModel model;
@@ -276,17 +278,19 @@ public void setupTable() {
276278
model = new DeviceTableModel();
277279

278280
// restore previous settings
279-
List<String> appList = SettingsDialog.getCustomApps();
280-
model.setAppList(appList);
281+
setCustomColumns();
281282

282283
List<String> hiddenColList = SettingsDialog.getHiddenColumnList();
283284
model.setHiddenColumns(hiddenColList);
284285

285-
//table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
286286
table.setModel(model);
287287
table.setDefaultRenderer(Device.class, new DeviceCellRenderer());
288288
table.setEmptyText("No Connected Devices!");
289289

290+
boolean autoResize = PreferenceUtils.getPreference(PreferenceUtils.PrefBoolean.PREF_DEVICE_AUTO_RESIZE, true);
291+
int flag = autoResize ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF;
292+
table.setAutoResizeMode(flag);
293+
290294
// restore user-defined column sizes
291295
if (!table.restoreTable()) {
292296
// use some default column sizes
@@ -346,6 +350,18 @@ public void setupTable() {
346350
filterTextField.setupSearch(table);
347351
}
348352

353+
public void setCustomColumns() {
354+
List<String> entryList = SettingsDialog.getCustomColumns();
355+
List<String> nameList = new ArrayList<>();
356+
for (String entry : entryList) {
357+
if (TextUtils.isEmpty(entry) || TextUtils.startsWithAny(entry, false, "#", "//")) continue;
358+
String[] entryArr = entry.split(":");
359+
String label = entryArr.length >= 1 ? entryArr[0].trim() : entry;
360+
nameList.add(label);
361+
}
362+
model.setCustomColumnList(nameList);
363+
}
364+
349365
/**
350366
* @return PopupMenu to display or null
351367
*/
@@ -355,15 +371,33 @@ private JPopupMenu getPopupMenu(int row, int column) {
355371
JPopupMenu popupMenu = new JPopupMenu();
356372
DeviceTableModel.Columns columnType = model.getColumnType(column);
357373
if (columnType != null) {
374+
// standard columns (all others are custom)
358375
UiUtils.addPopupMenuItem(popupMenu, "Hide " + columnType.name(), actionEvent -> handleHideColumn(column));
359-
UiUtils.addPopupMenuItem(popupMenu, "Size to Fit", actionEvent -> {
376+
}
377+
UiUtils.addPopupMenuItem(popupMenu, "Size to Fit", actionEvent -> {
378+
TableColumnAdjuster adjuster = new TableColumnAdjuster(table, 0);
379+
adjuster.adjustColumn(column);
380+
});
381+
382+
popupMenu.addSeparator();
383+
384+
UiUtils.addPopupMenuItem(popupMenu, "Manage Columns", actionEvent -> SettingsDialog.showManageDeviceColumnsDialog(this));
385+
386+
boolean autoResize = PreferenceUtils.getPreference(PreferenceUtils.PrefBoolean.PREF_DEVICE_AUTO_RESIZE, true);
387+
String resizeDesc = autoResize ? "ON" : "OFF";
388+
UiUtils.addPopupMenuItem(popupMenu, "Auto Resize: " + resizeDesc, actionEvent -> {
389+
boolean update = !autoResize;
390+
PreferenceUtils.setPreference(PreferenceUtils.PrefBoolean.PREF_DEVICE_AUTO_RESIZE, update);
391+
int flag = update ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF;
392+
table.setAutoResizeMode(flag);
393+
});
394+
if (!autoResize) {
395+
UiUtils.addPopupMenuItem(popupMenu, "Size ALL to Fit", actionEvent -> {
360396
TableColumnAdjuster adjuster = new TableColumnAdjuster(table, 0);
361-
adjuster.adjustColumn(column);
397+
adjuster.adjustColumns();
362398
});
363-
UiUtils.addPopupMenuItem(popupMenu, "Manage Columns", actionEvent -> SettingsDialog.showManageDeviceColumnsDialog(this));
364-
return popupMenu;
365399
}
366-
return null;
400+
return popupMenu;
367401
}
368402
Device device = model.getDeviceAtRow(row);
369403
if (device == null) return null;
@@ -485,7 +519,6 @@ public void handleDeviceRemoved(Device device) {
485519
updateDeviceState(device);
486520
sorter.sort();
487521
});
488-
489522
}
490523

491524
@Override
@@ -683,10 +716,10 @@ public void handleFilesDropped(List<File> fileList) {
683716
return;
684717
}
685718
log.debug("handleFilesDropped: {}, #devices:{}", fileList, selectedDeviceList.size());
686-
installOrCopyFiles(selectedDeviceList, fileList, null);
719+
installOrCopyFiles(selectedDeviceList, fileList);
687720
}
688721

689-
public void installOrCopyFiles(List<Device> selectedDeviceList, List<File> fileList, DeviceManager.TaskListener listener) {
722+
public void installOrCopyFiles(List<Device> selectedDeviceList, List<File> fileList) {
690723
FileUtils.FileStats stats = FileUtils.getFileStats(fileList);
691724
// if all files are .apk, do install instead of copy
692725
boolean isInstall = stats.numApk == stats.numTotal;
@@ -701,14 +734,16 @@ public void installOrCopyFiles(List<Device> selectedDeviceList, List<File> fileL
701734
dialog.setAlwaysOnTop(true);
702735
if (!DialogHelper.showConfirmDialog(this, title, msg)) return;
703736
if (isInstall) {
704-
installFiles(selectedDeviceList, fileList, listener);
737+
installFiles(selectedDeviceList, fileList);
705738
} else {
706-
copyFiles(selectedDeviceList, fileList, listener);
739+
copyFiles(selectedDeviceList, fileList);
707740
}
708741
}
709742

710-
private void copyFiles(List<Device> selectedDeviceList, List<File> fileList, DeviceManager.TaskListener listener) {
711-
ResultWatcher resultWatcher = new ResultWatcher(getRootPane(), selectedDeviceList.size(), listener);
743+
private void copyFiles(List<Device> selectedDeviceList, List<File> fileList) {
744+
ResultWatcher resultWatcher = new ResultWatcher(getRootPane(), selectedDeviceList.size(), (isSuccess, error) -> {
745+
746+
});
712747
String desc = String.format("Copying %d file(s) to %d device(s)", fileList.size(), selectedDeviceList.size());
713748
resultWatcher.setDesc(desc);
714749
// TODO: where to put files on device?
@@ -724,8 +759,15 @@ private void copyFiles(List<Device> selectedDeviceList, List<File> fileList, Dev
724759
}
725760
}
726761

727-
private void installFiles(List<Device> selectedDeviceList, List<File> apkList, DeviceManager.TaskListener listener) {
728-
ResultWatcher resultWatcher = new ResultWatcher(getRootPane(), selectedDeviceList.size() * apkList.size(), listener);
762+
private void installFiles(List<Device> selectedDeviceList, List<File> apkList) {
763+
ResultWatcher resultWatcher = new ResultWatcher(getRootPane(), selectedDeviceList.size() * apkList.size(), (isSuccess, error) -> {
764+
if (isSuccess) {
765+
// TODO: prompt to open app
766+
// - requires figuring out the package name from .apk (aapt2?)
767+
} else {
768+
DialogHelper.showDialog(this, "Install Failed", error);
769+
}
770+
});
729771
for (Device device : selectedDeviceList) {
730772
for (File file : apkList) {
731773
String filename = file.getName();
@@ -886,22 +928,68 @@ private void handleDeviceDetails(Device device) {
886928
private void showInstalledApps(Device device) {
887929
if (device == null) return;
888930
DeviceManager.getInstance().getInstalledApps(device, appSet -> {
889-
final Map<String, String> appMep = new TreeMap<>();
931+
final Map<String, String> appMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
890932
// convert set to map
891-
for (String app : appSet) appMep.put(app, null);
892-
DialogHelper.showListDialog(this, "Installed Apps", appMep, (key, value) -> {
893-
log.trace("showInstalledApps: click: {}", key);
894-
DeviceManager.getInstance().fetchAppVersion(device, key, version -> {
895-
String text = String.format("%s = %s", key, version);
896-
DialogHelper.showTextDialog(this, key, text);
897-
});
933+
for (String app : appSet) appMap.put(app, null);
934+
DialogHelper.showListDialog(this, "Installed Apps", appMap, new DialogHelper.ListListener() {
935+
@Override
936+
public void handleDoubleClick(String key, String value) {
937+
log.trace("showInstalledApps: click: {}", key);
938+
DeviceManager.getInstance().fetchAppVersion(device, key, version -> {
939+
String text = String.format("%s = %s", key, version);
940+
DialogHelper.showTextDialog(DeviceScreen.this, key, text);
941+
});
942+
}
943+
944+
@Override
945+
public void handleRightClick(String key, String value, JPopupMenu popupMenu) {
946+
UiUtils.addPopupMenuItem(popupMenu, "Download App", actionEvent -> {
947+
extractApk(device, key);
948+
});
949+
}
898950
});
899951
});
900952
}
901953

954+
private void extractApk(Device device, String key) {
955+
String command = "pm path " + key;
956+
DeviceManager deviceManager = DeviceManager.getInstance();
957+
deviceManager.runCustomCommand(device, command, (result) -> {
958+
if (!result.isSuccess) {
959+
String msg = "Unable to download " + key + "\n\n" + result;
960+
DialogHelper.showDialog(this, "Error", msg);
961+
return;
962+
}
963+
// download to new folder
964+
String downloadFolder = Utils.getDownloadFolder();
965+
File appFolder = new File(downloadFolder, key);
966+
appFolder.mkdirs();
967+
968+
for (String path : result.resultList) {
969+
if (!TextUtils.startsWith(path, PACKAGE_PREFIX)) {
970+
log.trace("extractApk: BAD LINE: {}", path);
971+
continue;
972+
}
973+
path = path.substring(PACKAGE_PREFIX.length());
974+
int pos = path.lastIndexOf('/');
975+
if (pos < 1) continue;
976+
DeviceFile file = new DeviceFile();
977+
file.name = path.substring(pos + 1);
978+
path = path.substring(0, pos);
979+
980+
File saveFile = new File(appFolder, file.name);
981+
deviceManager.downloadFile(device, path, file, saveFile, (isSuccess, error) -> {
982+
log.trace("extractApk: {}: {}", isSuccess, error);
983+
});
984+
}
985+
});
986+
}
987+
902988
private void showDeviceProperties(Device device) {
903989
if (device == null || device.propMap == null) return;
904-
DialogHelper.showListDialog(this, "Device Properties", device.propMap, null);
990+
TreeMap<String, String> sortedPropMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
991+
sortedPropMap.putAll(device.propMap);
992+
DialogHelper.showListDialog(this, "Device Properties", sortedPropMap, null);
905993
}
906994

907995
private void addDeviceDetail(JPanel panel, String label, String value) {
@@ -1174,19 +1262,23 @@ public void mousePressed(MouseEvent e) {
11741262

11751263
private void handleCustomScriptClicked(File script, String name) {
11761264
List<Device> selectedDeviceList = getSelectedDevices(true);
1177-
if (selectedDeviceList.isEmpty()) return;
1265+
//if (selectedDeviceList.isEmpty()) return;
11781266

11791267
log.trace("handleCustomScriptClicked: {}, {}", name, script.getAbsolutePath());
11801268

1181-
ResultWatcher resultWatcher = new ResultWatcher(getRootPane(), selectedDeviceList.size());
1182-
for (Device device : selectedDeviceList) {
1269+
String[] serialArr = new String[selectedDeviceList.size()];
1270+
for (int i = 0; i < selectedDeviceList.size(); i++) {
1271+
Device device = selectedDeviceList.get(i);
11831272
setDeviceBusy(device, true);
1184-
DeviceManager.getInstance().runCustomScript((isSuccess, error) -> {
1185-
log.trace("mousePressed: DONE:{}, {}", isSuccess, error);
1186-
setDeviceBusy(device, false);
1187-
resultWatcher.handleResult(device.serial, isSuccess, error);
1188-
}, script.getAbsolutePath(), device.serial);
1273+
serialArr[i] = device.serial;
11891274
}
1275+
1276+
DeviceManager.getInstance().runCustomScript((isSuccess, error) -> {
1277+
log.trace("handleCustomScriptClicked: DONE:{}, {}", isSuccess, error);
1278+
for (Device device : selectedDeviceList) {
1279+
setDeviceBusy(device, false);
1280+
}
1281+
}, script.getAbsolutePath(), serialArr);
11901282
}
11911283

11921284
private void handleSettingsClicked() {

‎src/main/java/com/jpage4500/devicemanager/ui/dialog/CommandDialog.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,9 @@ private void runCommand() {
166166
});
167167
resultWatcher.setDesc("-- COMMAND -- \n" + command);
168168
for (Device device : selectedDeviceList) {
169-
DeviceManager.getInstance().runCustomCommand(device, command, (isSuccess, error) -> {
170-
resultWatcher.handleResult(device.serial, isSuccess, error);
169+
DeviceManager.getInstance().runCustomCommand(device, command, (result) -> {
170+
String displayStr = TextUtils.join(result.resultList, "\n");
171+
resultWatcher.handleResult(device.serial, result.isSuccess, displayStr);
171172
});
172173
}
173174
}

‎src/main/java/com/jpage4500/devicemanager/ui/dialog/SettingsDialog.java

+69-64
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
import org.slf4j.LoggerFactory;
1515

1616
import javax.swing.*;
17-
import java.awt.event.MouseAdapter;
18-
import java.awt.event.MouseEvent;
1917
import java.io.File;
2018
import java.util.ArrayList;
2119
import java.util.List;
@@ -39,76 +37,67 @@ private SettingsDialog(DeviceScreen deviceScreen) {
3937
}
4038

4139
private void initalizeUi() {
42-
addButton("Manage Columns", "EDIT", () -> showManageDeviceColumnsDialog(deviceScreen));
43-
addButton("Custom Apps", "EDIT", this::showAppsSettings);
44-
addButton("Customize Toolbar", "EDIT", () -> showManageToolbar(deviceScreen));
45-
addButton("Download Location", "EDIT", this::showDownloadLocation);
46-
47-
addCheckbox("Minimize to System Tray", PreferenceUtils.PrefBoolean.PREF_EXIT_TO_TRAY, false, null);
48-
addCheckbox("Check for updates", PreferenceUtils.PrefBoolean.PREF_CHECK_UPDATES, true, isChecked -> deviceScreen.scheduleUpdateChecks());
49-
addCheckbox("Show background image", PreferenceUtils.PrefBoolean.PREF_SHOW_BACKGROUND, true, isChecked -> {
40+
UiUtils.addSettingButton(this, "Manage Columns", "EDIT", () -> showManageDeviceColumnsDialog(deviceScreen));
41+
UiUtils.addSettingButton(this, "Custom Columns", "EDIT", this::showAppsSettings);
42+
UiUtils.addSettingButton(this, "Customize Toolbar", "EDIT", () -> showManageToolbar(deviceScreen));
43+
UiUtils.addSettingButton(this, "Download Location", "EDIT", this::showDownloadLocation);
44+
45+
UiUtils.addSettingCheckbox(this, "Minimize to System Tray", PreferenceUtils.PrefBoolean.PREF_EXIT_TO_TRAY, false, null);
46+
UiUtils.addSettingCheckbox(this, "Check for updates", PreferenceUtils.PrefBoolean.PREF_CHECK_UPDATES, true, isChecked -> deviceScreen.scheduleUpdateChecks());
47+
UiUtils.addSettingCheckbox(this, "Show background image", PreferenceUtils.PrefBoolean.PREF_SHOW_BACKGROUND, true, isChecked -> {
5048
// force table background to be repainted
5149
deviceScreen.model.fireTableDataChanged();
5250
});
53-
addCheckbox("Debug Mode", PreferenceUtils.PrefBoolean.PREF_DEBUG_MODE, false, isChecked -> {
54-
AppLoggerFactory logger = (AppLoggerFactory) LoggerFactory.getILoggerFactory();
55-
logger.setFileLogLevel(isChecked ? Log.DEBUG : Log.INFO);
56-
});
5751

58-
addButton("View Logs", "VIEW", this::viewLogs);
59-
addButton("Reset Preferences", "RESET", this::resetPreferences);
52+
JButton logButton = UiUtils.addSettingButton(this, "Log Level", "EDIT", null);
53+
UiUtils.addClickListener(logButton, e -> toggleLogLevels(logButton));
54+
updateLogLevel(logButton);
55+
56+
UiUtils.addSettingButton(this, "View Logs", "VIEW", this::viewLogs);
57+
UiUtils.addSettingButton(this, "Reset Preferences", "RESET", this::resetPreferences);
6058

6159
doLayout();
6260
invalidate();
6361
}
6462

65-
public interface ButtonListener {
66-
void onClicked();
67-
}
68-
69-
private void addButton(String label, String action, ButtonListener listener) {
70-
add(new JLabel(label));
71-
JButton button = new JButton(action);
72-
button.addMouseListener(new MouseAdapter() {
73-
@Override
74-
public void mouseClicked(MouseEvent e) {
75-
listener.onClicked();
76-
}
77-
});
78-
add(button, "wrap");
79-
}
80-
81-
public interface CheckBoxListener {
82-
void onChecked(boolean isChecked);
63+
private void updateLogLevel(JButton logButton) {
64+
int logLevel = PreferenceUtils.getPreference(PreferenceUtils.PrefInt.PREF_LOG_LEVEL, Log.INFO);
65+
String name;
66+
switch (logLevel) {
67+
case Log.INFO:
68+
name = "Info";
69+
break;
70+
case Log.DEBUG:
71+
name = "Debug";
72+
break;
73+
case Log.VERBOSE:
74+
default:
75+
name = "Trace";
76+
break;
77+
}
78+
logButton.setText(name);
8379
}
8480

85-
private void addCheckbox(String label, PreferenceUtils.PrefBoolean pref, boolean defaultValue, CheckBoxListener listener) {
86-
JLabel textLabel = new JLabel(label);
87-
add(textLabel);
88-
89-
JCheckBox checkbox = new JCheckBox();
90-
boolean currentChecked = PreferenceUtils.getPreference(pref, defaultValue);
91-
checkbox.setSelected(currentChecked);
92-
checkbox.setHorizontalTextPosition(SwingConstants.LEFT);
93-
add(checkbox, "align center, wrap");
94-
95-
checkbox.addActionListener(actionEvent -> {
96-
boolean selected = checkbox.isSelected();
97-
PreferenceUtils.setPreference(pref, selected);
98-
if (listener != null) listener.onChecked(selected);
99-
});
81+
private void toggleLogLevels(JButton logButton) {
82+
int logLevel = PreferenceUtils.getPreference(PreferenceUtils.PrefInt.PREF_LOG_LEVEL, Log.INFO);
83+
switch (logLevel) {
84+
case Log.INFO:
85+
logLevel = Log.DEBUG;
86+
break;
87+
case Log.DEBUG:
88+
logLevel = Log.VERBOSE;
89+
break;
90+
case Log.VERBOSE:
91+
default:
92+
logLevel = Log.INFO;
93+
break;
94+
}
95+
PreferenceUtils.setPreference(PreferenceUtils.PrefInt.PREF_LOG_LEVEL, logLevel);
10096

101-
textLabel.addMouseListener(new MouseAdapter() {
102-
@Override
103-
public void mouseClicked(MouseEvent mouseEvent) {
104-
// TODO: fire checkbox action listener directly
105-
boolean selected = !checkbox.isSelected();
106-
checkbox.setSelected(selected);
107-
PreferenceUtils.setPreference(pref, selected);
108-
if (listener != null) listener.onChecked(selected);
109-
}
110-
});
97+
AppLoggerFactory logger = (AppLoggerFactory) LoggerFactory.getILoggerFactory();
98+
logger.setFileLogLevel(logLevel);
11199

100+
updateLogLevel(logButton);
112101
}
113102

114103
private void resetPreferences() {
@@ -229,18 +218,34 @@ public static void showManageToolbar(DeviceScreen deviceScreen) {
229218
}
230219

231220
private void showAppsSettings() {
232-
List<String> appList = getCustomApps();
233-
List<String> resultList = showMultilineEditDialog("Custom Apps", "Enter package name(s) to track - 1 per line", appList);
221+
String msg = """
222+
<html>
223+
<b>Format: "LABEL:TYPE:VALUE"</b>
224+
<ul>
225+
<li>LABEL is the column header<br/></li>
226+
<li>TYPE describes the VALUE. one of: [VER|PROP]<br/></li>
227+
<li>VALUE is a package name (version) or property (getprop)</li>
228+
<li>Each line is a column</li>
229+
</ul>
230+
Examples:
231+
<ul>
232+
<li>TG:VER:org.telegram.messenger.web</li>
233+
<li>Groups:PROP:my.cust.prop</li>
234+
</ul>
235+
</html>
236+
""";
237+
List<String> appList = getCustomColumns();
238+
List<String> resultList = showMultilineEditDialog("Custom Columns", msg, appList);
234239
if (resultList == null) return;
235240

236241
PreferenceUtils.setPreference(PreferenceUtils.Pref.PREF_CUSTOM_APPS, GsonHelper.toJson(resultList));
237-
deviceScreen.model.setAppList(resultList);
242+
deviceScreen.setCustomColumns();
238243
}
239244

240245
/**
241-
* get list of custom monitored apps
246+
* get list of custom columns
242247
*/
243-
public static List<String> getCustomApps() {
248+
public static List<String> getCustomColumns() {
244249
String appPrefs = PreferenceUtils.getPreference(PreferenceUtils.Pref.PREF_CUSTOM_APPS);
245250
return GsonHelper.stringToList(appPrefs, String.class);
246251
}

‎src/main/java/com/jpage4500/devicemanager/ui/views/CustomTable.java

+1-5
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
import org.slf4j.LoggerFactory;
66

77
import javax.swing.*;
8-
import javax.swing.event.ChangeEvent;
9-
import javax.swing.event.ListSelectionEvent;
10-
import javax.swing.event.TableColumnModelEvent;
11-
import javax.swing.event.TableColumnModelListener;
128
import javax.swing.table.*;
139
import java.awt.*;
1410
import java.awt.dnd.DropTarget;
@@ -422,7 +418,7 @@ public TableColumn getColumnByName(String searchName) {
422418
return column;
423419
}
424420
}
425-
log.error("getColumnByName: NOT_FOUND:{}, {}", searchName, Utils.getStackTraceString());
421+
if (log.isTraceEnabled()) log.trace("getColumnByName: NOT_FOUND:{}, {}", searchName, Utils.getStackTraceString());
426422
return null;
427423
}
428424

‎src/main/java/com/jpage4500/devicemanager/utils/DialogHelper.java

+11-12
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,17 @@ public static String showInputDialog(Component component, String title, String t
6868
return result;
6969
}
7070

71-
public interface DoubleClickListener {
71+
public interface ListListener {
7272
void handleDoubleClick(String key, String value);
73+
74+
void handleRightClick(String key, String value, JPopupMenu menu);
7375
}
7476

7577
/**
7678
* show a UI List of key-value pairs
7779
* NOTE: contains a filter to quickly narrow the list
7880
*/
79-
public static void showListDialog(Component component, String title, Map<String, String> keyValueMap, DoubleClickListener listener) {
81+
public static void showListDialog(Component component, String title, Map<String, String> keyValueMap, ListListener listener) {
8082
JPanel panel = new JPanel(new MigLayout());
8183
DefaultListModel<String> listModel = new DefaultListModel<>();
8284

@@ -91,8 +93,11 @@ public void mouseClicked(MouseEvent evt) {
9193
list.requestFocus();
9294
int index = list.locationToIndex(evt.getPoint());
9395
list.setSelectedIndex(index);
94-
String value = list.getSelectedValue();
96+
String[] valueArr = TextUtils.split(list.getSelectedValue(), KEY_VALUE_DELIM);
97+
String key = valueArr[0];
98+
String value = valueArr.length > 1 ? valueArr[1] : null;
9599
JPopupMenu popupMenu = new JPopupMenu();
100+
if (listener != null) listener.handleRightClick(key, value, popupMenu);
96101
UiUtils.addPopupMenuItem(popupMenu, "Copy to Clipboard", actionEvent -> {
97102
log.trace("mouseClicked: copy: {}", value);
98103
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
@@ -103,15 +108,9 @@ public void mouseClicked(MouseEvent evt) {
103108
} else if (evt.getClickCount() == 2) {
104109
String selectedValue = list.getSelectedValue();
105110
if (listener != null) {
106-
String key, value;
107-
int i = selectedValue.indexOf(KEY_VALUE_DELIM);
108-
if (i > 0) {
109-
key = selectedValue.substring(0, i);
110-
value = selectedValue.substring(i + 1);
111-
} else {
112-
key = selectedValue;
113-
value = null;
114-
}
111+
String[] valueArr = TextUtils.split(list.getSelectedValue(), KEY_VALUE_DELIM);
112+
String key = valueArr[0];
113+
String value = valueArr.length > 1 ? valueArr[1] : null;
115114
listener.handleDoubleClick(key, value);
116115
} else {
117116
JTextArea textArea = new JTextArea(selectedValue);

‎src/main/java/com/jpage4500/devicemanager/utils/PreferenceUtils.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,21 @@ public enum Pref {
3333
* Boolean value preferences
3434
*/
3535
public enum PrefBoolean {
36-
PREF_DEBUG_MODE,
3736
PREF_CHECK_UPDATES,
3837
PREF_ALWAYS_ON_TOP,
3938
PREF_USE_ROOT,
4039
PREF_SHOW_BACKGROUND,
4140
PREF_AUTO_FORMAT_MESSAGE,
4241
PREF_WRAP_MESSAGE,
4342
PREF_EXIT_TO_TRAY,
43+
PREF_DEVICE_AUTO_RESIZE,
4444
}
4545

4646
/**
4747
* Boolean value preferences
4848
*/
4949
public enum PrefInt {
50+
PREF_LOG_LEVEL,
5051
PREF_LAST_DEVICE_PORT,
5152
PREF_FONT_SIZE_OFFSET,
5253
PREF_LOGS_FONT_SIZE,

‎src/main/java/com/jpage4500/devicemanager/utils/ResultWatcher.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void setDesc(String desc) {
5252
this.desc = desc;
5353
}
5454

55-
public void handleResult(String device, boolean isSuccess, String message) {
55+
public boolean handleResult(String device, boolean isSuccess, String message) {
5656
synchronized (resultList) {
5757
resultList.add(new Result(device, isSuccess, message));
5858
}
@@ -65,18 +65,17 @@ public void handleResult(String device, boolean isSuccess, String message) {
6565
boolean isError = false;
6666
StringBuilder sb = new StringBuilder();
6767
if (desc != null) sb.append(desc + "\n\n");
68-
sb.append("-- RESULTS --\n");
6968
for (int i = 0; i < resultList.size(); i++) {
7069
if (i > 0) sb.append("\n");
7170
Result result = resultList.get(i);
7271
// only show results with a message
7372
if (result.message == null) continue;
7473

75-
if (device != null) {
74+
if (device != null && numResults > 1) {
7675
sb.append(result.device);
7776
sb.append(": ");
7877
}
79-
sb.append(result.isSucess ? "OK" : "FAIL");
78+
sb.append(result.isSucess ? "OK" : "ERROR");
8079
sb.append(": ");
8180
sb.append(result.message);
8281
if (!result.isSucess) {
@@ -90,7 +89,9 @@ public void handleResult(String device, boolean isSuccess, String message) {
9089
DialogHelper.showTextDialog(component, "Results", sb.toString());
9190
}
9291
});
92+
return true;
9393
}
94+
return false;
9495
}
9596

9697
}

‎src/main/java/com/jpage4500/devicemanager/utils/UiUtils.java

+48
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,52 @@ public static JMenuItem addMenuItem(JMenu menu, String label, ActionListener lis
139139
return menuItem;
140140
}
141141

142+
public interface ButtonListener {
143+
void onClicked();
144+
}
145+
146+
public static JButton addSettingButton(Container panel, String label, String action, ButtonListener listener) {
147+
panel.add(new JLabel(label));
148+
JButton button = new JButton(action);
149+
if (listener != null) {
150+
UiUtils.addClickListener(button, e -> {
151+
listener.onClicked();
152+
});
153+
}
154+
panel.add(button, "wrap");
155+
return button;
156+
}
157+
158+
public interface CheckBoxListener {
159+
void onChecked(boolean isChecked);
160+
}
161+
162+
public static JCheckBox addSettingCheckbox(Container panel, String label, PreferenceUtils.PrefBoolean pref, boolean defaultValue, CheckBoxListener listener) {
163+
JLabel textLabel = new JLabel(label);
164+
panel.add(textLabel);
165+
166+
JCheckBox checkbox = new JCheckBox();
167+
boolean currentChecked = PreferenceUtils.getPreference(pref, defaultValue);
168+
checkbox.setSelected(currentChecked);
169+
checkbox.setHorizontalTextPosition(SwingConstants.LEFT);
170+
panel.add(checkbox, "align center, wrap");
171+
172+
checkbox.addActionListener(actionEvent -> {
173+
boolean selected = checkbox.isSelected();
174+
PreferenceUtils.setPreference(pref, selected);
175+
if (listener != null) listener.onChecked(selected);
176+
});
177+
178+
textLabel.addMouseListener(new MouseAdapter() {
179+
@Override
180+
public void mouseClicked(MouseEvent mouseEvent) {
181+
// TODO: fire checkbox action listener directly
182+
boolean selected = !checkbox.isSelected();
183+
checkbox.setSelected(selected);
184+
PreferenceUtils.setPreference(pref, selected);
185+
if (listener != null) listener.onChecked(selected);
186+
}
187+
});
188+
return checkbox;
189+
}
142190
}
+48-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,57 @@
11
#!/bin/bash
22
###############################################################################
33
# run custom script
4-
# ARG1: device serial
5-
# ARG2: script name (full path)
6-
# ARG3: download directory
4+
# ARG1: script name (full path)
5+
# ARG*: serial number
76
###############################################################################
87

9-
ADB_DEVICE=$1
10-
SCRIPT=$2
11-
DOWNLOAD_DIR=$3
8+
SCRIPT=$1
9+
shift
10+
DEVICES="$@"
1211

1312
cd "$(/usr/bin/dirname $0)"
1413
source ./env-vars.sh
1514

16-
${SCRIPT} "${ADB_DEVICE}" "${DOWNLOAD_DIR}"
15+
function handleMacOSX() {
16+
if [[ -d /Applications/iTerm.app ]]; then
17+
echo "using iTerm"
18+
osascript <<END
19+
tell application "iTerm2"
20+
activate
21+
tell current window
22+
create tab with default profile
23+
tell current session
24+
write text "${SCRIPT} ${DEVICES}"
25+
end tell
26+
end tell
27+
end tell
28+
END
29+
else
30+
echo "using Terminal"
31+
osascript <<END
32+
tell application "Terminal"
33+
activate
34+
tell application "System Events" to keystroke "t" using command down
35+
repeat while contents of selected tab of window 1 starts with linefeed
36+
delay 0.01
37+
end repeat
38+
do script "${SCRIPT} ${DEVICES}" in window 1
39+
end tell
40+
END
41+
fi
42+
}
43+
44+
function handleLinux() {
45+
# TODO: TEST
46+
gnome-terminal -- bash -c "${SCRIPT} ${DEVICES}"
47+
}
48+
49+
##
50+
if [[ "$OSTYPE" == "darwin"* ]]; then
51+
handleMacOSX
52+
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
53+
handleLinux
54+
else
55+
echo "unknown OS: $OSTYPE"
56+
exit 1
57+
fi

0 commit comments

Comments
 (0)
Please sign in to comment.