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();
+ }
+}