diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 029fbbd..3562da7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,6 +34,7 @@
+
diff --git a/app/src/main/java/com/termux/api/TermuxApiReceiver.java b/app/src/main/java/com/termux/api/TermuxApiReceiver.java
index f790bee..e3c6604 100644
--- a/app/src/main/java/com/termux/api/TermuxApiReceiver.java
+++ b/app/src/main/java/com/termux/api/TermuxApiReceiver.java
@@ -174,6 +174,9 @@ public class TermuxApiReceiver extends BroadcastReceiver {
case "Torch":
TorchAPI.onReceive(this, context, intent);
break;
+ case "Usb":
+ UsbAPI.onReceive(this, context, intent);
+ break;
case "Vibrate":
VibrateAPI.onReceive(this, context, intent);
break;
diff --git a/app/src/main/java/com/termux/api/UsbAPI.java b/app/src/main/java/com/termux/api/UsbAPI.java
new file mode 100644
index 0000000..d71f118
--- /dev/null
+++ b/app/src/main/java/com/termux/api/UsbAPI.java
@@ -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 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 deviceList = usbManager.getDeviceList();
+ Iterator 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 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;
+ }
+
+}
diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java
index c9ab418..b50ca38 100644
--- a/app/src/main/java/com/termux/api/util/ResultReturner.java
+++ b/app/src/main/java/com/termux/api/util/ResultReturner.java
@@ -7,9 +7,11 @@ import android.content.BroadcastReceiver.PendingResult;
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;
@@ -66,6 +68,18 @@ public abstract class ResultReturner {
}
}
+ 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 {
@@ -102,6 +116,7 @@ public abstract class ResultReturner {
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));
@@ -117,9 +132,20 @@ public abstract class ResultReturner {
} 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);