mirror of
https://github.com/revanced/jadb.git
synced 2024-11-19 10:39:23 +01:00
commit
3b5e0ce2fb
@ -22,6 +22,11 @@ It's very easy to send and receive files from your android device, for example a
|
||||
JadbDevice device = ...
|
||||
device.pull(new RemoteFile("/path/to/file.txt"), new File("file.txt"));
|
||||
|
||||
Some high level operations such as installing and uninstalling packages are also available.
|
||||
|
||||
JadbDevice device = ...
|
||||
new PackageManager(device).install(new File("/path/to/my.apk"));
|
||||
|
||||
## Protocol Description ##
|
||||
|
||||
An overview of the protocol can be found here: [Overview](https://github.com/cgjones/android-system-core/blob/master/adb/OVERVIEW.TXT)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package se.vidstige.jadb;
|
||||
|
||||
import se.vidstige.jadb.managers.Bash;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -50,9 +52,7 @@ public class JadbDevice {
|
||||
StringBuilder shellLine = new StringBuilder(command);
|
||||
for (String arg : args) {
|
||||
shellLine.append(" ");
|
||||
// TODO: throw if arg contains double quote
|
||||
// TODO: quote arg if it contains space
|
||||
shellLine.append(arg);
|
||||
shellLine.append(Bash.quote(arg));
|
||||
}
|
||||
send(transport, "shell:" + shellLine.toString());
|
||||
return new AdbFilterInputStream(new BufferedInputStream(transport.getInputStream()));
|
||||
@ -69,15 +69,12 @@ public class JadbDevice {
|
||||
StringBuilder shellLine = new StringBuilder(command);
|
||||
for (String arg : args) {
|
||||
shellLine.append(" ");
|
||||
// TODO: throw if arg contains double quote
|
||||
// TODO: quote arg if it contains space
|
||||
shellLine.append(arg);
|
||||
shellLine.append(Bash.quote(arg));
|
||||
}
|
||||
send(transport, "shell:" + shellLine.toString());
|
||||
if (output != null) {
|
||||
transport.readResponseTo(new AdbFilterOutputStream(output));
|
||||
}
|
||||
//return AdbFilterInputStream(transport.getInputStream());
|
||||
}
|
||||
|
||||
public List<RemoteFile> list(String remotePath) throws IOException, JadbException {
|
||||
|
@ -1,8 +1,10 @@
|
||||
package se.vidstige.jadb;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class Stream {
|
||||
public static void copy(InputStream in, OutputStream out) throws IOException {
|
||||
@ -13,4 +15,9 @@ public class Stream {
|
||||
}
|
||||
}
|
||||
|
||||
public static String readAll(InputStream input, Charset charset) throws IOException {
|
||||
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
|
||||
Stream.copy(input, tmp);
|
||||
return new String(tmp.toByteArray(), charset);
|
||||
}
|
||||
}
|
||||
|
11
src/se/vidstige/jadb/managers/Bash.java
Normal file
11
src/se/vidstige/jadb/managers/Bash.java
Normal file
@ -0,0 +1,11 @@
|
||||
package se.vidstige.jadb.managers;
|
||||
|
||||
public class Bash {
|
||||
public static String quote(String s) {
|
||||
// TODO: Should also check other whitespace
|
||||
if (!s.contains(" ")) {
|
||||
return s;
|
||||
}
|
||||
return "'" + s.replace("'", "'\\''") + "'";
|
||||
}
|
||||
}
|
21
src/se/vidstige/jadb/managers/Package.java
Normal file
21
src/se/vidstige/jadb/managers/Package.java
Normal file
@ -0,0 +1,21 @@
|
||||
package se.vidstige.jadb.managers;
|
||||
|
||||
/**
|
||||
* Android package
|
||||
*/
|
||||
public class Package {
|
||||
private final String name;
|
||||
|
||||
public Package(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return name; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) { return name.equals(o); }
|
||||
|
||||
@Override
|
||||
public int hashCode() { return name.hashCode(); }
|
||||
}
|
67
src/se/vidstige/jadb/managers/PackageManager.java
Normal file
67
src/se/vidstige/jadb/managers/PackageManager.java
Normal file
@ -0,0 +1,67 @@
|
||||
package se.vidstige.jadb.managers;
|
||||
|
||||
import se.vidstige.jadb.JadbDevice;
|
||||
import se.vidstige.jadb.JadbException;
|
||||
import se.vidstige.jadb.RemoteFile;
|
||||
import se.vidstige.jadb.Stream;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Java interface to package manager. Launches package manager through jadb
|
||||
*/
|
||||
public class PackageManager {
|
||||
private final JadbDevice device;
|
||||
|
||||
public PackageManager(JadbDevice device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
public List<Package> getPackages() throws IOException, JadbException {
|
||||
ArrayList<Package> result = new ArrayList<Package>();
|
||||
BufferedReader input = null;
|
||||
try {
|
||||
input = new BufferedReader(new InputStreamReader(device.executeShell("pm", "list", "packages"), Charset.forName("UTF-8")));
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
final String prefix = "package:";
|
||||
if (line.startsWith(prefix)) {
|
||||
result.add(new Package(line.substring(prefix.length())));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (input != null) input.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getErrorMessage(String operation, String target, String errorMessage) {
|
||||
return "Could not " + operation + " " + target + ": " + errorMessage;
|
||||
}
|
||||
|
||||
private void verifyOperation(String operation, String target, String result) throws JadbException {
|
||||
if (!result.contains("Success")) throw new JadbException(getErrorMessage(operation, target, result));
|
||||
}
|
||||
|
||||
public void install(File apkFile) throws IOException, JadbException {
|
||||
RemoteFile remote = new RemoteFile("/sdcard/tmp/" + apkFile.getName());
|
||||
device.push(apkFile, remote);
|
||||
InputStream s = device.executeShell("pm", "install", Bash.quote(remote.getPath()));
|
||||
String result = Stream.readAll(s, Charset.forName("UTF-8"));
|
||||
// TODO: Remove remote file
|
||||
verifyOperation("install", apkFile.getName(), result);
|
||||
}
|
||||
|
||||
public void uninstall(Package name) throws IOException, JadbException {
|
||||
InputStream s = device.executeShell("pm", "uninstall", name.toString());
|
||||
String result = Stream.readAll(s, Charset.forName("UTF-8"));
|
||||
verifyOperation("uninstall", name.toString(), result);
|
||||
}
|
||||
|
||||
public void launch(Package name) throws IOException, JadbException {
|
||||
InputStream s = device.executeShell("monkey", "-p", name.toString(), "-c", "android.intent.category.LAUNCHER", "1");
|
||||
}
|
||||
}
|
@ -15,4 +15,6 @@ public interface AdbDeviceResponder {
|
||||
|
||||
void filePushed(RemoteFile path, int mode, ByteArrayOutputStream buffer) throws JadbException;
|
||||
void filePulled(RemoteFile path, ByteArrayOutputStream buffer) throws JadbException, IOException;
|
||||
|
||||
void shell(String command) throws IOException;
|
||||
}
|
||||
|
@ -80,6 +80,9 @@ class AdbProtocolHandler implements Runnable {
|
||||
SyncTransport sync = new SyncTransport(output, input);
|
||||
sync.send("FAIL", e.getMessage());
|
||||
}
|
||||
} else if (command.startsWith("shell:")) {
|
||||
shell(command.substring("shell:".length()));
|
||||
output.writeBytes("OKAY");
|
||||
} else {
|
||||
throw new ProtocolException("Unknown command: " + command);
|
||||
}
|
||||
@ -91,6 +94,10 @@ class AdbProtocolHandler implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void shell(String command) throws IOException {
|
||||
selected.shell(command);
|
||||
}
|
||||
|
||||
private int readInt(DataInput input) throws IOException {
|
||||
return Integer.reverseBytes(input.readInt());
|
||||
}
|
||||
|
Binary file not shown.
@ -77,6 +77,22 @@ public class MockedTestCases {
|
||||
Assert.assertArrayEquals("foobar".getBytes(Charset.forName("utf-8")), buffer.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteShell() throws Exception {
|
||||
server.add("serial-123");
|
||||
server.expectShell("serial-123", "ls -l");
|
||||
JadbDevice device = connection.getDevices().get(0);
|
||||
device.executeShell("ls", "-l");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteShellQuotesSpace() throws Exception {
|
||||
server.add("serial-123");
|
||||
server.expectShell("serial-123", "ls 'space file'");
|
||||
JadbDevice device = connection.getDevices().get(0);
|
||||
device.executeShell("ls", "space file");
|
||||
}
|
||||
|
||||
private long parseDate(String date) throws ParseException {
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||
return dateFormat.parse(date).getTime();
|
||||
|
53
test/se/vidstige/jadb/test/PackageMangerTests.java
Normal file
53
test/se/vidstige/jadb/test/PackageMangerTests.java
Normal file
@ -0,0 +1,53 @@
|
||||
package se.vidstige.jadb.test;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import se.vidstige.jadb.JadbConnection;
|
||||
import se.vidstige.jadb.managers.Package;
|
||||
import se.vidstige.jadb.managers.PackageManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class PackageMangerTests {
|
||||
private static JadbConnection jadb;
|
||||
private PackageManager pm;
|
||||
|
||||
@BeforeClass
|
||||
public static void connect() throws IOException {
|
||||
try {
|
||||
jadb = new JadbConnection();
|
||||
jadb.getHostVersion();
|
||||
} catch (Exception e) {
|
||||
org.junit.Assume.assumeNoException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createPackageManager()
|
||||
{
|
||||
pm = new PackageManager(jadb.getAnyDevice());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchActivity() throws Exception {
|
||||
pm.launch(new Package("com.android.settings"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListPackages() throws Exception {
|
||||
List<Package> packages = pm.getPackages();
|
||||
for (Package p : packages) {
|
||||
System.out.println(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstallUninstallCycle() throws Exception {
|
||||
File f = new File("test/data/Tiniest Smallest APK ever_v' platformBuildVersionName=_apkpure.com.apk");
|
||||
pm.install(f);
|
||||
pm.uninstall(new Package("b.a"));
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import se.vidstige.jadb.server.AdbServer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.ProtocolException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -75,6 +76,10 @@ public class FakeAdbServer implements AdbResponder {
|
||||
return findBySerial(serial).expectPull(path);
|
||||
}
|
||||
|
||||
public void expectShell(String serial, String commands) {
|
||||
findBySerial(serial).expectShell(commands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AdbDeviceResponder> getDevices() {
|
||||
return new ArrayList<AdbDeviceResponder>(devices);
|
||||
@ -82,7 +87,8 @@ public class FakeAdbServer implements AdbResponder {
|
||||
|
||||
private class DeviceResponder implements AdbDeviceResponder {
|
||||
private final String serial;
|
||||
private List<FileExpectation> expectations = new ArrayList<FileExpectation>();
|
||||
private List<FileExpectation> fileExpectations = new ArrayList<FileExpectation>();
|
||||
private List<ShellExpectation> shellExpectations = new ArrayList<ShellExpectation>();
|
||||
|
||||
private DeviceResponder(String serial) {
|
||||
this.serial = serial;
|
||||
@ -100,9 +106,9 @@ public class FakeAdbServer implements AdbResponder {
|
||||
|
||||
@Override
|
||||
public void filePushed(RemoteFile path, int mode, ByteArrayOutputStream buffer) throws JadbException {
|
||||
for (FileExpectation fe : expectations) {
|
||||
for (FileExpectation fe : fileExpectations) {
|
||||
if (fe.matches(path)) {
|
||||
expectations.remove(fe);
|
||||
fileExpectations.remove(fe);
|
||||
fe.throwIfFail();
|
||||
fe.verifyContent(buffer.toByteArray());
|
||||
return;
|
||||
@ -113,9 +119,9 @@ public class FakeAdbServer implements AdbResponder {
|
||||
|
||||
@Override
|
||||
public void filePulled(RemoteFile path, ByteArrayOutputStream buffer) throws JadbException, IOException {
|
||||
for (FileExpectation fe : expectations) {
|
||||
for (FileExpectation fe : fileExpectations) {
|
||||
if (fe.matches(path)) {
|
||||
expectations.remove(fe);
|
||||
fileExpectations.remove(fe);
|
||||
fe.throwIfFail();
|
||||
fe.returnFile(buffer);
|
||||
return;
|
||||
@ -124,8 +130,20 @@ public class FakeAdbServer implements AdbResponder {
|
||||
throw new JadbException("Unexpected push to device " + serial + " at " + path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shell(String command) throws IOException {
|
||||
for (ShellExpectation se : shellExpectations) {
|
||||
if (se.matches(command)) {
|
||||
shellExpectations.remove(se);
|
||||
se.throwIfFail();
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new ProtocolException("Unexpected shell to device " + serial + ": " + command);
|
||||
}
|
||||
|
||||
public void verifyExpectations() {
|
||||
org.junit.Assert.assertEquals(0, expectations.size());
|
||||
org.junit.Assert.assertEquals(0, fileExpectations.size());
|
||||
}
|
||||
|
||||
private class FileExpectation implements ExpectationBuilder {
|
||||
@ -172,15 +190,37 @@ public class FakeAdbServer implements AdbResponder {
|
||||
}
|
||||
}
|
||||
|
||||
public class ShellExpectation {
|
||||
private final String command;
|
||||
|
||||
public ShellExpectation(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public boolean matches(String command) {
|
||||
return command.equals(this.command);
|
||||
}
|
||||
|
||||
public void throwIfFail() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public ExpectationBuilder expectPush(RemoteFile path) {
|
||||
FileExpectation expectation = new FileExpectation(path);
|
||||
expectations.add(expectation);
|
||||
fileExpectations.add(expectation);
|
||||
return expectation;
|
||||
}
|
||||
|
||||
public ExpectationBuilder expectPull(RemoteFile path) {
|
||||
FileExpectation expectation = new FileExpectation(path);
|
||||
expectations.add(expectation);
|
||||
fileExpectations.add(expectation);
|
||||
return expectation;
|
||||
}
|
||||
|
||||
public ShellExpectation expectShell(String command) {
|
||||
ShellExpectation expectation = new ShellExpectation(command);
|
||||
shellExpectations.add(new ShellExpectation(command));
|
||||
return expectation;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user