Merge pull request #1526 from keymanapp/android-dispatch-external-keys

[Android] dispatch external keys if KMW doesn't handle them
This commit is contained in:
Darcy Wong 2019-01-14 18:43:40 +07:00 • committed by GitHub
commit c1929add39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 203 additions and 128 deletions

View file

@ -216,12 +216,14 @@
kmw['executePopupKey'](keyID);
}
function executeHardwareKeystroke(code, shift, lstates) {
function executeHardwareKeystroke(code, shift, lstates, eventModifiers) {
var kmw=window['keyman'];
//window.console.log('executeHardwareKeystroke:('+code+', ' + shift + ', ' + lstates + ');');
try {
var r = kmw['executeHardwareKeystroke'](code, shift, lstates);
//window.console.log('executeHardwareKeystroke completed with '+r);
if (!kmw['executeHardwareKeystroke'](code, shift, lstates)) {
// KMW didn't process the key, so have the Android app dispatch the key with the original event modifiers
window.jsInterface.dispatchKey(code, eventModifiers);
}
} catch(e) {
window.console.log('executeHardwareKeystroke exception: '+e);
}

View file

@ -4,119 +4,13 @@
package com.tavultesoft.kmea;
import com.tavultesoft.kmea.KMScanCodeMap;
import android.content.Context;
import android.view.KeyEvent;
public class KMHardwareKeyboardInterpreter implements KeyEvent.Callback {
/**
* Maps relevant keycodes from android.view.keyevent to the standard keycodes used by Keyman
* keyboards. Is conveniently based at 0, else we might need to HashMap it instead.
*/
/**
* From reference: https://source.android.com/devices/input#understanding-hid-usages-and-event-codes
* The Android API sometimes refers to the Linux key code associated with a key as its "scan code".
* This is technically incorrect in but it helps to distinguish Linux key codes from Android
* key codes in the API.
*
* Maps relevant Linux key code (LKC) to the standard key codes used by Keyman keyboards
* Table: https://source.android.com/devices/input/keyboard-devices
*
* Limitations: Intentionally not assigning number pad keys
*/
private static final
int scanCodeMap[] = {
0, // padding = 0x00;
0, // public static final int KEY_ESC = 0x01;
'1', // public static final int KEY_1 = 0x02;
'2', // public static final int KEY_2 = 0x03;
'3', // public static final int KEY_3 = 0x04;
'4', // public static final int KEY_4 = 0x05;
'5', // public static final int KEY_5 = 0x06;
'6', // public static final int KEY_6 = 0x07;
'7', // public static final int KEY_7 = 0x08;
'8', // public static final int KEY_8 = 0x09;
'9', // public static final int KEY_9 = 0x0A;
'0', // public static final int KEY_0 = 0x0B;
189, // public static final int KEY_MINUS = 0x0C;
187, // public static final int KEY_EQUALS = 0x0D;
8, // public static final int KEY_BACKSPACE = 0x0E;
9, // public static final int KEY_TAB = 0x0F;
'Q', // public static final int KEY_Q = 0x10;
'W', // public static final int KEY_W = 0x11;
'E', // public static final int KEY_E = 0x12;
'R', // public static final int KEY_R = 0x13;
'T', // public static final int KEY_T = 0x14;
'Y', // public static final int KEY_Y = 0x15;
'U', // public static final int KEY_U = 0x16;
'I', // public static final int KEY_I = 0x17;
'O', // public static final int KEY_O = 0x18;
'P', // public static final int KEY_P = 0x19;
219, // public static final int KEY_LEFTBRACE = 0x1A;
221, // public static final int KEY_RIGHTBRACE = 0x1B;
13, // public static final int KEY_ENTER = 0x1C;
0, // public static final int KEY_LEFTCTRL = 0x1D;
'A', // public static final int KEY_A = 0x1E;
'S', // public static final int KEY_S = 0x1F;
'D', // public static final int KEY_D = 0x20;
'F', // public static final int KEY_F = 0x21;
'G', // public static final int KEY_G = 0x22;
'H', // public static final int KEY_H = 0x23;
'J', // public static final int KEY_J = 0x24;
'K', // public static final int KEY_K = 0x25;
'L', // public static final int KEY_L = 0x26;
186, // public static final int KEY_SEMICOLON = 0x27;
222, // public static final int KEY_APOSTROPHE = 0x28;
192, // public static final int KEY_GRAVE = 0x29;
0, // public static final int KEY_LEFTSHIFT = 0x2A;
220, // public static final int KEY_BACKSLASH = 0x2B;
'Z', // public static final int KEY_Z = 0x2C;
'X', // public static final int KEY_X = 0x2D;
'C', // public static final int KEY_C = 0x2E;
'V', // public static final int KEY_V = 0x2F;
'B', // public static final int KEY_B = 0x30;
'N', // public static final int KEY_N = 0x31;
'M', // public static final int KEY_M = 0x32;
188, // public static final int KEY_COMMA = 0x33;
190, // public static final int KEY_DOT = 0x34;
191, // public static final int KEY_SLASH = 0x35;
0, // public static final int KEY_RIGHTSHIFT = 0x36;
0, // public static final int KEY_KPASTERISK = 0x37;
0, // public static final int KEY_LEFTALT = 0x38;
32, // public static final int KEY_SPACE = 0x39;
0, // public static final int KEY_CAPSLOCK = 0x3A;
0, // public static final int KEY_F1 = 0x3B;
0, // public static final int KEY_F2 = 0x3C;
0, // public static final int KEY_F3 = 0x3D;
0, // public static final int KEY_F4 = 0x3E;
0, // public static final int KEY_F5 = 0x3F;
0, // public static final int KEY_F6 = 0x40;
0, // public static final int KEY_F7 = 0x41;
0, // public static final int KEY_F8 = 0x42;
0, // public static final int KEY_F9 = 0x43;
0, // public static final int KEY_F10 = 0x44;
0, // public static final int KEY_NUMLOCK = 0x45;
0, // public static final int KEY_SCROLLLOCK = 0x46;
0, // public static final int KEY_KP7 = 0x47;
0, // public static final int KEY_KP8 = 0x48;
0, // public static final int KEY_KP9 = 0x49;
0, // public static final int KEY_KPMINUS = 0x4A;
0, // public static final int KEY_KP4 = 0x4B;
0, // public static final int KEY_KP5 = 0x4C;
0, // public static final int KEY_KP6 = 0x4D;
0, // public static final int KEY_KPPLUS = 0x4E;
0, // public static final int KEY_KP1 = 0x4F;
0, // public static final int KEY_KP2 = 0x50;
0, // public static final int KEY_KP3 = 0x51;
0, // public static final int KEY_KP0 = 0x52;
0, // public static final int KEY_KPDOT = 0x53;
0, // padding 0x54;
0, // public static final int KEY_ZENKAKUHANKAKU = 0x55;
226 // public static final int KEY_102ND = 0x56;
// Many more KEYS currently not used by KMW...
};
private final Context context;
private final KMManager.KeyboardType keyboardType;
@ -169,14 +63,14 @@ public class KMHardwareKeyboardInterpreter implements KeyEvent.Callback {
// Range check scanCode. If it's beyond our expected range, just use "0"
int scanCode = event.getScanCode();
int code = (scanCode >= 0 && scanCode < scanCodeMap.length) ? scanCodeMap[scanCode] : 0 ;
int code = (scanCode >= 0 && scanCode < KMScanCodeMap.scanCodeMap.length) ? KMScanCodeMap.scanCodeMap[scanCode] : 0 ;
if (code == 0) {
// Not an alphanumeric, punctuation or enter/tab/space key
return false;
}
// Send keystroke to KeymanWeb for processing: will return true to swallow the keystroke
return KMManager.executeHardwareKeystroke(code, keymanModifiers, keyboardType, Lstates);
return KMManager.executeHardwareKeystroke(code, keymanModifiers, keyboardType, Lstates, androidModifiers);
}
@Override

View file

@ -175,9 +175,9 @@ final class KMKeyboard extends WebView {
loadUrl(jsString);
}
public void executeHardwareKeystroke(int code, int shift, int lstates) {
String jsFormat = "javascript:executeHardwareKeystroke(%d,%d, %d)";
String jsString = String.format(jsFormat, code, shift, lstates);
public void executeHardwareKeystroke(int code, int shift, int lstates, int eventModifiers) {
String jsFormat = "javascript:executeHardwareKeystroke(%d,%d, %d, %d)";
String jsString = String.format(jsFormat, code, shift, lstates, eventModifiers);
loadUrl(jsString);
}

View file

@ -63,6 +63,9 @@ public abstract class KMKeyboardJSHandler {
}
}
@JavascriptInterface
public abstract boolean dispatchKey(final int code, final int eventModifiers);
// Insert the selected string s
@JavascriptInterface
public abstract void insertText(final int dn, final String s);

View file

@ -52,6 +52,7 @@ import com.tavultesoft.kmea.KMKeyboardJSHandler;
import com.tavultesoft.kmea.KeyboardEventHandler.EventType;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener;
import com.tavultesoft.kmea.packages.PackageProcessor;
import com.tavultesoft.kmea.KMScanCodeMap;
import com.tavultesoft.kmea.util.FileUtils;
import org.json.JSONObject;
@ -198,23 +199,23 @@ public final class KMManager {
IMService = service;
}
public static boolean executeHardwareKeystroke(int code, int shift, int lstates) {
public static boolean executeHardwareKeystroke(int code, int shift, int lstates, int eventModifiers) {
if (SystemKeyboard != null) {
return executeHardwareKeystroke(code, shift, KeyboardType.KEYBOARD_TYPE_SYSTEM, lstates);
return executeHardwareKeystroke(code, shift, KeyboardType.KEYBOARD_TYPE_SYSTEM, lstates, eventModifiers);
} else if (InAppKeyboard != null) {
return executeHardwareKeystroke(code, shift, KeyboardType.KEYBOARD_TYPE_INAPP, lstates);
return executeHardwareKeystroke(code, shift, KeyboardType.KEYBOARD_TYPE_INAPP, lstates, eventModifiers);
}
return false;
}
public static boolean executeHardwareKeystroke(
int code, int shift, KeyboardType keyboard, int lstates) {
int code, int shift, KeyboardType keyboard, int lstates, int eventModifiers) {
if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) {
InAppKeyboard.executeHardwareKeystroke(code, shift, lstates);
InAppKeyboard.executeHardwareKeystroke(code, shift, lstates, eventModifiers);
return true;
} else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
SystemKeyboard.executeHardwareKeystroke(code, shift, lstates);
SystemKeyboard.executeHardwareKeystroke(code, shift, lstates, eventModifiers);
return true;
}
@ -1461,6 +1462,33 @@ public final class KMManager {
KMInAppKeyboardJSHandler(Context context, KMKeyboard k) {
super(context, k);
}
private static final String HANDLER_TAG = "IAK: JS Handler";
@JavascriptInterface
public boolean dispatchKey(final int code, final int eventModifiers) {
Handler mainLoop = new Handler(Looper.getMainLooper());
mainLoop.post(new Runnable() {
public void run() {
if (InAppKeyboard.subKeysWindow != null || KMTextView.activeView == null || KMTextView.activeView.getClass() != KMTextView.class) {
if ((KMTextView.activeView == null) && isDebugMode()) {
Log.w(HANDLER_TAG, "dispatchKey failed: activeView is null");
}
return;
}
// Handle tab or enter since KMW didn't process it
KMTextView textView = (KMTextView) KMTextView.activeView;
if (code == KMScanCodeMap.scanCodeMap[KMScanCodeMap.KEY_TAB]) {
KeyEvent event = new KeyEvent(0, 0, 0, KeyEvent.KEYCODE_TAB, 0, eventModifiers, 0, 0, 0);
textView.dispatchKeyEvent(event);
} else if (code == KMScanCodeMap.scanCodeMap[KMScanCodeMap.KEY_ENTER]) {
KeyEvent event = new KeyEvent(0, 0, 0, KeyEvent.KEYCODE_ENTER, 0, eventModifiers, 0, 0, 0);
textView.dispatchKeyEvent(event);
}
}
});
return true;
}
// This annotation is required in Jelly Bean and later:
@JavascriptInterface
@ -1552,6 +1580,40 @@ public final class KMManager {
KMSystemKeyboardJSHandler(Context context, KMKeyboard k) {
super(context, k);
}
private static final String HANDLER_TAG = "SWK: JS Handler";
@JavascriptInterface
public boolean dispatchKey(final int code, final int eventModifiers) {
Handler mainLoop = new Handler(Looper.getMainLooper());
mainLoop.post(new Runnable() {
public void run() {
if (SystemKeyboard.subKeysWindow != null) {
return;
}
InputConnection ic = IMService.getCurrentInputConnection();
if (ic == null) {
if (isDebugMode()) {
Log.w(HANDLER_TAG, "insertText failed: InputConnection is null");
}
return;
}
SystemKeyboard.dismissHelpBubble();
// Handle tab or enter since KMW didn't process it
Log.d(HANDLER_TAG, "dispatchKey called with code: " + code + ", eventModifiers: " + eventModifiers);
if (code == KMScanCodeMap.scanCodeMap[KMScanCodeMap.KEY_TAB]) {
KeyEvent event = new KeyEvent(0, 0, 0, KeyEvent.KEYCODE_TAB, 0, eventModifiers, 0, 0, 0);
ic.sendKeyEvent(event);
} else if (code == KMScanCodeMap.scanCodeMap[KMScanCodeMap.KEY_ENTER]) {
KeyEvent event = new KeyEvent(0, 0, 0, KeyEvent.KEYCODE_ENTER, 0, eventModifiers, 0, 0, 0);
ic.sendKeyEvent(event);
}
}
});
return true;
}
// This annotation is required in Jelly Bean and later:
@JavascriptInterface
@ -1566,7 +1628,7 @@ public final class KMManager {
InputConnection ic = IMService.getCurrentInputConnection();
if (ic == null) {
if (isDebugMode()) {
Log.w("SWK: JS Handler", "insertText failed: InputConnection is null");
Log.w(HANDLER_TAG, "insertText failed: InputConnection is null");
}
return;
}

View file

@ -0,0 +1,115 @@
package com.tavultesoft.kmea;
public final class KMScanCodeMap {
/**
* Maps relevant keycodes from android.view.keyevent to the standard keycodes used by Keyman
* keyboards. Is conveniently based at 0, else we might need to HashMap it instead.
*/
/**
* From reference: https://source.android.com/devices/input#understanding-hid-usages-and-event-codes
* The Android API sometimes refers to the Linux key code associated with a key as its "scan code".
* This is technically incorrect in but it helps to distinguish Linux key codes from Android
* key codes in the API.
*
* Maps relevant Linux key code (LKC) to the standard key codes used by Keyman keyboards
* Table: https://source.android.com/devices/input/keyboard-devices
*
* Limitations: Intentionally not assigning number pad keys
*/
final static int scanCodeMap[] = {
0, // padding = 0x00;
0, // public static final int KEY_ESC = 0x01;
'1', // public static final int KEY_1 = 0x02;
'2', // public static final int KEY_2 = 0x03;
'3', // public static final int KEY_3 = 0x04;
'4', // public static final int KEY_4 = 0x05;
'5', // public static final int KEY_5 = 0x06;
'6', // public static final int KEY_6 = 0x07;
'7', // public static final int KEY_7 = 0x08;
'8', // public static final int KEY_8 = 0x09;
'9', // public static final int KEY_9 = 0x0A;
'0', // public static final int KEY_0 = 0x0B;
189, // public static final int KEY_MINUS = 0x0C;
187, // public static final int KEY_EQUALS = 0x0D;
8, // public static final int KEY_BACKSPACE = 0x0E;
9, // public static final int KEY_TAB = 0x0F;
'Q', // public static final int KEY_Q = 0x10;
'W', // public static final int KEY_W = 0x11;
'E', // public static final int KEY_E = 0x12;
'R', // public static final int KEY_R = 0x13;
'T', // public static final int KEY_T = 0x14;
'Y', // public static final int KEY_Y = 0x15;
'U', // public static final int KEY_U = 0x16;
'I', // public static final int KEY_I = 0x17;
'O', // public static final int KEY_O = 0x18;
'P', // public static final int KEY_P = 0x19;
219, // public static final int KEY_LEFTBRACE = 0x1A;
221, // public static final int KEY_RIGHTBRACE = 0x1B;
13, // public static final int KEY_ENTER = 0x1C;
0, // public static final int KEY_LEFTCTRL = 0x1D;
'A', // public static final int KEY_A = 0x1E;
'S', // public static final int KEY_S = 0x1F;
'D', // public static final int KEY_D = 0x20;
'F', // public static final int KEY_F = 0x21;
'G', // public static final int KEY_G = 0x22;
'H', // public static final int KEY_H = 0x23;
'J', // public static final int KEY_J = 0x24;
'K', // public static final int KEY_K = 0x25;
'L', // public static final int KEY_L = 0x26;
186, // public static final int KEY_SEMICOLON = 0x27;
222, // public static final int KEY_APOSTROPHE = 0x28;
192, // public static final int KEY_GRAVE = 0x29;
0, // public static final int KEY_LEFTSHIFT = 0x2A;
220, // public static final int KEY_BACKSLASH = 0x2B;
'Z', // public static final int KEY_Z = 0x2C;
'X', // public static final int KEY_X = 0x2D;
'C', // public static final int KEY_C = 0x2E;
'V', // public static final int KEY_V = 0x2F;
'B', // public static final int KEY_B = 0x30;
'N', // public static final int KEY_N = 0x31;
'M', // public static final int KEY_M = 0x32;
188, // public static final int KEY_COMMA = 0x33;
190, // public static final int KEY_DOT = 0x34;
191, // public static final int KEY_SLASH = 0x35;
0, // public static final int KEY_RIGHTSHIFT = 0x36;
0, // public static final int KEY_KPASTERISK = 0x37;
0, // public static final int KEY_LEFTALT = 0x38;
32, // public static final int KEY_SPACE = 0x39;
0, // public static final int KEY_CAPSLOCK = 0x3A;
0, // public static final int KEY_F1 = 0x3B;
0, // public static final int KEY_F2 = 0x3C;
0, // public static final int KEY_F3 = 0x3D;
0, // public static final int KEY_F4 = 0x3E;
0, // public static final int KEY_F5 = 0x3F;
0, // public static final int KEY_F6 = 0x40;
0, // public static final int KEY_F7 = 0x41;
0, // public static final int KEY_F8 = 0x42;
0, // public static final int KEY_F9 = 0x43;
0, // public static final int KEY_F10 = 0x44;
0, // public static final int KEY_NUMLOCK = 0x45;
0, // public static final int KEY_SCROLLLOCK = 0x46;
0, // public static final int KEY_KP7 = 0x47;
0, // public static final int KEY_KP8 = 0x48;
0, // public static final int KEY_KP9 = 0x49;
0, // public static final int KEY_KPMINUS = 0x4A;
0, // public static final int KEY_KP4 = 0x4B;
0, // public static final int KEY_KP5 = 0x4C;
0, // public static final int KEY_KP6 = 0x4D;
0, // public static final int KEY_KPPLUS = 0x4E;
0, // public static final int KEY_KP1 = 0x4F;
0, // public static final int KEY_KP2 = 0x50;
0, // public static final int KEY_KP3 = 0x51;
0, // public static final int KEY_KP0 = 0x52;
0, // public static final int KEY_KPDOT = 0x53;
0, // padding 0x54;
0, // public static final int KEY_ZENKAKUHANKAKU = 0x55;
226 // public static final int KEY_102ND = 0x56;
// Many more KEYS currently not used by KMW...
};
final static int KEY_TAB = 0x0F;
final static int KEY_ENTER = 0x1C;
}

View file

@ -2,6 +2,7 @@
## 2019-01-14 11.0.2055 beta
* Keyman for Android 11 requires a minimum version of Android 4.1 (Jelly Bean) (#1532)
* When KMW doesn't process external "tab" or "enter" keys, have the Android app dispatch the keys (#1526)
## 2019-01-10 11.0.2054 beta
* Fix keyboard version comparison that was causing "Unable to contact Keyman server" notifications (#1520)

View file

@ -579,15 +579,12 @@
* @return {boolean} true if key code successfully processed
*/
keymanweb.processDefaultMapping = function(code, shift, Lelem, keyName) {
// Default handling for external keys.
// Intentionally not assigning K_TAB or K_ENTER so KMW will pass them back
// to the mobile apps to handle (insert characters or navigate forms).
if (code == osk.keyCodes.K_SPACE) {
kbdInterface.output(0, Lelem, ' ');
return true;
} else if (code == osk.keyCodes.K_ENTER) {
kbdInterface.output(0, Lelem, '\n');
return true;
} else if (code == osk.keyCodes.K_TAB) {
kbdInterface.output(0, Lelem, '\t');
return true;
} else if (code == osk.keyCodes.K_BKSP) {
kbdInterface.defaultBackspace();
return true;
@ -601,6 +598,7 @@
return true;
}
// Determine the character from the OSK
var ch = osk.defaultKeyOutput(keyName, code, shift, false, undefined);
if(ch) {
kbdInterface.output(0, Lelem, ch);