package com.iiordanov.bVNC;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import com.freerdp.freerdpcore.services.LibFreeRDP.UIEventListener;
import com.iiordanov.bVNC.input.RemoteKeyboard;
import com.iiordanov.bVNC.input.RemoteSpicePointer;
import com.iiordanov.bVNC.Constants;
import org.freedesktop.gstreamer.*;
public class SpiceCommunicator implements RfbConnectable {
private final static String TAG = "SpiceCommunicator";
private HashMap<String, Integer> deviceToFdMap = new HashMap<String, Integer>();
UsbManager mUsbManager = null;
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Constants.ACTION_USB_PERMISSION.equals(action)) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
int vid = device.getVendorId();
int pid = device.getProductId();
String mapKey = Integer.toString(vid)+":"+Integer.toString(pid);
synchronized (deviceToFdMap.get(mapKey)) {
deviceToFdMap.get(mapKey).notify();
}
}
}
}
};
public native int SpiceClientConnect (String ip, String port, String tport, String password, String ca_file, String cert_subj, boolean sound);
public native void SpiceClientDisconnect ();
public native void SpiceButtonEvent (int x, int y, int metaState, int pointerMask);
public native void SpiceKeyEvent (boolean keyDown, int virtualKeyCode);
public native void UpdateBitmap (Bitmap bitmap, int x, int y, int w, int h);
public native void SpiceRequestResolution (int x, int y);
static {
System.loadLibrary("gstreamer_android");
System.loadLibrary("spice");
}
int metaState = 0;
int remoteMetaState = 0;
private int width = 0;
private int height = 0;
boolean isInNormalProtocol = false;
private SpiceThread spicethread = null;
private static SpiceCommunicator myself = null;
private Context context;
private boolean usbEnabled = true;
public SpiceCommunicator (Context context, RemoteCanvas canvas, ConnectionBean connection) {
myself = this;
this.context = context;
mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (connection.getEnableSound()) {
try {
GStreamer.init(context);
} catch (Exception e) {
e.printStackTrace();
canvas.displayShortToastMessage(e.getMessage());
}
}
}
private static UIEventListener uiEventListener = null;
private Handler handler = null;
public void setHandler(Handler handler) {
this.handler = handler;
}
public void setUIEventListener(UIEventListener ui) {
uiEventListener = ui;
}
public Handler getHandler() {
return handler;
}
public void connect(String ip, String port, String tport, String password, String cf, String cs, boolean sound) {
//android.util.Log.e(TAG, ip + ", " + port + ", " + tport + ", " + password + ", " + cf + ", " + cs);
spicethread = new SpiceThread(ip, port, tport, password, cf, cs, sound);
spicethread.start();
}
public void disconnect() {
SpiceClientDisconnect();
try {spicethread.join(3000);} catch (InterruptedException e) {}
}
class SpiceThread extends Thread {
private String ip, port, tport, password, cf, cs;
boolean sound;
public SpiceThread(String ip, String port, String tport, String password, String cf, String cs, boolean sound) {
this.ip = ip;
this.port = port;
this.tport = tport;
this.password = password;
this.cf = cf;
this.cs = cs;
this.sound = sound;
}
public void run() {
SpiceClientConnect (ip, port, tport, password, cf, cs, sound);
android.util.Log.e(TAG, "SpiceClientConnect returned.");
// If we've exited SpiceClientConnect, the connection was
// interrupted or was never established.
if (handler != null) {
handler.sendEmptyMessage(Constants.SPICE_CONNECT_FAILURE);
}
}
}
public void sendMouseEvent (int x, int y, int metaState, int pointerMask) {
SpiceButtonEvent(x, y, metaState, pointerMask);
}
public void sendKeyEvent (boolean keyDown, int scanCode) {
android.util.Log.e(TAG, "Sending scanCode: " + scanCode + ". Is it down: " + keyDown);
SpiceKeyEvent(keyDown, scanCode);
}
/* Callbacks from jni */
public static int openUsbDevice(int vid, int pid) throws InterruptedException {
Log.i(TAG, "Attempting to open a USB device and return a file descriptor.");
if (Utils.isFree(myself.context) || !myself.usbEnabled || android.os.Build.VERSION.SDK_INT < 12) {
return -1;
}
String mapKey = Integer.toString(vid)+":"+Integer.toString(pid);
myself.deviceToFdMap.put(mapKey, 0);
boolean deviceFound = false;
UsbDevice device = null;
HashMap<String, UsbDevice> stringDeviceMap = null;
int timeout = Constants.usbDeviceTimeout;
while (!deviceFound && timeout > 0) {
stringDeviceMap = myself.mUsbManager.getDeviceList();
Collection<UsbDevice> usbDevices = stringDeviceMap.values();
Iterator<UsbDevice> usbDeviceIter = usbDevices.iterator();
while (usbDeviceIter.hasNext()) {
UsbDevice ud = usbDeviceIter.next();
Log.i(TAG, "DEVICE: " + ud.toString());
if (ud.getVendorId() == vid && ud.getProductId() == pid) {
Log.i(TAG, "USB device successfully matched.");
deviceFound = true;
device = ud;
break;
}
}
timeout -= 100;
SystemClock.sleep(100);
}
int fd = -1;
// If the device was located in the Java layer, we try to open it, and failing that
// we request permission and wait for it to be granted or denied, or for a timeout to occur.
if (device != null) {
UsbDeviceConnection deviceConnection = myself.mUsbManager.openDevice(device);
if (deviceConnection != null) {
fd = deviceConnection.getFileDescriptor();
} else {
// Request permission to access the device.
synchronized (myself.deviceToFdMap.get(mapKey)) {
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(myself.context, 0, new Intent(Constants.ACTION_USB_PERMISSION), 0);
// TODO: Try putting this intent filter into the activity in the manifest file.
IntentFilter filter = new IntentFilter(Constants.ACTION_USB_PERMISSION);
myself.context.registerReceiver(myself.mUsbReceiver, filter);
myself.mUsbManager.requestPermission(device, mPermissionIntent);
// Wait for permission with a timeout.
myself.deviceToFdMap.get(mapKey).wait(Constants.usbDevicePermissionTimeout);
deviceConnection = myself.mUsbManager.openDevice(device);
if (deviceConnection != null) {
fd = deviceConnection.getFileDescriptor();
}
}
}
}
return fd;
}
private static void OnSettingsChanged(int inst, int width, int height, int bpp) {
if (uiEventListener != null)
uiEventListener.OnSettingsChanged(width, height, bpp);
}
private static boolean OnAuthenticate(int inst, StringBuilder username, StringBuilder domain, StringBuilder password) {
if (uiEventListener != null)
return uiEventListener.OnAuthenticate(username, domain, password);
return false;
}
private static void OnGraphicsUpdate(int inst, int x, int y, int width, int height) {
if (uiEventListener != null)
uiEventListener.OnGraphicsUpdate(x, y, width, height);
}
private static void OnGraphicsResize(int inst, int width, int height, int bpp) {
android.util.Log.e("Connector", "onGraphicsResize, width: " + width + " height: " + height);
if (uiEventListener != null)
uiEventListener.OnGraphicsResize(width, height, bpp);
}
@Override
public int framebufferWidth() {
return width;
}
@Override
public int framebufferHeight() {
return height;
}
public void setFramebufferWidth(int w) {
width = w;
}
public void setFramebufferHeight(int h) {
height = h;
}
@Override
public String desktopName() {
// TODO Auto-generated method stub
return "";
}
@Override
public void requestUpdate(boolean incremental) {
// TODO Auto-generated method stub
}
@Override
public void writeClientCutText(String text) {
// TODO Auto-generated method stub
}
@Override
public void setIsInNormalProtocol(boolean state) {
isInNormalProtocol = state;
}
@Override
public boolean isInNormalProtocol() {
return isInNormalProtocol;
}
@Override
public String getEncoding() {
// TODO Auto-generated method stub
return "";
}
@Override
public void writePointerEvent(int x, int y, int metaState, int pointerMask) {
this.metaState = metaState;
if ((pointerMask & RemoteSpicePointer.PTRFLAGS_DOWN) != 0)
sendModifierKeys(true, 0);
sendMouseEvent(x, y, metaState, pointerMask);
if ((pointerMask & RemoteSpicePointer.PTRFLAGS_DOWN) == 0)
sendModifierKeys(false, 0);
}
private void sendModifierKeys(boolean keyDown, int key) {
// Avoid sending meta keys down multiple times.
if ((metaState & RemoteKeyboard.CTRL_MASK) != 0) {
if (!keyDown || key != RemoteKeyboard.SCAN_LEFTCTRL && (remoteMetaState & RemoteKeyboard.CTRL_MASK) == 0) {
android.util.Log.v(TAG, "Sending LCTRL: " + RemoteKeyboard.SCAN_LEFTCTRL + " down: " + keyDown);
sendKeyEvent(keyDown, RemoteKeyboard.SCAN_LEFTCTRL);
}
}
if ((metaState & RemoteKeyboard.RCTRL_MASK) != 0) {
if (!keyDown || key != RemoteKeyboard.SCAN_RIGHTCTRL && (remoteMetaState & RemoteKeyboard.RCTRL_MASK) == 0) {
android.util.Log.v(TAG, "Sending RCTRL: " + RemoteKeyboard.SCAN_RIGHTCTRL + " down: " + keyDown);
sendKeyEvent(keyDown, RemoteKeyboard.SCAN_RIGHTCTRL);
}
}
if ((metaState & RemoteKeyboard.ALT_MASK) != 0) {
if (!keyDown || key != RemoteKeyboard.SCAN_LEFTALT && (remoteMetaState & RemoteKeyboard.ALT_MASK) == 0) {
android.util.Log.v(TAG, "Sending LALT: " + RemoteKeyboard.SCAN_LEFTALT + " down: " + keyDown);
sendKeyEvent(keyDown, RemoteKeyboard.SCAN_LEFTALT);
}
}
if ((metaState & RemoteKeyboard.RALT_MASK) != 0) {
if (!keyDown || key != RemoteKeyboard.SCAN_RIGHTALT && (remoteMetaState & RemoteKeyboard.RALT_MASK) == 0) {
android.util.Log.v(TAG, "Sending RALT: " + RemoteKeyboard.SCAN_RIGHTALT + " down: " + keyDown);
sendKeyEvent(keyDown, RemoteKeyboard.SCAN_RIGHTALT);
}
}
if ((metaState & RemoteKeyboard.SUPER_MASK) != 0) {
if (!keyDown || key != RemoteKeyboard.SCAN_LEFTSUPER && (remoteMetaState & RemoteKeyboard.SUPER_MASK) == 0) {
android.util.Log.v(TAG, "Sending SUPER: " + RemoteKeyboard.SCAN_LEFTSUPER + " down: " + keyDown);
sendKeyEvent(keyDown, RemoteKeyboard.SCAN_LEFTSUPER);
}
}
if ((metaState & RemoteKeyboard.RSUPER_MASK) != 0) {
if (!keyDown || key != RemoteKeyboard.SCAN_RIGHTSUPER && (remoteMetaState & RemoteKeyboard.RSUPER_MASK) == 0) {
android.util.Log.v(TAG, "Sending RSUPER: " + RemoteKeyboard.SCAN_RIGHTSUPER + " down: " + keyDown);
sendKeyEvent(keyDown, RemoteKeyboard.SCAN_RIGHTSUPER);
}
}
if ((metaState & RemoteKeyboard.SHIFT_MASK) != 0) {
if (!keyDown || key != RemoteKeyboard.SCAN_LEFTSHIFT && (remoteMetaState & RemoteKeyboard.SHIFT_MASK) == 0) {
android.util.Log.v(TAG, "Sending SHIFT: " + RemoteKeyboard.SCAN_LEFTSHIFT + " down: " + keyDown);
sendKeyEvent(keyDown, RemoteKeyboard.SCAN_LEFTSHIFT);
}
}
if ((metaState & RemoteKeyboard.RSHIFT_MASK) != 0) {
if (!keyDown || key != RemoteKeyboard.SCAN_RIGHTSHIFT && (remoteMetaState & RemoteKeyboard.RSHIFT_MASK) == 0) {
android.util.Log.v(TAG, "Sending RSHIFT: " + RemoteKeyboard.SCAN_RIGHTSHIFT + " down: " + keyDown);
sendKeyEvent(keyDown, RemoteKeyboard.SCAN_RIGHTSHIFT);
}
}
if (keyDown) {
remoteMetaState |= metaState;
} else {
remoteMetaState &= metaState;
}
}
@Override
public void writeKeyEvent(int key, int metaState, boolean keyDown) {
if (keyDown) {
this.metaState = metaState;
sendModifierKeys (true, key);
}
sendKeyEvent(keyDown, key);
if (!keyDown) {
sendModifierKeys (false, key);
this.metaState = metaState;
}
}
@Override
public void writeSetPixelFormat(int bitsPerPixel, int depth,
boolean bigEndian, boolean trueColour, int redMax, int greenMax,
int blueMax, int redShift, int greenShift, int blueShift,
boolean fGreyScale) {
// TODO Auto-generated method stub
}
@Override
public void writeFramebufferUpdateRequest(int x, int y, int w, int h,
boolean b) {
// TODO Auto-generated method stub
}
@Override
public void close() {
disconnect();
}
@Override
public void requestResolution(int x, int y) {
SpiceRequestResolution (x, y);
}
}