这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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