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);