Skip to content

Commit

Permalink
Merge pull request #294 from bndeff/usbapi
Browse files Browse the repository at this point in the history
Add a USB API compatible with libusb
  • Loading branch information
xalexalex committed Oct 6, 2019
2 parents 3149537 + 8294e2d commit 10320ac
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 0 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<uses-feature android:name="android.hardware.microphone" android:required="false" />
<uses-feature android:name="android.hardware.telephony" android:required="false" />
<uses-feature android:name="android.hardware.wifi" android:required="false" />
<uses-feature android:name="android.hardware.usb.host" android:required="false" />

<!-- This permission is not used, but a permission is needed on the sharedfiles contentprovider,
which will always use FLAG_GRANT_READ_URI_PERMISSION. -->
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/termux/api/TermuxApiReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ private void doWork(Context context, Intent intent) {
case "Torch":
TorchAPI.onReceive(this, context, intent);
break;
case "Usb":
UsbAPI.onReceive(this, context, intent);
break;
case "Vibrate":
VibrateAPI.onReceive(this, context, intent);
break;
Expand Down
165 changes: 165 additions & 0 deletions app/src/main/java/com/termux/api/UsbAPI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package com.termux.api;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Looper;
import android.util.JsonWriter;
import android.util.SparseArray;

import com.termux.api.util.ResultReturner;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.HashMap;

import androidx.annotation.NonNull;

public class UsbAPI {

private static SparseArray<UsbDeviceConnection> openDevices = new SparseArray<>();

static void onReceive(final TermuxApiReceiver apiReceiver, final Context context, final Intent intent) {
UsbDevice device;
String action = intent.getAction();
if (action == null) {
ResultReturner.returnData(apiReceiver, intent, out -> out.append("Missing action\n"));
} else {
switch (action) {
case "list":
ResultReturner.returnData(apiReceiver, intent, new ResultReturner.ResultJsonWriter() {
@Override
public void writeJson(JsonWriter out) throws Exception {
listDevices(context, out);
}
});
break;
case "permission":
device = getDevice(apiReceiver, context, intent);
if (device == null) return;
ResultReturner.returnData(apiReceiver, intent, out -> {
boolean result = getPermission(device, context, intent);
out.append(result ? "yes\n" : "no\n");
});
break;
case "open":
device = getDevice(apiReceiver, context, intent);
if (device == null) return;
ResultReturner.returnData(apiReceiver, intent, new ResultReturner.WithAncillaryFd() {
@Override
public void writeResult(PrintWriter out) {
if (getPermission(device, context, intent)) {
int result = open(device, context);
if (result < 0) {
out.append("Failed to open device\n");
} else {
this.setFd(result);
out.append("@"); // has to be non-empty
}
} else out.append("No permission\n");
}
});

break;
default:
ResultReturner.returnData(apiReceiver, intent, out -> out.append("Invalid action\n"));
}
}

}

private static void listDevices(final Context context, JsonWriter out) throws IOException {
final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Iterator<String> deviceIterator = deviceList.keySet().iterator();
out.beginArray();
while (deviceIterator.hasNext()) {
out.value(deviceIterator.next());
}
out.endArray();
}

private static UsbDevice getDevice(final TermuxApiReceiver apiReceiver, final Context context, final Intent intent) {
String deviceName = intent.getStringExtra("device");
final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
UsbDevice device = deviceList.get(deviceName);
if (device == null) {
ResultReturner.returnData(apiReceiver, intent, out -> out.append("No such device\n"));
}
return device;
}

private static boolean hasPermission(final @NonNull UsbDevice device, final Context context) {
final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
return usbManager.hasPermission(device);
}

private static boolean requestPermission(final @NonNull UsbDevice device, final Context context) {
Looper.prepare();
Looper looper = Looper.myLooper();
final boolean[] result = new boolean[1];

final String ACTION_USB_PERMISSION = "com.termux.api.USB_PERMISSION";
final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context usbContext, final Intent usbIntent) {
String action = usbIntent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbDevice device = usbIntent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (usbIntent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
result[0] = true;
if (looper != null) looper.quit();
}
} else {
result[0] = false;
if (looper != null) looper.quit();
}
}

}
}
};

final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0,
new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
context.getApplicationContext().registerReceiver(usbReceiver, filter);
usbManager.requestPermission(device, permissionIntent);
Looper.loop();
return result[0];
}

private static boolean getPermission(final @NonNull UsbDevice device, final Context context, final Intent intent) {
boolean request = intent.getBooleanExtra("request", false);
if(request) {
return requestPermission(device, context);
} else {
return hasPermission(device, context);
}
}

private static int open(final @NonNull UsbDevice device, final Context context) {
final UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
UsbDeviceConnection connection = usbManager.openDevice(device);
if (connection == null)
return -2;
int fd = connection.getFileDescriptor();
if (fd == -1) {
connection.close();
return -1;
}
openDevices.put(fd, connection);
return fd;
}

}
26 changes: 26 additions & 0 deletions app/src/main/java/com/termux/api/util/ResultReturner.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import android.content.Intent;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.ParcelFileDescriptor;
import android.util.JsonWriter;

import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -66,6 +68,18 @@ public final void setInput(InputStream inputStream) throws Exception {
}
}

public static abstract class WithAncillaryFd implements ResultWriter {
private int fd = -1;

public final void setFd(int newFd) {
fd = newFd;
}

public final int getFd() {
return fd;
}
}

public static abstract class ResultJsonWriter implements ResultWriter {
@Override
public final void writeResult(PrintWriter out) throws Exception {
Expand Down Expand Up @@ -102,6 +116,7 @@ public static void returnData(Object context, final Intent intent, final ResultW

final Runnable runnable = () -> {
try {
final ParcelFileDescriptor[] pfds = { null };
try (LocalSocket outputSocket = new LocalSocket()) {
String outputSocketAdress = intent.getStringExtra(SOCKET_OUTPUT_EXTRA);
outputSocket.connect(new LocalSocketAddress(outputSocketAdress));
Expand All @@ -117,9 +132,20 @@ public static void returnData(Object context, final Intent intent, final ResultW
} else {
resultWriter.writeResult(writer);
}
if(resultWriter instanceof WithAncillaryFd) {
int fd = ((WithAncillaryFd) resultWriter).getFd();
if (fd >= 0) {
pfds[0] = ParcelFileDescriptor.adoptFd(fd);
FileDescriptor[] fds = { pfds[0].getFileDescriptor() };
outputSocket.setFileDescriptorsForSend(fds);
}
}
}
}
}
if(pfds[0] != null) {
pfds[0].close();
}

if (asyncResult != null) {
asyncResult.setResultCode(0);
Expand Down

0 comments on commit 10320ac

Please sign in to comment.