From 7999d50bf6291dccb56dabd1880ea777f46f28d0 Mon Sep 17 00:00:00 2001 From: guory1029 Date: Wed, 2 Jul 2025 11:03:43 +0800 Subject: [PATCH] Add jadb enhacnced features 1. Add kill-server, root, unroot, remound commands support 2. Add pull directory support 3. Upgrade to jdk 1.8 --- .idea/encodings.xml | 4 +- .idea/modules.xml | 9 -- pom.xml | 8 +- src/se/vidstige/jadb/AdbServerController.java | 65 +++++++++ src/se/vidstige/jadb/AdbServerLauncher.java | 3 + src/se/vidstige/jadb/JadbDevice.java | 26 ++++ src/se/vidstige/jadb/RemoteDir.java | 11 ++ .../test/integration/RealDeviceTestCases.java | 22 ++- .../test/unit/AdbServerControllerFixture.java | 126 ++++++++++++++++++ 9 files changed, 256 insertions(+), 18 deletions(-) delete mode 100644 .idea/modules.xml create mode 100644 src/se/vidstige/jadb/AdbServerController.java create mode 100644 src/se/vidstige/jadb/RemoteDir.java create mode 100644 test/se/vidstige/jadb/test/unit/AdbServerControllerFixture.java diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 8ffd759..ee6638b 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,7 +1,9 @@ - + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 296e813..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/pom.xml b/pom.xml index 498ecc6..cc6cdf9 100644 --- a/pom.xml +++ b/pom.xml @@ -28,8 +28,8 @@ UTF-8 UTF-8 - 1.7 - 1.7 + 1.8 + 1.8 2.7.17 @@ -78,8 +78,8 @@ maven-compiler-plugin 3.5.1 - 1.7 - 1.7 + 1.8 + 1.8 diff --git a/src/se/vidstige/jadb/AdbServerController.java b/src/se/vidstige/jadb/AdbServerController.java new file mode 100644 index 0000000..a19ee7c --- /dev/null +++ b/src/se/vidstige/jadb/AdbServerController.java @@ -0,0 +1,65 @@ +package se.vidstige.jadb; + +import java.io.IOException; +import java.util.Map; + +public class AdbServerController { + private final String executable; + private Subprocess subprocess; + + /** + * Creates a new controller loading ADB from the environment. + * + * @param subprocess the sub-process. + * @param environment the environment to use to locate the ADB executable. + */ + public AdbServerController(Subprocess subprocess, Map environment) { + this(subprocess, findAdbExecutable(environment)); + } + + /** + * Creates a new controller with the specified ADB. + * + * @param subprocess the sub-process. + * @param executable the location of the ADB executable. + */ + public AdbServerController(Subprocess subprocess, String executable) { + this.subprocess = subprocess; + this.executable = executable; + } + + private static String findAdbExecutable(Map environment) { + String androidHome = environment.get("ANDROID_HOME"); + if (androidHome == null || androidHome.isEmpty()) { + return "adb"; + } + return androidHome + "/platform-tools/adb"; + } + + private void runWithAdb(String command) throws IOException, InterruptedException { + Process p = subprocess.execute(new String[]{executable, command}); + p.waitFor(); + int exitValue = p.exitValue(); + if (exitValue != 0) throw new IOException("adb exited with exit code: " + exitValue); + } + + public void launch() throws IOException, InterruptedException { + runWithAdb("start-server"); + } + + public void kill() throws IOException, InterruptedException { + runWithAdb("kill-server"); + } + + public void root() throws IOException, InterruptedException { + runWithAdb("root"); + } + + public void unroot() throws IOException, InterruptedException { + runWithAdb("unroot"); + } + + public void remount() throws IOException, InterruptedException { + runWithAdb("remount"); + } +} diff --git a/src/se/vidstige/jadb/AdbServerLauncher.java b/src/se/vidstige/jadb/AdbServerLauncher.java index bb4b4bc..6e1ecc7 100644 --- a/src/se/vidstige/jadb/AdbServerLauncher.java +++ b/src/se/vidstige/jadb/AdbServerLauncher.java @@ -5,7 +5,10 @@ /** * Launches the ADB server + * + * @deprecated Use {@link AdbServerController} instead. */ +@Deprecated public class AdbServerLauncher { private final String executable; private Subprocess subprocess; diff --git a/src/se/vidstige/jadb/JadbDevice.java b/src/se/vidstige/jadb/JadbDevice.java index a16c648..5aa4b8d 100644 --- a/src/se/vidstige/jadb/JadbDevice.java +++ b/src/se/vidstige/jadb/JadbDevice.java @@ -3,6 +3,7 @@ import se.vidstige.jadb.managers.Bash; import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -219,6 +220,31 @@ public void pull(RemoteFile remote, File local) throws IOException, JadbExceptio } } + public void pull(RemoteDir remote, File local) throws IOException, JadbException { + if (!isDirectory(remote)) throw new JadbException("Remote path must be an exists directory"); + if (!local.exists()) local.mkdirs(); + if (!local.isDirectory()) throw new JadbException("Local path must be a directory"); + + InputStream is = executeShell("ls", "-1", remote.getPath()); + String[] remoteFiles = Stream.readAll(is, StandardCharsets.UTF_8).trim().split("\n"); + for (String remoteFile : remoteFiles) { + if (remoteFile.isEmpty() || ".".equals(remoteFile) || "..".equals(remoteFile)) continue; + RemoteFile remotePath = new RemoteFile(remote.getPath() + "/" + remoteFile); + if (isDirectory(remotePath)) { + // Pull file recursively + pull(new RemoteDir(remotePath), new File(local, remoteFile)); + } else { + pull(remotePath, new File(local, remoteFile)); + } + } + } + + public boolean isDirectory(RemoteFile remote) throws IOException, JadbException { + InputStream is = executeShell("if [ -d \"" + remote.getPath() + "\" ]; then echo 'dir'; fi"); + String result = Stream.readAll(is, StandardCharsets.UTF_8).trim(); + return "dir".equals(result); + } + private void send(Transport transport, String command) throws IOException, JadbException { transport.send(command); transport.verifyResponse(); diff --git a/src/se/vidstige/jadb/RemoteDir.java b/src/se/vidstige/jadb/RemoteDir.java new file mode 100644 index 0000000..4dcbd5c --- /dev/null +++ b/src/se/vidstige/jadb/RemoteDir.java @@ -0,0 +1,11 @@ +package se.vidstige.jadb; + +public class RemoteDir extends RemoteFile { + public RemoteDir(RemoteFile remoteFile) { + this(remoteFile.getPath()); + } + + public RemoteDir(String path) { + super(path); + } +} diff --git a/test/se/vidstige/jadb/test/integration/RealDeviceTestCases.java b/test/se/vidstige/jadb/test/integration/RealDeviceTestCases.java index 9913e9d..adcd805 100644 --- a/test/se/vidstige/jadb/test/integration/RealDeviceTestCases.java +++ b/test/se/vidstige/jadb/test/integration/RealDeviceTestCases.java @@ -14,11 +14,11 @@ import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Objects; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class RealDeviceTestCases { @@ -30,7 +30,7 @@ public class RealDeviceTestCases { @BeforeClass public static void tryToStartAdbServer() { try { - new AdbServerLauncher(new Subprocess(), System.getenv()).launch(); + new AdbServerController(new Subprocess(), System.getenv()).launch(); } catch (IOException | InterruptedException e) { System.out.println("Could not start adb-server"); } @@ -98,6 +98,20 @@ public void testPullInvalidFile() throws Exception { any.pull(new RemoteFile("/file/does/not/exist"), temporaryFolder.newFile("xyz")); } + @Test + public void testPullDir() throws Exception { + JadbDevice any = jadb.getAnyDevice(); + InputStream is = any.executeShell("mkdir", "-p", "/sdcard/jadb-test"); + Stream.readAll(is, StandardCharsets.UTF_8); + File readme = new File("README.md"); + any.push(readme, new RemoteFile("/sdcard/jadb-test/README.md")); + any.push(readme, new RemoteFile("/sdcard/jadb-test/README_copy.md")); + + File local = temporaryFolder.newFolder("jadb-test"); + any.pull(new RemoteDir("/sdcard/jadb-test"), local); + assertEquals(2, Objects.requireNonNull(local.list()).length); + } + @SuppressWarnings("deprecation") @Test public void testShellExecuteTwice() throws Exception { diff --git a/test/se/vidstige/jadb/test/unit/AdbServerControllerFixture.java b/test/se/vidstige/jadb/test/unit/AdbServerControllerFixture.java new file mode 100644 index 0000000..a3eee28 --- /dev/null +++ b/test/se/vidstige/jadb/test/unit/AdbServerControllerFixture.java @@ -0,0 +1,126 @@ +package se.vidstige.jadb.test.unit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import se.vidstige.jadb.AdbServerController; +import se.vidstige.jadb.test.fakes.FakeSubprocess; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class AdbServerControllerFixture { + private FakeSubprocess subprocess; + private Map environment = new HashMap<>(); + + @Before + public void setUp() { + subprocess = new FakeSubprocess(); + } + + @After + public void tearDown() { + subprocess.verifyExpectations(); + } + + @Test + public void testStartServer() throws Exception { + subprocess.expect(new String[]{"/abc/platform-tools/adb", "start-server"}, 0); + Map environment = new HashMap<>(); + environment.put("ANDROID_HOME", "/abc"); + new AdbServerController(subprocess, environment).launch(); + } + + @Test + public void testStartServerWithoutANDROID_HOME() throws Exception { + subprocess.expect(new String[]{"adb", "start-server"}, 0); + new AdbServerController(subprocess, environment).launch(); + } + + @Test(expected = IOException.class) + public void testStartServerFails() throws Exception { + subprocess.expect(new String[]{"adb", "start-server"}, -1); + new AdbServerController(subprocess, environment).launch(); + } + + @Test + public void testKillServer() throws Exception { + subprocess.expect(new String[]{"/abc/platform-tools/adb", "kill-server"}, 0); + Map environment = new HashMap<>(); + environment.put("ANDROID_HOME", "/abc"); + new AdbServerController(subprocess, environment).kill(); + } + + @Test + public void testKillServerWithoutANDROID_HOME() throws Exception { + subprocess.expect(new String[]{"adb", "kill-server"}, 0); + new AdbServerController(subprocess, environment).kill(); + } + + @Test(expected = IOException.class) + public void testKillServerFails() throws Exception { + subprocess.expect(new String[]{"adb", "kill-server"}, -1); + new AdbServerController(subprocess, environment).kill(); + } + + @Test + public void testRootDevice() throws Exception { + subprocess.expect(new String[]{"/abc/platform-tools/adb", "root"}, 0); + Map environment = new HashMap<>(); + environment.put("ANDROID_HOME", "/abc"); + new AdbServerController(subprocess, environment).root(); + } + + @Test + public void testRootDeviceWithoutANDROID_HOME() throws Exception { + subprocess.expect(new String[]{"adb", "root"}, 0); + new AdbServerController(subprocess, environment).root(); + } + + @Test(expected = IOException.class) + public void testRootDeviceFails() throws Exception { + subprocess.expect(new String[]{"adb", "root"}, -1); + new AdbServerController(subprocess, environment).root(); + } + + @Test + public void testUnrootDevice() throws Exception { + subprocess.expect(new String[]{"/abc/platform-tools/adb", "unroot"}, 0); + Map environment = new HashMap<>(); + environment.put("ANDROID_HOME", "/abc"); + new AdbServerController(subprocess, environment).unroot(); + } + + @Test + public void testUnrootDeviceWithoutANDROID_HOME() throws Exception { + subprocess.expect(new String[]{"adb", "unroot"}, 0); + new AdbServerController(subprocess, environment).unroot(); + } + + @Test(expected = IOException.class) + public void testUnrootDeviceFails() throws Exception { + subprocess.expect(new String[]{"adb", "unroot"}, -1); + new AdbServerController(subprocess, environment).unroot(); + } + + @Test + public void testRemountDevice() throws Exception { + subprocess.expect(new String[]{"/abc/platform-tools/adb", "remount"}, 0); + Map environment = new HashMap<>(); + environment.put("ANDROID_HOME", "/abc"); + new AdbServerController(subprocess, environment).remount(); + } + + @Test + public void testRemountDeviceWithoutANDROID_HOME() throws Exception { + subprocess.expect(new String[]{"adb", "remount"}, 0); + new AdbServerController(subprocess, environment).remount(); + } + + @Test(expected = IOException.class) + public void testRemountDeviceFails() throws Exception { + subprocess.expect(new String[]{"adb", "remount"}, -1); + new AdbServerController(subprocess, environment).remount(); + } +}