updated InputStick API and added more languages (change by Jakub)

updated version code
This commit is contained in:
Philipp Crocoll 2014-09-29 21:29:52 +02:00
parent 97bcf753a4
commit 3c5c9105b7
29 changed files with 1235 additions and 249 deletions

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.inputstick.api"
android:versionCode="1"

View File

@ -10,11 +10,30 @@ package com.inputstick.api;
public final class R {
public static final class attr {
}
public static final class dimen {
/** Default screen margins, per the Android Design guidelines.
Customize dimensions originally defined in res/values/dimens.xml (such as
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
*/
public static int activity_horizontal_margin=0x7f030000;
public static int activity_vertical_margin=0x7f030001;
}
public static final class drawable {
public static int ic_launcher=0x7f020000;
}
public static final class id {
public static int action_settings=0x7f070000;
}
public static final class menu {
public static int install_utility=0x7f060000;
}
public static final class string {
public static int app_name=0x7f030000;
public static int action_settings=0x7f040002;
public static int app_name=0x7f040000;
public static int hello_world=0x7f040003;
public static int title_activity_install_utility=0x7f040001;
}
public static final class style {
/**
@ -38,10 +57,10 @@ public final class R {
API 14 theme customizations can go here.
*/
public static int AppBaseTheme=0x7f040000;
public static int AppBaseTheme=0x7f050000;
/** Application theme.
All customizations that are NOT specific to a particular API-level can go here.
*/
public static int AppTheme=0x7f040001;
public static int AppTheme=0x7f050001;
}
}

View File

@ -11,5 +11,5 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19
target=android-20
android.library=true

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">InputStickAPI</string>
<string name="title_activity_install_utility">Download InputStickUtility</string>
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
</resources>

View File

@ -10,8 +10,10 @@ public class AES {
private Cipher mCipherEncr;
private Cipher mCipherDecr;
private SecretKeySpec mKey;
private boolean ready;
public AES() {
ready = false;
}
public static byte[] getMD5(String s) {
@ -31,10 +33,10 @@ public class AES {
mCipherEncr = Cipher.getInstance("AES/CBC/NoPadding");
mCipherEncr.init(Cipher.ENCRYPT_MODE, mKey);
iv = mCipherEncr.getIV();
//System.out.println("AES IV: ");
Util.printHex(iv);
Util.printHex(iv, "AES IV: ");
mCipherDecr = Cipher.getInstance("AES/CBC/NoPadding");
mCipherDecr.init(Cipher.DECRYPT_MODE, mKey, new IvParameterSpec(iv));
mCipherDecr.init(Cipher.DECRYPT_MODE, mKey, new IvParameterSpec(iv));
ready = true;
} catch (Exception e) {
e.printStackTrace();
}
@ -49,4 +51,7 @@ public class AES {
return mCipherDecr.update(data);
}
public boolean isReady() {
return ready;
}
}

View File

@ -21,7 +21,6 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private Application mApp;
private BTService mBTService;
private PacketManager mPacketManager;
//private PacketQueue mPacketQueue;
private final BTHandler mBTHandler = new BTHandler(this);
@ -45,31 +44,12 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
break;
case BTService.EVENT_CANCELLED:
manager.onDisconnected();
break;
case BTService.EVENT_CONNECTION_FAILED:
manager.onFailure(1);
break;
case BTService.EVENT_ERROR:
manager.onFailure(msg.arg1);
break;
case BTService.EVENT_CONNECTION_LOST:
manager.onFailure(1);
break;
case BTService.EVENT_NO_BT_HW:
manager.onFailure(1);
break;
case BTService.EVENT_INVALID_MAC:
manager.onFailure(1);
break;
case BTService.EVENT_CMD_TIMEOUT:
manager.onFailure(1);
break;
case BTService.EVENT_INTERVAL_TIMEOUT:
manager.onFailure(1);
break;
case BTService.EVENT_TURN_ON_TIMEOUT:
manager.onFailure(1);
break;
case BTService.EVENT_OTHER_ERROR:
manager.onFailure(1);
break;
default:
manager.onFailure(InputStickError.ERROR_BLUETOOTH);
}
}
}
@ -80,6 +60,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private void onConnected() {
stateNotify(ConnectionManager.STATE_CONNECTED);
//mInitManager.startTimeoutCountdown(InitManager.DEFAULT_INIT_TIMEOUT);
mInitManager.onConnected();
}
@ -89,7 +70,8 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private void onFailure(int code) {
mErrorCode = code;
stateNotify(ConnectionManager.STATE_FAILURE);
stateNotify(ConnectionManager.STATE_FAILURE);
disconnect();
}
private void onData(byte[] rawData) {
@ -97,16 +79,15 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
data = mPacketManager.bytesToPacket(rawData);
if (data == null) {
//TODO
//TODO failure?
return;
}
mInitManager.onData(data);
//sendNext(); TODO
for (InputStickDataListener listener : mDataListeners) {
listener.onInputStickData(data);
}
}
}
@ -124,7 +105,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
public void connect(boolean reflection, int timeout) {
mErrorCode = ConnectionManager.ERROR_NONE;
mErrorCode = InputStickError.ERROR_NONE;
if (mBTService == null) {
mBTService = new BTService(mApp, mBTHandler);
mPacketManager = new PacketManager(mBTService, mKey);
@ -143,10 +124,14 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
}
}
public void disconnect(int failureCode) {
onFailure(failureCode);
}
@Override
public void sendPacket(Packet p) {
mPacketManager.sendPacket(p); //TODO tmp; zalozmy z beda same NO_RESP ???
mPacketManager.sendPacket(p);
}
@ -162,7 +147,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
@Override
public void onInitFailure(int code) {
onFailure(code);
onFailure(code);
}
}

View File

@ -11,13 +11,6 @@ public abstract class ConnectionManager {
public static final int STATE_READY = 4;
public static final int ERROR_NONE = 0;
public static final int ERROR_UNSUPPORTED_FIRMWARE = 10;
public static final int ERROR_PASSWORD_PROTECTED = 11;
public static final int ERROR_INVALID_KEY = 12;
protected Vector<InputStickStateListener> mStateListeners = new Vector<InputStickStateListener>();
protected Vector<InputStickDataListener> mDataListeners = new Vector<InputStickDataListener>();
@ -47,7 +40,9 @@ public abstract class ConnectionManager {
public void addStateListener(InputStickStateListener listener) {
if (listener != null) {
mStateListeners.add(listener);
if ( !mStateListeners.contains(listener)) {
mStateListeners.add(listener);
}
}
}
@ -59,7 +54,9 @@ public abstract class ConnectionManager {
public void addDataListener(InputStickDataListener listener) {
if (listener != null) {
mDataListeners.add(listener);
if ( !mDataListeners.contains(listener)) {
mDataListeners.add(listener);
}
}
}

View File

@ -15,10 +15,16 @@ public class HIDInfo {
private boolean mouseReady;
private boolean consumerReady;
// >= 0.93
private boolean sentToHostInfo;
private int keyboardReportsSentToHost;
private int mouseReportsSentToHost;
private int consumerReportsSentToHost;
public HIDInfo() {
keyboardReportProtocol = true;
mouseReportProtocol = true;
sentToHostInfo = false;
}
public void update(byte[] data) {
@ -70,6 +76,14 @@ public class HIDInfo {
} else {
consumerReady = true;
}
if (data.length >= 12) {
if (data[11] == (byte)0xFF) {
sentToHostInfo = true;
keyboardReportsSentToHost = data[8] & 0xFF;
mouseReportsSentToHost = data[9] & 0xFF;
consumerReportsSentToHost = data[10] & 0xFF;
}
}
}
public void setKeyboardBusy() {
@ -112,4 +126,24 @@ public class HIDInfo {
return consumerReady;
}
// > v0.93 firmware only
public boolean isSentToHostInfoAvailable() {
return sentToHostInfo;
}
public int getKeyboardReportsSentToHost() {
return keyboardReportsSentToHost;
}
public int getMouseReportsSentToHost() {
return mouseReportsSentToHost;
}
public int getConsumerReportsSentToHost() {
return consumerReportsSentToHost;
}
}

View File

@ -52,7 +52,6 @@ public class IPCConnectionManager extends ConnectionManager {
}
break;
case SERVICE_CMD_STATE:
//System.out.println("CMD STATE: "+msg.arg1);
manager.stateNotify(msg.arg1);
break;
}
@ -61,7 +60,6 @@ public class IPCConnectionManager extends ConnectionManager {
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
//System.out.println("onServiceConnected!");
mService = new Messenger(service);
mBound = true;
sendMessage(SERVICE_CMD_CONNECT, 0, 0);
@ -69,9 +67,10 @@ public class IPCConnectionManager extends ConnectionManager {
public void onServiceDisconnected(ComponentName className) {
// unexpectedly disconnected from service
//System.out.println("onService DISCONNECTED!");
mService = null;
mBound = false;
mErrorCode = InputStickError.ERROR_ANDROID_SERVICE_DISCONNECTED;
stateNotify(STATE_FAILURE);
stateNotify(STATE_DISCONNECTED);
}
};
@ -130,34 +129,32 @@ public class IPCConnectionManager extends ConnectionManager {
}
if (exists) {
mErrorCode = ConnectionManager.ERROR_NONE;
mErrorCode = InputStickError.ERROR_NONE;
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.inputstick.apps.inputstickutility","com.inputstick.apps.inputstickutility.service.InputStickService"));
mCtx.startService(intent);
mCtx.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
if (mBound) {
//already bound?
//System.out.println("Service already Connected");
//already bound
sendMessage(SERVICE_CMD_CONNECT, 0, 0);
}
} else {
mErrorCode = 1; //TODO
mErrorCode = InputStickError.ERROR_ANDROID_NO_UTILITY_APP;
stateNotify(STATE_FAILURE);
stateNotify(STATE_DISCONNECTED);
}
}
@Override
public void disconnect() {
if (mBound) {
//System.out.println("UNBIND");
sendMessage(SERVICE_CMD_DISCONNECT, 0, 0);
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.inputstick.apps.inputstickutility","com.inputstick.apps.inputstickutility.service.InputStickService"));
mCtx.unbindService(mConnection);
mCtx.stopService(intent);
mCtx.stopService(intent);
mBound = false;
//TODO stateNotify
//service will pass notification message
//service will pass notification message (disconnected)
} else {
//just set state, there is nothing else to do
stateNotify(STATE_DISCONNECTED);
@ -172,9 +169,8 @@ public class IPCConnectionManager extends ConnectionManager {
} else {
sendMessage(IPCConnectionManager.SERVICE_CMD_DATA, 0, 0, p.getBytes());
}
}
}
}
}

View File

@ -22,6 +22,12 @@ public class Packet {
public static final byte CMD_FW_INFO = 0x10;
public static final byte CMD_INIT = 0x11;
public static final byte CMD_INIT_AUTH = 0x12;
public static final byte CMD_INIT_CON = 0x13;
//public static final byte CMD_SET_KEY = 0x14;
public static final byte CMD_SET_VALUE = 0x14;
public static final byte CMD_RESTORE_DEFAULTS = 0x15;
public static final byte CMD_RESTORE_STATUS = 0x16;
public static final byte CMD_HID_STATUS_REPORT = 0x20;
@ -37,6 +43,7 @@ public class Packet {
public static final byte RESP_OK = 0x01;
public static final byte RESP_UNKNOWN_CMD = (byte)0xFF;
public static final byte[] RAW_OLD_BOOTLOADER = new byte[] {START_TAG, (byte)0x00, (byte)0x02, (byte)0x83, (byte)0x00, (byte)0xDA};
@ -119,4 +126,8 @@ public class Packet {
return mRespond;
}
public void print() {
Util.printHex(mData, "PACKET DATA:");
}
}

View File

@ -39,9 +39,13 @@ public class PacketManager {
}
}
public boolean isEncrypted() {
return mEncryption;
}
public Packet encPacket(boolean enable) {
Random r = new Random();
Packet p = new Packet(true, Packet.CMD_INIT);
Packet p = new Packet(true, Packet.CMD_INIT_AUTH);
if (enable) {
p.addByte((byte)1);
} else {
@ -68,13 +72,13 @@ public class PacketManager {
initData = mAes.encrypt(initData);
p.addBytes(initData);
//Util.printHex(initData, "InitData: ");
Util.printHex(initData, "InitData: ");
cmpData = new byte[16];
r.nextBytes(cmpData);
p.addBytes(cmpData);
//Util.printHex(cmpData, "CmpData: ");
Util.printHex(cmpData, "CmpData: ");
return p;
}
@ -87,11 +91,15 @@ public class PacketManager {
payload = Arrays.copyOfRange(data, 2, data.length); //remove TAG, info
if ((data[1] & Packet.FLAG_ENCRYPTED) != 0) {
Util.log("DECRYPT");
payload = mAes.decrypt(payload);
//Util.log("DECRYPT");
if (mAes.isReady()) {
payload = mAes.decrypt(payload);
} else {
return null;
}
}
Util.printHex(payload, "DATA IN: ");
//Util.printHex(payload, "DATA IN: ");
//check CRC
crcCompare = Util.getLong(payload[0], payload[1], payload[2], payload[3]);
@ -104,17 +112,19 @@ public class PacketManager {
payload = Arrays.copyOfRange(payload, 4, payload.length); //remove CRC
return payload;
} else {
return null; //TODO
return null; //TODO
}
}
public void sendRAW(byte[] data) {
mBTService.write(data);
}
}
public void sendPacket(Packet p) {
sendPacket(p, mEncryption);
if (p != null) {
sendPacket(p, mEncryption);
}
}
public void sendPacket(Packet p, boolean encrypt) {
@ -137,7 +147,7 @@ public class PacketManager {
mCrc.reset();
mCrc.update(result, CRC_OFFSET, result.length - CRC_OFFSET);
crcValue = mCrc.getValue();
Util.log("CRC: "+crcValue);
//Util.log("CRC: "+crcValue);
result[3] = (byte)crcValue;
crcValue >>= 8;
result[2] = (byte)crcValue;

View File

@ -1,13 +1,25 @@
package com.inputstick.api;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public abstract class Util {
private static final boolean debug = false;
public static boolean debug = false;
public static void log(String msg) {
log(msg, false);
}
public static void log(String msg, boolean displayTime) {
if (debug) {
System.out.println("LOG: " + msg);
System.out.print("LOG: " + msg);
if (displayTime) {
System.out.print(" @ " + System.currentTimeMillis());
}
System.out.println();
}
}
@ -21,29 +33,35 @@ public abstract class Util {
public static void printHex(byte[] toPrint) {
if (debug) {
int cnt = 0;
String s;
byte b;
for (int i = 0; i < toPrint.length; i++) {
b = toPrint[i];
if ((b < 10) && (b >= 0)) {
s = Integer.toHexString((int)b);
s = "0" + s;
} else {
s = Integer.toHexString((int)b);
if (s.length() > 2) {
s = s.substring(s.length() - 2);
if (toPrint != null) {
int cnt = 0;
String s;
byte b;
for (int i = 0; i < toPrint.length; i++) {
b = toPrint[i];
//0x0..0xF = 0x00..0x0F
if ((b < 0x10) && (b >= 0)) {
s = Integer.toHexString((int)b);
s = "0" + s;
} else {
s = Integer.toHexString((int)b);
if (s.length() > 2) {
s = s.substring(s.length() - 2);
}
}
s = s.toUpperCase();
System.out.print("0x" + s + " ");
cnt++;
if (cnt == 8) {
System.out.println("");
cnt = 0;
}
}
s = s.toUpperCase();
System.out.print("0x" + s + " ");
cnt++;
if (cnt == 8) {
System.out.println("");
cnt = 0;
}
}
System.out.println("\n#####");
}
} else {
System.out.println("null");
}
System.out.println("\n#####");
}
}
@ -79,5 +97,18 @@ public abstract class Util {
return result;
}
public static byte[] getPasswordBytes(String plainText) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(plainText.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -1,35 +1,54 @@
package com.inputstick.api.basic;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import android.app.AlertDialog;
import android.app.Application;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import com.inputstick.api.BTConnectionManager;
import com.inputstick.api.ConnectionManager;
import com.inputstick.api.HIDInfo;
import com.inputstick.api.IPCConnectionManager;
import com.inputstick.api.InputStickDataListener;
import com.inputstick.api.InputStickError;
import com.inputstick.api.InputStickStateListener;
import com.inputstick.api.OnEmptyBufferListener;
import com.inputstick.api.Packet;
import com.inputstick.api.Util;
import com.inputstick.api.hid.HIDTransaction;
import com.inputstick.api.hid.HIDTransactionQueue;
import com.inputstick.init.InitManager;
public class InputStickHID implements InputStickStateListener, InputStickDataListener {
//private static final String mTag = "InputStickBasic";
public static final int INTERFACE_KEYBOARD = 0;
public static final int INTERFACE_CONSUMER = 1;
public static final int INTERFACE_MOUSE = 2;
//private static final String mTag = "InputStickBasic";
private static ConnectionManager mConnectionManager;
private static Vector<InputStickStateListener> mStateListeners = new Vector<InputStickStateListener>();
private static InputStickHID instance = new InputStickHID();
private static HIDInfo mHIDInfo = new HIDInfo();
private static HIDInfo mHIDInfo;
private static HIDTransactionQueue keyboardQueue;
private static HIDTransactionQueue mouseQueue;
private static HIDTransactionQueue consumerQueue;
// >= FW 0.93
private static Timer t1;
private static boolean constantUpdateMode;
private InputStickHID() {
}
@ -38,9 +57,11 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
}
private static void init() {
keyboardQueue = new HIDTransactionQueue(HIDTransactionQueue.KEYBOARD, mConnectionManager);
mouseQueue = new HIDTransactionQueue(HIDTransactionQueue.MOUSE, mConnectionManager);
consumerQueue = new HIDTransactionQueue(HIDTransactionQueue.CONSUMER, mConnectionManager);
mHIDInfo = new HIDInfo();
constantUpdateMode = false;
keyboardQueue = new HIDTransactionQueue(INTERFACE_KEYBOARD, mConnectionManager);
mouseQueue = new HIDTransactionQueue(INTERFACE_MOUSE, mConnectionManager);
consumerQueue = new HIDTransactionQueue(INTERFACE_CONSUMER, mConnectionManager);
mConnectionManager.addStateListener(instance);
mConnectionManager.addDataListener(instance);
@ -55,8 +76,6 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
//direct Bluetooth connection
public static void connect(Application app, String mac, byte[] key) {
//mConnectionManager = new BTConnectionManager(new BasicInitManager(key), app, mac, reflections, key);
//mConnectionManager = new BTConnectionManager(new BasicInitManager(key), app, mac, key);
mConnectionManager = new BTConnectionManager(new InitManager(key), app, mac, key);
init();
}
@ -80,6 +99,16 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
}
}
public static int getErrorCode() {
if (mConnectionManager != null) {
return mConnectionManager.getErrorCode();
} else {
return InputStickError.ERROR_UNKNOWN;
}
}
public static boolean isReady() {
if (getState() == ConnectionManager.STATE_READY) {
return true;
@ -90,7 +119,9 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
public static void addStateListener(InputStickStateListener listener) {
if (listener != null) {
mStateListeners.add(listener);
if ( !mStateListeners.contains(listener)) {
mStateListeners.add(listener);
}
}
}
@ -99,6 +130,22 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
mStateListeners.remove(listener);
}
}
public static void addBufferEmptyListener(OnEmptyBufferListener listener) {
if (listener != null) {
keyboardQueue.addBufferEmptyListener(listener);
mouseQueue.addBufferEmptyListener(listener);
consumerQueue.addBufferEmptyListener(listener);
}
}
public static void removeBufferEmptyListener(OnEmptyBufferListener listener) {
if (listener != null) {
keyboardQueue.removeBufferEmptyListener(listener);
mouseQueue.removeBufferEmptyListener(listener);
consumerQueue.removeBufferEmptyListener(listener);
}
}
public static void addKeyboardTransaction(HIDTransaction transaction) {
keyboardQueue.addTransaction(transaction);
@ -112,6 +159,18 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
consumerQueue.addTransaction(transaction);
}
public static void clearKeyboardBuffer() {
keyboardQueue.clearBuffer();
}
public static void clearMouseBuffer() {
mouseQueue.clearBuffer();
}
public static void clearConsumerBuffer() {
consumerQueue.clearBuffer();
}
public static boolean sendPacket(Packet p) {
if (mConnectionManager != null) {
mConnectionManager.sendPacket(p);
@ -122,31 +181,112 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
}
@Override
public void onStateChanged(int state) {
public void onStateChanged(int state) {
if ((state == ConnectionManager.STATE_DISCONNECTED) && (t1 != null)) {
t1.cancel();
t1 = null;
}
for (InputStickStateListener listener : mStateListeners) {
listener.onStateChanged(state);
}
}
public static boolean isKeyboardLocalBufferEmpty() {
return keyboardQueue.isLocalBufferEmpty();
}
public static boolean isMouseLocalBufferEmpty() {
return mouseQueue.isLocalBufferEmpty();
}
public static boolean isConsumerLocalBufferEmpty() {
return consumerQueue.isLocalBufferEmpty();
}
public static boolean isKeyboardRemoteBufferEmpty() {
return keyboardQueue.isRemoteBufferEmpty();
}
public static boolean isMouseRemoteBufferEmpty() {
return mouseQueue.isRemoteBufferEmpty();
}
public static boolean isConsumerRemoteBufferEmpty() {
return consumerQueue.isRemoteBufferEmpty();
}
@Override
public void onInputStickData(byte[] data) {
if (data[0] == Packet.CMD_HID_STATUS) {
mHIDInfo.update(data);
if (mHIDInfo.isKeyboardReady()) {
keyboardQueue.deviceReady();
if (mHIDInfo.isSentToHostInfoAvailable()) {
// >= FW 0.93
keyboardQueue.deviceReady(mHIDInfo, mHIDInfo.getKeyboardReportsSentToHost());
mouseQueue.deviceReady(mHIDInfo, mHIDInfo.getMouseReportsSentToHost());
consumerQueue.deviceReady(mHIDInfo, mHIDInfo.getConsumerReportsSentToHost());
if ( !constantUpdateMode) {
Util.log("Constatnt update mode enabled");
constantUpdateMode = true;
t1 = new Timer();
t1.schedule(new TimerTask() {
@Override
public void run() {
keyboardQueue.sendToBuffer(false);
mouseQueue.sendToBuffer(false);
consumerQueue.sendToBuffer(false);
}
}, 5,5);
}
} else {
//previous FW versions
if (mHIDInfo.isKeyboardReady()) {
keyboardQueue.deviceReady(null, 0);
}
if (mHIDInfo.isMouseReady()) {
mouseQueue.deviceReady(null, 0);
}
if (mHIDInfo.isConsumerReady()) {
consumerQueue.deviceReady(null, 0);
}
}
if (mHIDInfo.isMouseReady()) {
mouseQueue.deviceReady();
}
if (mHIDInfo.isConsumerReady()) {
consumerQueue.deviceReady();
}
InputStickKeyboard.setLEDs(mHIDInfo.getNumLock(), mHIDInfo.getCapsLock(), mHIDInfo.getScrollLock());
}
}
public static AlertDialog getDownloadDialog(final Context ctx) {
if (mConnectionManager.getErrorCode() == InputStickError.ERROR_ANDROID_NO_UTILITY_APP) {
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(ctx);
downloadDialog.setTitle("No InputStickUtility app installed");
downloadDialog.setMessage("InputStickUtility is required to run this application. Download now?");
downloadDialog.setPositiveButton("Yes",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
final String appPackageName = "com.inputstick.apps.inputstickutility";
try {
ctx.startActivity(new Intent(
Intent.ACTION_VIEW, Uri
.parse("market://details?id="
+ appPackageName)));
} catch (android.content.ActivityNotFoundException anfe) {
ctx.startActivity(new Intent(
Intent.ACTION_VIEW,
Uri.parse("http://play.google.com/store/apps/details?id="
+ appPackageName)));
}
}
});
downloadDialog.setNegativeButton("No",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
return downloadDialog.show();
} else {
return null;
}
}
}

View File

@ -2,6 +2,8 @@ package com.inputstick.api.basic;
import java.util.Vector;
import android.util.SparseArray;
import com.inputstick.api.InputStickKeyboardListener;
import com.inputstick.api.hid.HIDKeycodes;
import com.inputstick.api.hid.HIDTransaction;
@ -13,6 +15,10 @@ public class InputStickKeyboard {
private static final byte NONE = (byte)0;
private static final byte LED_NUM_LOCK = 1;
private static final byte LED_CAPS_LOCK = 2;
private static final byte LED_SCROLL_LOCK = 4;
private static boolean mReportProtocol;
private static boolean mNumLock;
private static boolean mCapsLock;
@ -20,12 +26,23 @@ public class InputStickKeyboard {
private static Vector<InputStickKeyboardListener> mKeyboardListeners = new Vector<InputStickKeyboardListener>();
private static final SparseArray<String> ledsMap;
static
{
ledsMap = new SparseArray<String>();
ledsMap.put(LED_NUM_LOCK, "NumLock");
ledsMap.put(LED_CAPS_LOCK, "CapsLock");
ledsMap.put(LED_SCROLL_LOCK, "ScrollLock");
}
private InputStickKeyboard() {
}
public static void addKeyboardListener(InputStickKeyboardListener listener) {
if (listener != null) {
mKeyboardListeners.add(listener);
if ( !mKeyboardListeners.contains(listener)) {
mKeyboardListeners.add(listener);
}
}
}
@ -114,5 +131,25 @@ public class InputStickKeyboard {
t.addReport(report);
InputStickHID.addKeyboardTransaction(t);
}*/
public static String ledsToString(byte leds) {
String result = "None";
boolean first = true;
byte mod;
for (int i = 0; i < 8; i++) {
mod = (byte)(LED_NUM_LOCK << i);
if ((leds & mod) != 0) {
if ( !first) {
result += ", ";
} else {
result = "";
}
first = false;
result += ledsMap.get(mod);
}
}
return result;
}
}

View File

@ -1,5 +1,7 @@
package com.inputstick.api.basic;
import android.util.SparseArray;
import com.inputstick.api.hid.HIDTransaction;
import com.inputstick.api.hid.MouseReport;
@ -12,6 +14,15 @@ public class InputStickMouse {
public static final byte BUTTON_RIGHT = 0x02;
public static final byte BUTTON_MIDDLE = 0x04;
private static final SparseArray<String> buttonsMap;
static
{
buttonsMap = new SparseArray<String>();
buttonsMap.put(BUTTON_LEFT, "Left");
buttonsMap.put(BUTTON_RIGHT, "Right");
buttonsMap.put(BUTTON_MIDDLE, "Middle");
}
private static boolean mReportProtocol;
private InputStickMouse() {
@ -53,5 +64,25 @@ public class InputStickMouse {
t.addReport(new MouseReport(buttons, x, y, wheel));
InputStickHID.addMouseTransaction(t);
}
public static String buttonsToString(byte buttons) {
String result = "None";
boolean first = true;
byte mod;
for (int i = 0; i < 8; i++) {
mod = (byte)(BUTTON_LEFT << i);
if ((buttons & mod) != 0) {
if ( !first) {
result += ", ";
} else {
result = "";
}
first = false;
result += buttonsMap.get(mod);
}
}
return result;
}
}

View File

@ -19,6 +19,7 @@ import android.os.Message;
import com.inputstick.api.Packet;
import com.inputstick.api.Util;
import com.inputstick.api.InputStickError;
public class BTService {
@ -28,16 +29,7 @@ public class BTService {
public static final int EVENT_DATA = 1;
public static final int EVENT_CONNECTED = 2;
public static final int EVENT_CANCELLED = 3;
public static final int EVENT_CONNECTION_FAILED = 4;
public static final int EVENT_CONNECTION_LOST = 5;
public static final int EVENT_NO_BT_HW = 6;
public static final int EVENT_INVALID_MAC = 7;
//TODO:
public static final int EVENT_CMD_TIMEOUT = 8;
public static final int EVENT_INTERVAL_TIMEOUT = 9;
public static final int EVENT_TURN_ON_TIMEOUT = 10;
public static final int EVENT_OTHER_ERROR = 11;
public static final int EVENT_ERROR = 4;
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //SPP
@ -71,8 +63,7 @@ public class BTService {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
System.out.println("ACTION: "+action);
final String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if ((state == BluetoothAdapter.STATE_ON) && (turnBluetoothOn)) {
@ -102,12 +93,12 @@ public class BTService {
public void enableReflection(boolean enabled) {
mUseReflection = enabled;
}
private synchronized void event(int event) {
private synchronized void event(int event, int arg1) {
Util.log("event() " + mLastEvent + " -> " + event);
mLastEvent = event;
Message msg = Message.obtain(null, mLastEvent, 0, 0);
mLastEvent = event;
Message msg = Message.obtain(null, mLastEvent, arg1, 0);
mHandler.sendMessage(msg);
}
@ -163,7 +154,7 @@ public class BTService {
if (BluetoothAdapter.checkBluetoothAddress(mac)) {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
event(EVENT_NO_BT_HW);
event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_NOT_SUPPORTED);
} else {
if (mBluetoothAdapter.isEnabled()) {
doConnect(false);
@ -172,7 +163,7 @@ public class BTService {
}
}
} else {
event(EVENT_INVALID_MAC);
event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_INVALID_MAC);
}
}
@ -180,7 +171,7 @@ public class BTService {
Util.log("disconnect");
disconnecting = true;
cancelThreads();
event(EVENT_CANCELLED);
event(EVENT_CANCELLED, 0);
}
@ -226,7 +217,7 @@ public class BTService {
mConnectedThread.start();
connected = true;
event(EVENT_CONNECTED);
event(EVENT_CONNECTED, 0);
}
@ -239,7 +230,7 @@ public class BTService {
Util.log("RETRY: "+retryCnt + " time left: " + (timeout - System.currentTimeMillis()));
doConnect(true);
} else {
event(EVENT_CONNECTION_FAILED);
event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_CONNECTION_FAILED);
}
}
}
@ -249,7 +240,7 @@ public class BTService {
if (disconnecting) {
disconnecting = false;
} else {
event(EVENT_CONNECTION_LOST);
event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_CONNECTION_LOST);
}
}
@ -356,11 +347,13 @@ public class BTService {
byte[] buffer = null;
int rxTmp;
int lengthByte;
int length;
int length;
int wdgCnt = 0;
while (true) {
try {
rxTmp = mmInStream.read();
if (rxTmp == Packet.START_TAG) {
wdgCnt = 0;
lengthByte = mmInStream.read();
length = lengthByte;
length &= 0x3F;
@ -373,8 +366,13 @@ public class BTService {
}
mHandler.obtainMessage(EVENT_DATA, 0, 0, buffer).sendToTarget();
} else {
System.out.println("RX: " + rxTmp);
//possible WDG reset
Util.log("Unexpected RX byte" + rxTmp);
if (rxTmp == 0xAF) {
wdgCnt++;
}
if (wdgCnt > 1024) {
//TODO
}
}
} catch (IOException e) {
connectionLost();
@ -388,7 +386,7 @@ public class BTService {
mmOutStream.write(buffer);
mmOutStream.flush();
} catch (IOException e) {
Util.log("Exception during write");
Util.log("write() exception");
}
}
@ -396,7 +394,7 @@ public class BTService {
try {
mmSocket.close();
} catch (IOException e) {
Util.log("close() of connect socket failed");
Util.log("socket close() exception");
}
}
}

View File

@ -1,5 +1,7 @@
package com.inputstick.api.hid;
import android.util.SparseArray;
public class HIDKeycodes {
public static final byte NONE = 0x00;
@ -13,6 +15,7 @@ public class HIDKeycodes {
public static final byte ALT_RIGHT = 0x40;
public static final byte GUI_RIGHT = (byte)0x80;
public static final byte KEY_ENTER = 0x28;
public static final byte KEY_ESCAPE = 0x29;
public static final byte KEY_BACKSPACE = 0x2A;
@ -79,6 +82,8 @@ public class HIDKeycodes {
public static final byte KEY_NUM_0 = 0x62;
public static final byte KEY_NUM_DOT = 0x63;
public static final byte KEY_BACKSLASH_NON_US = 0x64;
public static final byte KEY_A = 0x04;
public static final byte KEY_B = 0x05;
public static final byte KEY_C = 0x06;
@ -126,6 +131,133 @@ public class HIDKeycodes {
public static final SparseArray<String> modifiersMap;
static
{
modifiersMap = new SparseArray<String>();
modifiersMap.put(CTRL_LEFT, "Left Ctrl");
modifiersMap.put(SHIFT_LEFT, "Left Shift");
modifiersMap.put(ALT_LEFT, "Left Alt");
modifiersMap.put(GUI_LEFT, "Left GUI");
modifiersMap.put(CTRL_RIGHT, "Right Ctrl");
modifiersMap.put(SHIFT_RIGHT, "Right Shift");
modifiersMap.put(ALT_RIGHT, "Right Alt");
modifiersMap.put(GUI_RIGHT, "Right GUI");
}
public static final SparseArray<String> keyMap;
static
{
keyMap = new SparseArray<String>();
keyMap.put(0, "None");
keyMap.put(KEY_ENTER, "Enter");
keyMap.put(KEY_ESCAPE , "Esc");
keyMap.put(KEY_BACKSPACE , "Backspace");
keyMap.put(KEY_TAB , "Tab");
keyMap.put(KEY_SPACEBAR , "Space");
keyMap.put(KEY_CAPS_LOCK , "CapsLock");
keyMap.put(KEY_1 , "1");
keyMap.put(KEY_2 , "2");
keyMap.put(KEY_3 , "3");
keyMap.put(KEY_4 , "4");
keyMap.put(KEY_5 , "5");
keyMap.put(KEY_6 , "6");
keyMap.put(KEY_7 , "7");
keyMap.put(KEY_8 , "8");
keyMap.put(KEY_9 , "9");
keyMap.put(KEY_0 , "0");
keyMap.put(KEY_F1 , "F1");
keyMap.put(KEY_F2 , "F2");
keyMap.put(KEY_F3 , "F3");
keyMap.put(KEY_F4 , "F4");
keyMap.put(KEY_F5 , "F5");
keyMap.put(KEY_F6 , "F6");
keyMap.put(KEY_F7 , "F7");
keyMap.put(KEY_F8 , "F8");
keyMap.put(KEY_F9 , "F9");
keyMap.put(KEY_F10 , "F10");
keyMap.put(KEY_F11 , "F11");
keyMap.put(KEY_F12 , "F12");
keyMap.put(KEY_PRINT_SCREEN , "Print Scrn");
keyMap.put(KEY_SCROLL_LOCK , "ScrollLock");
keyMap.put(KEY_PASUE , "Pause Break");
keyMap.put(KEY_INSERT , "Insert");
keyMap.put(KEY_HOME , "Home");
keyMap.put(KEY_PAGE_UP , "PageUp");
keyMap.put(KEY_DELETE , "Delete");
keyMap.put(KEY_END , "End");
keyMap.put(KEY_PAGE_DOWN , "PageDown");
keyMap.put(KEY_ARROW_RIGHT , "Right Arrow");
keyMap.put(KEY_ARROW_LEFT , "Left Arrow");
keyMap.put(KEY_ARROW_DOWN , "Down Arrow");
keyMap.put(KEY_ARROW_UP , "Up Arrow");
keyMap.put(KEY_NUM_LOCK , "NumLock");
keyMap.put(KEY_NUM_BACKSLASH , "Num /");
keyMap.put(KEY_NUM_STAR , "Num *");
keyMap.put(KEY_NUM_MINUS , "Num -");
keyMap.put(KEY_NUM_PLUS , "Num +");
keyMap.put(KEY_NUM_ENTER , "Num Enter");
keyMap.put(KEY_NUM_1 , "Num 1");
keyMap.put(KEY_NUM_2 , "Num 2");
keyMap.put(KEY_NUM_3 , "Num 3");
keyMap.put(KEY_NUM_4 , "Num 4");
keyMap.put(KEY_NUM_5 , "Num 5");
keyMap.put(KEY_NUM_6 , "Num 6");
keyMap.put(KEY_NUM_7 , "Num 7");
keyMap.put(KEY_NUM_8 , "Num 8");
keyMap.put(KEY_NUM_9 , "Num 9");
keyMap.put(KEY_NUM_0 , "Num 0");
keyMap.put(KEY_NUM_DOT , "Num .");
keyMap.put(KEY_A , "A");
keyMap.put(KEY_B , "B");
keyMap.put(KEY_C , "C");
keyMap.put(KEY_D , "D");
keyMap.put(KEY_E , "E");
keyMap.put(KEY_F , "F");
keyMap.put(KEY_G , "G");
keyMap.put(KEY_H , "H");
keyMap.put(KEY_I , "I");
keyMap.put(KEY_J , "J");
keyMap.put(KEY_K , "K");
keyMap.put(KEY_L , "L");
keyMap.put(KEY_M , "M");
keyMap.put(KEY_N , "N");
keyMap.put(KEY_O , "O");
keyMap.put(KEY_P , "P");
keyMap.put(KEY_Q , "Q");
keyMap.put(KEY_R , "R");
keyMap.put(KEY_S , "S");
keyMap.put(KEY_T , "T");
keyMap.put(KEY_U , "U");
keyMap.put(KEY_V , "V");
keyMap.put(KEY_W , "W");
keyMap.put(KEY_X , "X");
keyMap.put(KEY_Y , "Y");
keyMap.put(KEY_Z , "Z");
keyMap.put(KEY_MINUS , "-");
keyMap.put(KEY_EQUALS , "=");
keyMap.put(KEY_LEFT_BRACKET , "[");
keyMap.put(KEY_RIGHT_BRACKET , "]");
keyMap.put(KEY_BACKSLASH , "\\");
//keyMap.put(KEY_GRAVE , "`");
keyMap.put(KEY_SEMICOLON , ";");
keyMap.put(KEY_APOSTROPHE , "'");
keyMap.put(KEY_GRAVE , "`");
keyMap.put(KEY_COMA , ",");
keyMap.put(KEY_DOT , ".");
keyMap.put(KEY_SLASH , "/");
keyMap.put(KEY_APPLICATION , "Application");
}
public static final int[] ASCIItoHID = {
0, //000
0, //001
@ -257,6 +389,14 @@ public class HIDKeycodes {
0 //127 just in case...
};
public static char getChar(byte keyCode) {
for (int i = 0; i < ASCIItoHID.length; i++) {
if (ASCIItoHID[i] == keyCode) {
return (char)i;
}
}
return 0;
}
public static byte getKeyCode(char c) {
return (byte)ASCIItoHID[c]; //TODO range
@ -266,7 +406,31 @@ public class HIDKeycodes {
return ASCIItoHID[c]; //TODO range
}
public static String modifiersToString(byte modifiers) {
String result = "None";
boolean first = true;
byte mod;
for (int i = 0; i < 8; i++) {
mod = (byte)(CTRL_LEFT << i);
if ((modifiers & mod) != 0) {
if ( !first) {
result += ", ";
} else {
result = "";
}
first = false;
result += modifiersMap.get(mod);
}
}
return result;
}
public static String keyToString(byte key) {
String result = keyMap.get(key);
if (result == null) {
result = "Unknown";
}
return result;
}
}

View File

@ -1,39 +1,71 @@
package com.inputstick.api.hid;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import com.inputstick.api.ConnectionManager;
import com.inputstick.api.HIDInfo;
import com.inputstick.api.OnEmptyBufferListener;
import com.inputstick.api.Packet;
import com.inputstick.api.basic.InputStickHID;
public class HIDTransactionQueue {
public static final int KEYBOARD = 1;
public static final int MOUSE = 2;
public static final int CONSUMER = 3;
private static final int BUFFER_SIZE = 32;
private static final int BT_DELAY = 50; //additional delay for BT overhead
private static final int MAX_PACKETS_PER_UPDATE = 10;
private static final int MAX_IMMEDIATE_PACKETS = 3;
private final Vector<HIDTransaction> queue;
private final ConnectionManager mConnectionManager;
private final byte cmd;
private boolean ready;
private int mInterfaceType;
private boolean mustNotify;
private Vector<OnEmptyBufferListener> mBufferEmptyListeners = new Vector<OnEmptyBufferListener>();
private Timer t;
private boolean timerCancelled;
private boolean sentAhead;
private long lastTime;
private int lastReports;
private long minNextTime;
private int lastReports;
public HIDTransactionQueue(int type, ConnectionManager connectionManager) {
// >= FW 0.93
private boolean bufferInitDone;
private boolean constantUpdateMode;
private int bufferFreeSpace;
private int immediatePacketsLeft;
//private int reportsSentSinceLastUpdate;
private int packetsSentSinceLastUpdate;
public HIDTransactionQueue(int interfaceType, ConnectionManager connectionManager) {
constantUpdateMode = false;
bufferFreeSpace = BUFFER_SIZE;
queue = new Vector<HIDTransaction>();
mConnectionManager = connectionManager;
ready = false;
switch (type) {
case KEYBOARD:
sentAhead = false;
minNextTime = 0;
mustNotify = false;
mInterfaceType = interfaceType;
switch (interfaceType) {
case InputStickHID.INTERFACE_KEYBOARD:
cmd = Packet.CMD_HID_DATA_KEYB;
break;
case MOUSE:
case InputStickHID.INTERFACE_MOUSE:
cmd = Packet.CMD_HID_DATA_MOUSE;
break;
case CONSUMER:
case InputStickHID.INTERFACE_CONSUMER:
cmd = Packet.CMD_HID_DATA_CONSUMER;
break;
default:
@ -41,26 +73,32 @@ public class HIDTransactionQueue {
}
}
private void sendNext() {
HIDTransaction transaction;
byte reports = 0;
ready = false;
Packet p = new Packet(false, cmd, reports);
private int sendNext(int maxReports) {
HIDTransaction transaction;
//assume there is at least 1 element in queue
transaction = queue.firstElement();
if (transaction.getReportsCount() > BUFFER_SIZE) {
//transaction too big! split
transaction = transaction.split(BUFFER_SIZE);
if (transaction.getReportsCount() > maxReports) {
// v0.92
if (maxReports < BUFFER_SIZE) {
//don't split transactions until there is no other way left!
return 0;
}
//transaction too big to fit single packet! split
transaction = transaction.split(BUFFER_SIZE);
} else {
queue.removeElementAt(0);
}
byte reports = 0;
ready = false;
Packet p = new Packet(false, cmd, reports);
while (transaction.hasNext()) {
p.addBytes(transaction.getNextReport());
reports++;
}
//TODO add next transactions if possible
while(true) {
if (queue.isEmpty()) {
@ -68,7 +106,7 @@ public class HIDTransactionQueue {
}
transaction = queue.firstElement();
if (reports + transaction.getReportsCount() < BUFFER_SIZE) {
if (reports + transaction.getReportsCount() < maxReports) {
queue.removeElementAt(0);
while (transaction.hasNext()) {
p.addBytes(transaction.getNextReport());
@ -79,33 +117,183 @@ public class HIDTransactionQueue {
}
}
//!! total number of reports must be < 32 ! (max packet limitation)
p.modifyByte(1, reports); //set reports count
mConnectionManager.sendPacket(p);
mConnectionManager.sendPacket(p);
lastReports = reports;
lastTime = System.currentTimeMillis();
minNextTime = lastTime + (lastReports * 4) + BT_DELAY;
if (queue.isEmpty()) {
notifyOnLocalBufferEmpty();
}
return reports;
}
public void addTransaction(HIDTransaction transaction) {
if (queue.isEmpty()) {
if (System.currentTimeMillis() > lastTime + (lastReports * 8 * 2/*just to be safe*/)) {
ready = true;
public void addBufferEmptyListener(OnEmptyBufferListener listener) {
if (listener != null) {
if ( !mBufferEmptyListeners.contains(listener)) {
mBufferEmptyListeners.add(listener);
}
}
}
}
public void removeBufferEmptyListener(OnEmptyBufferListener listener) {
if (listener != null) {
mBufferEmptyListeners.remove(listener);
}
}
private void notifyOnRemoteBufferEmpty() {
for (OnEmptyBufferListener listener : mBufferEmptyListeners) {
listener.onRemoteBufferEmpty(mInterfaceType);
}
}
private void notifyOnLocalBufferEmpty() {
for (OnEmptyBufferListener listener : mBufferEmptyListeners) {
listener.onRemoteBufferEmpty(mInterfaceType);
}
}
public synchronized boolean isLocalBufferEmpty() {
return queue.isEmpty();
}
public synchronized boolean isRemoteBufferEmpty() {
if ((queue.isEmpty()) && (bufferFreeSpace == BUFFER_SIZE)) {
return true;
}
if (queue.isEmpty() && ( !mustNotify)) {
return true;
} else {
return false;
}
}
public synchronized void clearBuffer() {
queue.removeAllElements();
}
public synchronized void addTransaction(HIDTransaction transaction) {
if ( !bufferInitDone) {
queue.add(transaction);
return;
}
if (constantUpdateMode) {
queue.add(transaction);
sendToBuffer(true);
return;
}
mustNotify = true;
//using sentAhead will slow down mouse. FW0.92 will solve the problems
if ((queue.isEmpty()) && (System.currentTimeMillis() > minNextTime) /*&& ( !sentAhead)*/) {
sentAhead = true;
ready = true;
}
queue.add(transaction);
if (ready) {
sendNext();
sendNext(BUFFER_SIZE);
}
}
private synchronized void timerAction() {
if ( !timerCancelled) {
if (sentAhead) {
deviceReady(null, 0); //will set sentAhead to false;
sentAhead = true; //restore value
} else {
deviceReady(null, 0);
}
}
}
public synchronized void deviceReady(HIDInfo hidInfo, int reportsSentToHost) {
//it is possible that in the meantime some packets has been sent to IS!!!
bufferInitDone = true;
if (hidInfo != null) {
if (hidInfo.isSentToHostInfoAvailable()) {
constantUpdateMode = true;
// >= FW 0.93
bufferFreeSpace += reportsSentToHost;
if ((bufferFreeSpace == BUFFER_SIZE) && (queue.isEmpty())) {
notifyOnRemoteBufferEmpty();
}
immediatePacketsLeft = MAX_IMMEDIATE_PACKETS;
//reportsSentSinceLastUpdate = 0;
packetsSentSinceLastUpdate = 0;
sendToBuffer(false);
return;
}
}
long now = System.currentTimeMillis();
//System.out.println("v90 HID update");
if (now < minNextTime) {
//set timer, just in case if deviceReady won't be called again
timerCancelled = false;
t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
timerAction();
}
}, (minNextTime - now + 1));
} else {
timerCancelled = true;
sentAhead = false;
if (!queue.isEmpty()) {
sendNext(BUFFER_SIZE);
} else {
ready = true;
//queue is empty, InputStick reported that buffer is empty, data was added since last notification
if (mustNotify) {
notifyOnRemoteBufferEmpty();
mustNotify = false;
}
}
}
}
public synchronized void sendToBuffer(boolean justAdded) {
if ((justAdded) && (immediatePacketsLeft <= 0)) {
return;
}
if ( !InputStickHID.isReady()) {
return;
}
if (queue.isEmpty()) {
return;
}
if (bufferFreeSpace <= 0) {
return;
}
if (packetsSentSinceLastUpdate >= MAX_PACKETS_PER_UPDATE) {
return;
}
int reportsSent = sendNext(bufferFreeSpace);
if (reportsSent > 0) {
if (justAdded) {
immediatePacketsLeft --;
}
bufferFreeSpace -= reportsSent;
packetsSentSinceLastUpdate ++;
}
}
public void deviceReady() {
if (!queue.isEmpty()) {
sendNext();
} else {
ready = true;
}
}
}

View File

@ -110,6 +110,49 @@ public class GermanLayout extends KeyboardLayout {
};
public static final int DEADKEYS[] = {
0x0060, 0x00b4, 0x005e
};
public static final int DEADKEY_LUT[][] = {
{ 0x00b4 , 0x0079 , 0x00fd } ,
{ 0x00b4 , 0x0061 , 0x00e1 } ,
{ 0x00b4 , 0x0065 , 0x00e9 } ,
{ 0x00b4 , 0x0075 , 0x00fa } ,
{ 0x00b4 , 0x0069 , 0x00ed } ,
{ 0x00b4 , 0x006f , 0x00f3 } ,
{ 0x00b4 , 0x0059 , 0x00dd } ,
{ 0x00b4 , 0x0041 , 0x00c1 } ,
{ 0x00b4 , 0x0045 , 0x00c9 } ,
{ 0x00b4 , 0x0055 , 0x00da } ,
{ 0x00b4 , 0x0049 , 0x00cd } ,
{ 0x00b4 , 0x004f , 0x00d3 } ,
{ 0x00b4 , 0x0020 , 0x00b4 } ,
{ 0x0060 , 0x0061 , 0x00e0 } ,
{ 0x0060 , 0x0065 , 0x00e8 } ,
{ 0x0060 , 0x0075 , 0x00f9 } ,
{ 0x0060 , 0x0069 , 0x00ec } ,
{ 0x0060 , 0x006f , 0x00f2 } ,
{ 0x0060 , 0x0041 , 0x00c0 } ,
{ 0x0060 , 0x0045 , 0x00c8 } ,
{ 0x0060 , 0x0055 , 0x00d9 } ,
{ 0x0060 , 0x0049 , 0x00cc } ,
{ 0x0060 , 0x004f , 0x00d2 } ,
{ 0x0060 , 0x0020 , 0x0060 } ,
{ 0x005e , 0x0061 , 0x00e2 } ,
{ 0x005e , 0x0065 , 0x00ea } ,
{ 0x005e , 0x0075 , 0x00fb } ,
{ 0x005e , 0x0069 , 0x00ee } ,
{ 0x005e , 0x006f , 0x00f4 } ,
{ 0x005e , 0x0041 , 0x00c2 } ,
{ 0x005e , 0x0045 , 0x00ca } ,
{ 0x005e , 0x0055 , 0x00db } ,
{ 0x005e , 0x0049 , 0x00ce } ,
{ 0x005e , 0x004f , 0x00d4 } ,
{ 0x005e , 0x0020 , 0x005e } ,
};
private static GermanLayout instance = new GermanLayout();
private GermanLayout() {
@ -126,9 +169,14 @@ public class GermanLayout extends KeyboardLayout {
@Override
public void type(String text) {
super.type(LUT, text);
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, (byte)0);
}
@Override
public void type(String text, byte modifiers) {
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, modifiers);
}
@Override
public char getChar(int scanCode, boolean capsLock, boolean shift, boolean altGr) {
return super.getChar(LUT, scanCode, capsLock, shift, altGr);
@ -137,6 +185,16 @@ public class GermanLayout extends KeyboardLayout {
@Override
public String getLocaleName() {
return LOCALE_NAME;
}
}
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
}

View File

@ -101,7 +101,7 @@ public abstract class KeyboardLayout {
/* 0x53 */ HIDKeycodes.KEY_DELETE,
/* 0x54 */ 0,
/* 0x55 */ 0,
/* 0x56 */ 0,
/* 0x56 */ HIDKeycodes.KEY_BACKSLASH_NON_US, //GERMAN LAYOUT!
/* 0x57 */ HIDKeycodes.KEY_F11,
/* 0x58 */ HIDKeycodes.KEY_F12,
/* 0x59 */ 0,
@ -117,16 +117,19 @@ public abstract class KeyboardLayout {
public static final int LAYOUT_CODE = 0;
public abstract int[][] getLUT();
public abstract int[][] getDeadkeyLUT();
public abstract int[] getDeadkeys();
public abstract String getLocaleName();
public abstract void type(String text);
public abstract void type(String text, byte modifiers);
public abstract char getChar(int scanCode, boolean capsLock, boolean shift, boolean altGr);
public void type(int[][] lut, String text) {
public void type(int[][] lut, int[][] deadkeyLUT, int[] deadkeys, String text, byte modifiers) {
if (InputStickHID.getState() == ConnectionManager.STATE_READY) {
char[] chars = text.toCharArray();
HIDTransaction t;
for (char c : chars) {
t = getHIDTransaction(lut, c);
t = getHIDTransaction(lut, deadkeyLUT, deadkeys, c, modifiers);
if (t != null) {
InputStickHID.addKeyboardTransaction(t);
}
@ -194,7 +197,7 @@ public abstract class KeyboardLayout {
}
public static int getScanCode(int[][] lut, char c) {
for (int scanCode = 0; scanCode < 80; scanCode++) {
for (int scanCode = 0; scanCode < 0x60; scanCode++) {
if (lut[scanCode][0] == -1) {
continue;
} else {
@ -233,19 +236,79 @@ public abstract class KeyboardLayout {
}
public static HIDTransaction getHIDTransaction(int[][] lut, char c) {
public static boolean isDeadkey(int[] deadkeys, char c) {
if (deadkeys != null) {
for (int key : deadkeys) {
if (key == (int)c) {
return true;
}
}
}
return false;
}
public static int searchLUT(int[][] deadkeyLUT, char c, int returnIndex) {
if (deadkeyLUT != null) {
for (int i = 0; i < deadkeyLUT.length; i++) {
if (deadkeyLUT[i][2] == (int)c) {
return deadkeyLUT[i][returnIndex];
}
}
}
return -1;
}
public static int findDeadKey(int[][] deadkeyLUT, char c) {
return searchLUT(deadkeyLUT, c, 0);
}
public static int findFollowingKey(int[][] deadkeyLUT, char c) {
return searchLUT(deadkeyLUT, c, 1);
}
public static HIDTransaction getHIDTransaction(int[][] lut, int[][] deadkeyLUT, int[] deadkeys, char c, byte additionalModifierKeys) {
byte modifiers, key;
int scanCode;
HIDTransaction t = new HIDTransaction();
scanCode = getScanCode(lut, c);
if (scanCode > 0) {
if (scanCode > 0) {
key = getKey(scanCode);
modifiers = getModifiers(lut, scanCode, c);
modifiers |= additionalModifierKeys;
t.addReport(new KeyboardReport(modifiers, (byte)0));
t.addReport(new KeyboardReport(modifiers, key));
t.addReport(new KeyboardReport());
//add space after deadkey!
if (isDeadkey(deadkeys, c)) {
t.addReport(new KeyboardReport((byte)0, HIDKeycodes.KEY_SPACEBAR)); //this won't work if modifiers are present!
t.addReport(new KeyboardReport());
}
} else {
//check if character can be obtained using deadkey:
int deadkey = findDeadKey(deadkeyLUT, c);
if (deadkey > 0) {
//yes it can
int following = findFollowingKey(deadkeyLUT, c);
scanCode = getScanCode(lut, (char)deadkey);
key = getKey(scanCode);
modifiers = getModifiers(lut, scanCode, (char)deadkey);
t.addReport(new KeyboardReport(modifiers, (byte)0));
t.addReport(new KeyboardReport(modifiers, key));
t.addReport(new KeyboardReport());
scanCode = getScanCode(lut, (char)following);
key = getKey(scanCode);
modifiers = getModifiers(lut, scanCode, (char)following);
t.addReport(new KeyboardReport(modifiers, (byte)0));
t.addReport(new KeyboardReport(modifiers, key));
t.addReport(new KeyboardReport());
}
}
return t;
}
@ -260,7 +323,22 @@ public abstract class KeyboardLayout {
return RussianLayout.getInstance();
} else if (locale.equals(GermanLayout.getInstance().getLocaleName())) {
return GermanLayout.getInstance();
}
} else if (locale.equals(SlovakLayout.getInstance().getLocaleName())) {
return SlovakLayout.getInstance();
} else if (locale.equals(PortugueseBrazilianLayout.getInstance().getLocaleName())) {
return PortugueseBrazilianLayout.getInstance();
} else if (locale.equals(DvorakLayout.getInstance().getLocaleName())) {
return DvorakLayout.getInstance();
} else if (locale.equals(NorwegianLayout.getInstance().getLocaleName())) {
return NorwegianLayout.getInstance();
} else if (locale.equals(SwedishLayout.getInstance().getLocaleName())) {
return SwedishLayout.getInstance();
} else if (locale.equals(FrenchLayout.getInstance().getLocaleName())) {
return FrenchLayout.getInstance();
} else if (locale.equals(SpanishLayout.getInstance().getLocaleName())) {
return SpanishLayout.getInstance();
}
}
return UnitedStatesLayout.getInstance();

View File

@ -111,6 +111,32 @@ public class PolishLayout extends KeyboardLayout {
};
public static final int DEADKEYS[] = {
0x007e
};
public static final int DEADKEY_LUT[][] = {
{ 0x007e , 0x006e , 0x0144 } ,
{ 0x007e , 0x0063 , 0x0107 } ,
{ 0x007e , 0x0078 , 0x017a } ,
{ 0x007e , 0x007a , 0x017c } ,
{ 0x007e , 0x0061 , 0x0105 } ,
{ 0x007e , 0x0073 , 0x015b } ,
{ 0x007e , 0x006c , 0x0142 } ,
{ 0x007e , 0x0065 , 0x0119 } ,
{ 0x007e , 0x006f , 0x00f3 } ,
{ 0x007e , 0x004e , 0x0143 } ,
{ 0x007e , 0x0043 , 0x0106 } ,
{ 0x007e , 0x0058 , 0x0179 } ,
{ 0x007e , 0x005a , 0x017b } ,
{ 0x007e , 0x0041 , 0x0104 } ,
{ 0x007e , 0x0053 , 0x015a } ,
{ 0x007e , 0x004c , 0x0141 } ,
{ 0x007e , 0x0045 , 0x0118 } ,
{ 0x007e , 0x004f , 0x00d3 } ,
{ 0x007e , 0x0020 , 0x007e } ,
};
private static PolishLayout instance = new PolishLayout();
private PolishLayout() {
@ -127,7 +153,12 @@ public class PolishLayout extends KeyboardLayout {
@Override
public void type(String text) {
super.type(LUT, text);
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, (byte)0);
}
@Override
public void type(String text, byte modifiers) {
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, modifiers);
}
@Override
@ -140,5 +171,14 @@ public class PolishLayout extends KeyboardLayout {
return LOCALE_NAME;
}
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
}

View File

@ -112,6 +112,9 @@ public class RussianLayout extends KeyboardLayout {
};
public static final int DEADKEYS[] = null;
public static final int DEADKEY_LUT[][] = null;
private static RussianLayout instance = new RussianLayout();
private RussianLayout() {
@ -128,7 +131,12 @@ public class RussianLayout extends KeyboardLayout {
@Override
public void type(String text) {
super.type(LUT, text);
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, (byte)0);
}
@Override
public void type(String text, byte modifiers) {
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, modifiers);
}
@Override
@ -140,5 +148,15 @@ public class RussianLayout extends KeyboardLayout {
public String getLocaleName() {
return LOCALE_NAME;
}
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
}

View File

@ -98,10 +98,10 @@ public class UnitedStatesLayout extends KeyboardLayout {
/* 50 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 51 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 52 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 53 */ { 0 , 0x002e , 0x002e , -1 , -1 , -1 } ,
/* 53 */ { 0 , 0x002e , 0x002e , -1 , -1 , -1 } ,
/* 54 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 55 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 56 */ { 0 , 0x005c , 0x007c , 0x001c , -1 , -1 } ,
/* 56 */ { 0 , 0x005c , 0x007c , 0x001c , -1 , -1 } ,
/* 57 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 58 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 59 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
@ -114,6 +114,9 @@ public class UnitedStatesLayout extends KeyboardLayout {
};
public static final int DEADKEYS[] = null;
public static final int DEADKEY_LUT[][] = null;
private static UnitedStatesLayout instance = new UnitedStatesLayout();
private UnitedStatesLayout() {
@ -130,7 +133,12 @@ public class UnitedStatesLayout extends KeyboardLayout {
@Override
public void type(String text) {
super.type(LUT, text);
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, (byte)0);
}
@Override
public void type(String text, byte modifiers) {
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, modifiers);
}
@Override
@ -141,6 +149,16 @@ public class UnitedStatesLayout extends KeyboardLayout {
@Override
public String getLocaleName() {
return LOCALE_NAME;
}
}
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
}

View File

@ -3,9 +3,7 @@ package com.inputstick.init;
import com.inputstick.api.Packet;
public class BasicInitManager extends InitManager {
private boolean initDone = false;
public class BasicInitManager extends InitManager {
public BasicInitManager(byte[] key) {
super(key);
@ -14,48 +12,46 @@ public class BasicInitManager extends InitManager {
@Override
public void onConnected() {
/*Packet p = new Packet(false, Packet.RAW_OLD_BOOTLOADER); //compatibility
sendPacket(p);*/
/*Packet p = new Packet(false, Packet.RAW_OLD_BOOTLOADER); //compatibility with old protocol version
sendPacket(p);*/
sendPacket(new Packet(true, Packet.CMD_RUN_FW));
}
@Override
public void onData(byte[] data) {
byte cmd = data[0];
byte respCode = data[1];
byte param = data[1];
if (cmd == Packet.CMD_RUN_FW) {
sendPacket(new Packet(true, Packet.CMD_GET_INFO));
}
if (cmd == Packet.CMD_GET_INFO) {
//store info
sendPacket(new Packet(true, Packet.CMD_INIT)); //TODO params!
}
if (cmd == Packet.CMD_INIT) {
if (respCode == Packet.RESP_OK) {
initDone = true;
sendPacket(new Packet(false, Packet.CMD_HID_STATUS_REPORT));
} else {
mListener.onInitFailure(respCode);
}
}
if (cmd == Packet.CMD_HID_STATUS) {
if (initDone) {
if (param == 0x05) {
mListener.onInitReady();
switch (cmd) {
case Packet.CMD_RUN_FW:
sendPacket(new Packet(true, Packet.CMD_FW_INFO));
break;
case Packet.CMD_FW_INFO:
onFWInfo(data, true, true, new Packet(true, Packet.CMD_INIT)); //TODO next FW: params!
break;
case Packet.CMD_INIT:
if (respCode == Packet.RESP_OK) {
initDone = true;
sendPacket(new Packet(true, Packet.CMD_HID_STATUS_REPORT));
} else {
mListener.onInitNotReady();
}
}
mListener.onInitFailure(respCode);
}
break;
case Packet.CMD_INIT_AUTH:
onAuth(data, true, new Packet(true, Packet.CMD_INIT)); //TODO next FW: params!
break;
case Packet.CMD_HID_STATUS:
if (initDone) {
if (param == 0x05) {
mListener.onInitReady();
} else {
mListener.onInitNotReady();
}
}
break;
}
}
}

View File

@ -1,33 +1,125 @@
package com.inputstick.init;
import java.util.Timer;
import java.util.TimerTask;
import com.inputstick.api.InputStickError;
import com.inputstick.api.Packet;
import com.inputstick.api.PacketManager;
public class InitManager {
public static final int DEFAULT_INIT_TIMEOUT = 60000; //60s init timeout
protected PacketManager mPacketManager;
protected InitManagerListener mListener;
protected byte[] mKey;
protected DeviceInfo mInfo;
protected boolean initDone;
//private Timer t;
public InitManager(byte[] key) {
mKey = key;
mKey = key;
}
public void init(InitManagerListener listener, PacketManager packetManager) {
public DeviceInfo getDeviceInfo() {
return mInfo;
}
public boolean isEncrypted() {
return mPacketManager.isEncrypted();
}
public void init(InitManagerListener listener, PacketManager packetManager) {
mListener = listener;
mPacketManager = packetManager;
initDone = false;
}
//WRONG THREAD!
/*public void startTimeoutCountdown(int timeout) {
t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
if ( !initDone) {
mListener.onInitFailure(InputStickError.ERROR_INIT_TIMEDOUT);
}
}
}, timeout);
}*/
public void onConnected() {
mListener.onInitReady();
}
public void onData(byte[] data) {
//byte cmd = data[0];
//byte param = data[1];
}
public void sendPacket(Packet p) {
mPacketManager.sendPacket(p);
}
public void onFWInfo(byte[] data, boolean authenticate, boolean enableEncryption, Packet sendNext) {
mInfo = new DeviceInfo(data);
if (authenticate) {
if (mInfo.isPasswordProtected()) {
if (mKey != null) {
//authenticate
sendPacket(mPacketManager.encPacket(enableEncryption));
} else {
mListener.onInitFailure(InputStickError.ERROR_SECURITY_NO_KEY);
}
} else {
if (mKey != null) {
//possible scenarios: FW upgrade / password removed using other device/app / tampering!
mListener.onInitFailure(InputStickError.ERROR_SECURITY_NOT_PROTECTED);
}
sendPacket(sendNext);
}
} else {
sendPacket(sendNext);
}
}
public void onAuth(byte[] data, boolean enableOutEncryption, Packet sendNext) {
byte respCode = data[1];
switch (respCode) {
case Packet.RESP_OK:
byte[] cmp = new byte[16];
//TODO check length!
System.arraycopy(data, 2, cmp, 0, 16);
if (mPacketManager.setEncryption(cmp, enableOutEncryption)) {
sendPacket(sendNext);
} else {
mListener.onInitFailure(InputStickError.ERROR_SECURITY_CHALLENGE);
}
break;
case 0x20:
mListener.onInitFailure(InputStickError.ERROR_SECURITY_INVALID_KEY);
break;
case 0x21:
mListener.onInitFailure(InputStickError.ERROR_SECURITY_NOT_PROTECTED);
break;
case Packet.RESP_UNKNOWN_CMD:
mListener.onInitFailure(InputStickError.ERROR_SECURITY_NOT_SUPPORTED);
break;
default:
mListener.onInitFailure(InputStickError.ERROR_SECURITY);
}
}
}

View File

@ -7,11 +7,24 @@
package com.inputstick.api;
public final class R {
public static final class dimen {
public static final int activity_horizontal_margin = 0x7f050000;
public static final int activity_vertical_margin = 0x7f050001;
}
public static final class drawable {
public static final int ic_launcher = 0x7f020000;
}
public static final class id {
public static final int action_settings = 0x7f0a0003;
}
public static final class menu {
public static final int install_utility = 0x7f090000;
}
public static final class string {
public static final int action_settings = 0x7f060002;
public static final int app_name = 0x7f060000;
public static final int hello_world = 0x7f060003;
public static final int title_activity_install_utility = 0x7f060001;
}
public static final class style {
public static final int AppBaseTheme = 0x7f070000;

View File

@ -21,6 +21,11 @@ public final class R {
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
Default screen margins, per the Android Design guidelines.
Customize dimensions originally defined in res/values/dimens.xml (such as
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
Default screen margins, per the Android Design guidelines.
Example customization of dimensions originally defined in res/values/dimens.xml
@ -46,23 +51,26 @@ public final class R {
public static final int fragment_main=0x7f030001;
}
public static final class menu {
public static final int main=0x7f090000;
public static final int install_utility=0x7f090000;
public static final int main=0x7f090001;
}
public static final class string {
public static final int action_input_stick=0x7f060005;
public static final int action_settings=0x7f060001;
public static final int action_type_enter=0x7f060007;
public static final int action_type_tab=0x7f060006;
public static final int action_type_user_tab_pass_enter=0x7f060008;
public static final int action_input_stick=0x7f060007;
public static final int action_settings=0x7f060002;
public static final int action_type_enter=0x7f060009;
public static final int action_type_tab=0x7f060008;
public static final int action_type_user_tab_pass_enter=0x7f06000a;
public static final int app_name=0x7f060000;
/** Strings related to Settings
*/
public static final int configure_plugin=0x7f06000a;
public static final int kp2aplugin_author=0x7f060004;
public static final int kp2aplugin_shortdesc=0x7f060003;
public static final int kp2aplugin_title=0x7f060002;
public static final int layout_title=0x7f06000b;
public static final int title_activity_settings=0x7f060009;
public static final int configure_plugin=0x7f06000c;
public static final int hello_world=0x7f060003;
public static final int kp2aplugin_author=0x7f060006;
public static final int kp2aplugin_shortdesc=0x7f060005;
public static final int kp2aplugin_title=0x7f060004;
public static final int layout_title=0x7f06000d;
public static final int title_activity_install_utility=0x7f060001;
public static final int title_activity_settings=0x7f06000b;
}
public static final class style {
/**

View File

@ -8,17 +8,31 @@
<string name="layout_title">Host keyboard layout</string>
<string-array name="layout_names">
<item>English (United States) - en-US</item>
<item>German - de-DE</item>
<item>Polish - pl-PL</item>
<item>Russian - ru-RU</item>
<item>English (US)</item>
<item>German</item>
<item>Polish</item>
<item>Russian</item>
<item>Slovak</item>
<item>Portuguese (BR)</item>
<item>Norwegian</item>
<item>Swedish</item>
<item>French</item>
<item>Spanish</item>
<item>English (Dvorak)</item>
</string-array>
<string-array name="layout_values">
<item>en-US</item>
<item>en-US</item>
<item>de-DE</item>
<item>pl-PL</item>
<item>ru-RU</item>
<item>sk-SK</item>
<item>pt-BR</item>
<item>nb-NO</item>
<item>sv-SE</item>
<item>fr-FR</item>
<item>es-ES</item>
<item>en-DV</item>
</string-array>
</resources>