Skip to content

Commit f886dc8

Browse files
committed
Added USB redirection support to aSPICE.
1 parent fbfa599 commit f886dc8

File tree

4 files changed

+153
-13
lines changed

4 files changed

+153
-13
lines changed

eclipse_projects/bVNC/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="com.iiordanov.bVNC" android:installLocation="auto"
4-
android:versionCode="3720" android:versionName="v3.7.2">
4+
android:versionCode="3730" android:versionName="v3.7.3">
55

66
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10"></uses-sdk>
77
<uses-permission android:name="android.permission.INTERNET"></uses-permission>

eclipse_projects/bVNC/jni/src/android/android-service.c

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "android-spice-widget.h"
3030
#include "android-spicy.h"
3131
#include "virt-viewer-file.h"
32+
#include "libusb.h"
3233

3334
static gboolean disconnect(gpointer user_data);
3435

@@ -151,7 +152,7 @@ spice_session_setup_from_vv(VirtViewerFile *file, SpiceSession *session)
151152

152153
void spice_session_setup(SpiceSession *session, const char *host, const char *port,
153154
const char *tls_port, const char *password, const char *ca_file,
154-
GByteArray *ca_cert, const char *cert_subj) {
155+
GByteArray *ca_cert, const char *cert_subj, const char *proxy) {
155156

156157
g_return_if_fail(SPICE_IS_SESSION(session));
157158

@@ -170,6 +171,8 @@ void spice_session_setup(SpiceSession *session, const char *host, const char *po
170171
g_object_set(session, "ca", ca_cert, NULL);
171172
if (cert_subj)
172173
g_object_set(session, "cert-subject", cert_subj, NULL);
174+
if (proxy)
175+
g_object_set(session, "proxy", proxy, NULL);
173176
}
174177

175178
static void signal_handler(int signal, siginfo_t *info, void *reserved) {
@@ -214,7 +217,7 @@ gboolean getJvmAndMethodReferences (JNIEnv *env) {
214217

215218
JNIEXPORT jint JNICALL
216219
Java_com_iiordanov_aSPICE_SpiceCommunicator_SpiceClientConnect (JNIEnv *env, jobject obj, jstring h, jstring p,
217-
jstring tp, jstring pw, jstring cf, jstring cs, jboolean sound)
220+
jstring tp, jstring pw, jstring cf, jstring cs, jboolean sound)
218221
{
219222
const gchar *host = NULL;
220223
const gchar *port = NULL;
@@ -235,7 +238,7 @@ Java_com_iiordanov_aSPICE_SpiceCommunicator_SpiceClientConnect (JNIEnv *env, job
235238
ca_file = (*env)->GetStringUTFChars(env, cf, NULL);
236239
cert_subj = (*env)->GetStringUTFChars(env, cs, NULL);
237240

238-
result = spiceClientConnect (host, port, tls_port, password, ca_file, NULL, cert_subj, sound);
241+
result = spiceClientConnect (host, port, tls_port, password, ca_file, NULL, cert_subj, sound, NULL);
239242

240243
jvm = NULL;
241244
jni_connector_class = NULL;
@@ -246,14 +249,14 @@ Java_com_iiordanov_aSPICE_SpiceCommunicator_SpiceClientConnect (JNIEnv *env, job
246249

247250

248251
int spiceClientConnect (const gchar *h, const gchar *p, const gchar *tp,
249-
const gchar *pw, const gchar *cf, GByteArray *cc,
250-
const gchar *cs, const gboolean sound)
252+
const gchar *pw, const gchar *cf, GByteArray *cc,
253+
const gchar *cs, const gboolean sound, const gchar *proxy)
251254
{
252255
spice_connection *conn;
253256

254257
soundEnabled = sound;
255258
conn = connection_new();
256-
spice_session_setup(conn->session, h, p, tp, pw, cf, cc, cs);
259+
spice_session_setup(conn->session, h, p, tp, pw, cf, cc, cs, proxy);
257260
return connectSession(conn);
258261
}
259262

@@ -477,6 +480,7 @@ int CreateOvirtSession(JNIEnv *env, jobject obj, const gchar *uri, const gchar *
477480
gchar *ghost = NULL;
478481
gchar *ticket = NULL;
479482
gchar *spice_host_subject = NULL;
483+
gchar *proxyuri = NULL;
480484

481485
if (!getJvmAndMethodReferences (env)) {
482486
success = -1;
@@ -597,6 +601,7 @@ int CreateOvirtSession(JNIEnv *env, jobject obj, const gchar *uri, const gchar *
597601
"secure-port", &secure_port,
598602
"ticket", &ticket,
599603
"host-subject", &spice_host_subject,
604+
"proxy", &proxyuri,
600605
NULL);
601606

602607
gport = g_strdup_printf("%d", port);
@@ -622,7 +627,7 @@ int CreateOvirtSession(JNIEnv *env, jobject obj, const gchar *uri, const gchar *
622627
g_object_get(G_OBJECT(proxy), "ca-cert", &ca_cert, NULL);
623628

624629
// We are ready to start the SPICE connection.
625-
success = spiceClientConnect (ghost, gport, gtlsport, ticket, NULL, ca_cert, spice_host_subject, sound);
630+
success = spiceClientConnect (ghost, gport, gtlsport, ticket, NULL, ca_cert, spice_host_subject, sound, proxyuri);
626631
}
627632

628633
error:
@@ -757,3 +762,30 @@ Java_com_iiordanov_aSPICE_SpiceCommunicator_FetchVmNames(JNIEnv *env,
757762
error:
758763
return success;
759764
}
765+
766+
int openUsbDevice (int vid, int pid) {
767+
JNIEnv* env = NULL;
768+
gboolean attached = FALSE;
769+
attached = attachThreadToJvm (&env);
770+
771+
jclass class = (*env)->FindClass (env, "com/iiordanov/aSPICE/SpiceCommunicator");
772+
jmethodID openUsbDevice = (*env)->GetStaticMethodID (env, class, "openUsbDevice", "(II)I");
773+
int fd = (*env)->CallStaticIntMethod(env, class, openUsbDevice, vid, pid);
774+
775+
if (attached) {
776+
detachThreadFromJvm ();
777+
}
778+
return fd;
779+
}
780+
781+
int get_usb_device_fd(libusb_device *device) {
782+
__android_log_write(6, "channel-usbredir", "Trying to open USB device.");
783+
struct libusb_device_descriptor desc;
784+
int errcode = libusb_get_device_descriptor(device, &desc);
785+
if (errcode < 0) {
786+
return -1;
787+
}
788+
int vid = desc.idVendor;
789+
int pid = desc.idProduct;
790+
return openUsbDevice(vid, pid);
791+
}

eclipse_projects/bVNC/src/com/iiordanov/bVNC/Constants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ public class Constants {
177177
public static final String generalSettingsTag = "generalSettings";
178178
public static final String masterPasswordEnabledTag = "masterPasswordEnabled";
179179

180+
public static final String ACTION_USB_PERMISSION = "com.iiordanov.aSPICE.USB_PERMISSION";
181+
public static final int usbDeviceTimeout = 5000;
182+
public static final int usbDevicePermissionTimeout = 15000;
183+
180184
/**
181185
* Returns a string matching a session selection index
182186
* @param index - index to convert

eclipse_projects/bVNC/src/com/iiordanov/bVNC/SpiceCommunicator.java

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,51 @@
11
package com.iiordanov.bVNC;
22

3+
import java.util.Collection;
4+
import java.util.HashMap;
5+
import java.util.Iterator;
6+
7+
import android.app.PendingIntent;
8+
import android.content.BroadcastReceiver;
39
import android.content.Context;
10+
import android.content.Intent;
11+
import android.content.IntentFilter;
412
import android.graphics.Bitmap;
13+
import android.hardware.usb.UsbDevice;
14+
import android.hardware.usb.UsbDeviceConnection;
15+
import android.hardware.usb.UsbManager;
516
import android.os.Handler;
17+
import android.os.SystemClock;
18+
import android.util.Log;
619

720
import com.freerdp.freerdpcore.services.LibFreeRDP.UIEventListener;
821
import com.iiordanov.bVNC.input.RdpKeyboardMapper;
922
import com.iiordanov.bVNC.input.RemoteKeyboard;
1023
import com.iiordanov.bVNC.input.RemoteSpicePointer;
24+
import com.iiordanov.bVNC.Constants;
1125
import com.gstreamer.*;
1226

1327
public class SpiceCommunicator implements RfbConnectable, RdpKeyboardMapper.KeyProcessingListener {
1428
private final static String TAG = "SpiceCommunicator";
15-
29+
30+
private HashMap<String, Integer> deviceToFdMap = new HashMap<String, Integer>();
31+
UsbManager mUsbManager = null;
32+
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
33+
public void onReceive(Context context, Intent intent) {
34+
String action = intent.getAction();
35+
if (Constants.ACTION_USB_PERMISSION.equals(action)) {
36+
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
37+
if (device != null) {
38+
int vid = device.getVendorId();
39+
int pid = device.getProductId();
40+
String mapKey = Integer.toString(vid)+":"+Integer.toString(pid);
41+
synchronized (deviceToFdMap.get(mapKey)) {
42+
deviceToFdMap.get(mapKey).notify();
43+
}
44+
}
45+
}
46+
}
47+
};
48+
1649
public native int SpiceClientConnect (String ip, String port, String tport, String password, String ca_file, String cert_subj, boolean sound);
1750
public native void SpiceClientDisconnect ();
1851
public native void SpiceButtonEvent (int x, int y, int metaState, int pointerMask);
@@ -41,9 +74,16 @@ public class SpiceCommunicator implements RfbConnectable, RdpKeyboardMapper.KeyP
4174

4275
boolean isInNormalProtocol = false;
4376

44-
private SpiceThread spicehread = null;
77+
private SpiceThread spicethread = null;
78+
private static SpiceCommunicator myself = null;
79+
private Context context;
80+
private boolean usbEnabled = true;
4581

4682
public SpiceCommunicator (Context context, RemoteCanvas canvas, ConnectionBean connection) {
83+
myself = this;
84+
this.context = context;
85+
mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
86+
4787
if (connection.getEnableSound()) {
4888
try {
4989
GStreamer.init(context);
@@ -71,13 +111,13 @@ public Handler getHandler() {
71111

72112
public void connect(String ip, String port, String tport, String password, String cf, String cs, boolean sound) {
73113
//android.util.Log.e(TAG, ip + ", " + port + ", " + tport + ", " + password + ", " + cf + ", " + cs);
74-
spicehread = new SpiceThread(ip, port, tport, password, cf, cs, sound);
75-
spicehread.start();
114+
spicethread = new SpiceThread(ip, port, tport, password, cf, cs, sound);
115+
spicethread.start();
76116
}
77117

78118
public void disconnect() {
79119
SpiceClientDisconnect();
80-
try {spicehread.join(3000);} catch (InterruptedException e) {}
120+
try {spicethread.join(3000);} catch (InterruptedException e) {}
81121
}
82122

83123
class SpiceThread extends Thread {
@@ -116,6 +156,70 @@ public void sendKeyEvent (boolean keyDown, int virtualKeyCode) {
116156

117157

118158
/* Callbacks from jni */
159+
160+
public static int openUsbDevice(int vid, int pid) throws InterruptedException {
161+
Log.i(TAG, "Attempting to open a USB device and return a file descriptor.");
162+
163+
if (Utils.isFree(myself.context) || !myself.usbEnabled || android.os.Build.VERSION.SDK_INT < 12) {
164+
return -1;
165+
}
166+
167+
String mapKey = Integer.toString(vid)+":"+Integer.toString(pid);
168+
myself.deviceToFdMap.put(mapKey, 0);
169+
170+
boolean deviceFound = false;
171+
UsbDevice device = null;
172+
HashMap<String, UsbDevice> stringDeviceMap = null;
173+
int timeout = Constants.usbDeviceTimeout;
174+
while (!deviceFound && timeout > 0) {
175+
stringDeviceMap = myself.mUsbManager.getDeviceList();
176+
Collection<UsbDevice> usbDevices = stringDeviceMap.values();
177+
178+
Iterator<UsbDevice> usbDeviceIter = usbDevices.iterator();
179+
while (usbDeviceIter.hasNext()) {
180+
UsbDevice ud = usbDeviceIter.next();
181+
Log.i(TAG, "DEVICE: " + ud.toString());
182+
if (ud.getVendorId() == vid && ud.getProductId() == pid) {
183+
Log.i(TAG, "USB device successfully matched.");
184+
deviceFound = true;
185+
device = ud;
186+
break;
187+
}
188+
}
189+
timeout -= 100;
190+
SystemClock.sleep(100);
191+
}
192+
193+
int fd = -1;
194+
// If the device was located in the Java layer, we try to open it, and failing that
195+
// we request permission and wait for it to be granted or denied, or for a timeout to occur.
196+
if (device != null) {
197+
UsbDeviceConnection deviceConnection = myself.mUsbManager.openDevice(device);
198+
if (deviceConnection != null) {
199+
fd = deviceConnection.getFileDescriptor();
200+
} else {
201+
// Request permission to access the device.
202+
synchronized (myself.deviceToFdMap.get(mapKey)) {
203+
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(myself.context, 0, new Intent(Constants.ACTION_USB_PERMISSION), 0);
204+
205+
// TODO: Try putting this intent filter into the activity in the manifest file.
206+
IntentFilter filter = new IntentFilter(Constants.ACTION_USB_PERMISSION);
207+
myself.context.registerReceiver(myself.mUsbReceiver, filter);
208+
209+
myself.mUsbManager.requestPermission(device, mPermissionIntent);
210+
// Wait for permission with a timeout.
211+
myself.deviceToFdMap.get(mapKey).wait(Constants.usbDevicePermissionTimeout);
212+
213+
deviceConnection = myself.mUsbManager.openDevice(device);
214+
if (deviceConnection != null) {
215+
fd = deviceConnection.getFileDescriptor();
216+
}
217+
}
218+
}
219+
}
220+
return fd;
221+
}
222+
119223
private static void OnSettingsChanged(int inst, int width, int height, int bpp) {
120224
if (uiEventListener != null)
121225
uiEventListener.OnSettingsChanged(width, height, bpp);

0 commit comments

Comments
 (0)