/** * Copyright (C) 2013- Iordan Iordanov * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ package com.undatech.opaque.input; import android.content.Context; import android.os.Handler; import android.os.SystemClock; import android.view.KeyCharacterMap; import android.view.KeyEvent; import com.undatech.opaque.RemoteCanvas; import com.undatech.opaque.SpiceCommunicator; public abstract class RemoteKeyboard { private static final String TAG = "RemoteKeyboard"; public final static int SCANCODE_LCTRL = 29; public final static int SCANCODE_RCTRL = 97; // My mask for the modifier masks. public final static int SHIFT_ON_MASK = 1; public final static int ALT_ON_MASK = 2; public final static int CTRL_ON_MASK = 4; public final static int SUPER_ON_MASK = 8; public final static int ALTGR_ON_MASK = 16; protected RemoteCanvas canvas; protected Handler handler; protected SpiceCommunicator spicecomm; protected Context context; protected KeyRepeater keyRepeater; // Variable holding the state of any pressed hardware meta keys protected int hardwareMetaState = 0; // Variable holding the state of the on-screen meta keys protected int onScreenMetaState = 0; // This variable tells us whether we need to skip junk characters for // SDK >= 16 and LatinIME next time a multi-character event comes along. public boolean skippedJunkChars = true; // Variable used for BB10 workarounds boolean bb = false; // Variable holding the state of the last metaState sent over with // a button down event. It is reset to 0 with a button up event. protected int lastDownMetaState = 0; RemoteKeyboard (SpiceCommunicator r, RemoteCanvas v, Handler h) { spicecomm = r; canvas = v; handler = h; keyRepeater = new KeyRepeater (this, h); if (android.os.Build.MODEL.contains("BlackBerry") || android.os.Build.BRAND.contains("BlackBerry") || android.os.Build.MANUFACTURER.contains("BlackBerry")) { bb = true; } } /** * Sends key event to server with no additional meta state. * @param keyCode * @param event * @return */ public abstract boolean keyEvent(int keyCode, KeyEvent event); /** * Sends key event with additional meta state to server * @param keyCode * @param event * @param additionalMetaState * @return */ public abstract boolean keyEvent(int keyCode, KeyEvent event, int additionalMetaState); /** * Starts repeating a key event. * @param keyCode * @param event */ public void repeatKeyEvent(int keyCode, KeyEvent event) { keyRepeater.start(keyCode, event); } /** * Stops repeating the last key event being repeated. */ public void stopRepeatingKeyEvent() { keyRepeater.stop(); } /** * Toggles on-screen Ctrl mask. Returns true if result is Ctrl enabled, false otherwise. * @return true if on false otherwise. */ public boolean onScreenCtrlToggle() { // If we find Ctrl on, turn it off. Otherwise, turn it on. if (onScreenMetaState == (onScreenMetaState | CTRL_ON_MASK)) { onScreenCtrlOff(); return false; } else { onScreenCtrlOn(); return true; } } /** * Turns on on-screen Ctrl. */ public void onScreenCtrlOn() { onScreenMetaState = onScreenMetaState | CTRL_ON_MASK; } /** * Turns off on-screen Ctrl. */ public void onScreenCtrlOff() { onScreenMetaState = onScreenMetaState & ~CTRL_ON_MASK; } /** * Toggles on-screen Alt mask. Returns true if result is Alt enabled, false otherwise. * @return true if on false otherwise. */ public boolean onScreenAltToggle() { // If we find Alt on, turn it off. Otherwise, turn it on. if (onScreenMetaState == (onScreenMetaState | ALT_ON_MASK)) { onScreenAltOff(); return false; } else { onScreenAltOn(); return true; } } /** * Turns on on-screen Alt. */ public void onScreenAltOn() { onScreenMetaState = onScreenMetaState | ALT_ON_MASK; } /** * Turns off on-screen Alt. */ public void onScreenAltOff() { onScreenMetaState = onScreenMetaState & ~ALT_ON_MASK; } /** * Toggles on-screen Super mask. Returns true if result is Super enabled, false otherwise. * @return true if on false otherwise. */ public boolean onScreenSuperToggle() { // If we find Super on, turn it off. Otherwise, turn it on. if (onScreenMetaState == (onScreenMetaState | SUPER_ON_MASK)) { onScreenSuperOff(); return false; } else { onScreenSuperOn(); return true; } } /** * Turns on on-screen Super. */ public void onScreenSuperOn() { onScreenMetaState = onScreenMetaState | SUPER_ON_MASK; } /** * Turns off on-screen Super. */ public void onScreenSuperOff() { onScreenMetaState = onScreenMetaState & ~SUPER_ON_MASK; } /** * Toggles on-screen Shift mask. Returns true if result is Shift enabled, false otherwise. * @return true if on false otherwise. */ public boolean onScreenShiftToggle() { // If we find Super on, turn it off. Otherwise, turn it on. if (onScreenMetaState == (onScreenMetaState | SHIFT_ON_MASK)) { onScreenShiftOff(); return false; } else { onScreenShiftOn(); return true; } } /** * Turns on on-screen Shift. */ public void onScreenShiftOn() { onScreenMetaState = onScreenMetaState | SHIFT_ON_MASK; } /** * Turns off on-screen Shift. */ public void onScreenShiftOff() { onScreenMetaState = onScreenMetaState & ~SHIFT_ON_MASK; } /** * Getter for the on-screen meta state. * @return */ public int getMetaState () { return onScreenMetaState|lastDownMetaState; } /** * Clears the on-screen meta state. */ public void clearOnScreenMetaState () { onScreenMetaState = 0; } /** * Used to send a string over as a stream of unicode characters. * @param s */ public void sendString(String s) { for (int i = 0; i < s.length(); i++) { char nextChar = s.charAt(i); if (Character.isISOControl(nextChar)) { if (nextChar == '\n') { int keyCode = KeyEvent.KEYCODE_ENTER; keyEvent(keyCode, new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); keyEvent(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode)); } } else { sendUnicodeChar (nextChar); } } } /** * Tries to convert a unicode character to a KeyEvent and if successful sends with keyEvent(). * @param unicodeChar * @param metaState */ public boolean sendUnicodeChar (char unicodeChar) { KeyCharacterMap fullKmap = KeyCharacterMap.load(KeyCharacterMap.FULL); KeyCharacterMap virtualKmap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); char[] s = new char[1]; s[0] = unicodeChar; KeyEvent[] events = fullKmap.getEvents(s); // Failing with the FULL keymap, try the VIRTUAL_KEYBOARD one. if (events == null) { events = virtualKmap.getEvents(s); } boolean result = false; if (events != null) { for (int i = 0; i < events.length; i++) { KeyEvent evt = events[i]; keyEvent(evt.getKeyCode(), evt); result = true; } } else { android.util.Log.e("RemoteKeyboard", "Could not use any keymap to generate KeyEvent for unicode: " + unicodeChar); } return result; } /** * Used to calculate how many junk characters to skip. * @param numchars * @param evt * @return */ int numJunkCharactersToSkip (int numchars, KeyEvent evt) { int i = 0; if (!skippedJunkChars) { if (numchars == 10000) { // We received the event not because the user typed something but // because of another reason (for example lock/unlock screen). i = numchars; } else { // The user has typed at least one char, so we need to skip just the junk // characters, so skip backward until we hit the first junk character. for (i = Math.max(numchars - 2, 0); i > 0 ; i--) { if (evt.getCharacters().charAt(i) == '%') { i = i + 1; break; } } skippedJunkChars = true; } } return i; } /** * Converts event meta state to our meta state. * @param event * @return */ protected int convertEventMetaState (KeyEvent event, int eventMetaState) { int metaState = 0; int altMask = KeyEvent.META_ALT_RIGHT_ON; // Detect whether this event is coming from a default hardware keyboard. // We have to leave KeyEvent.KEYCODE_ALT_LEFT for symbol input on a default hardware keyboard. boolean defaultHardwareKbd = (event.getDeviceId() == 0); if (!bb && !defaultHardwareKbd) { altMask = KeyEvent.META_ALT_MASK; } // Add shift, ctrl, alt, and super to metaState if necessary. if ((eventMetaState & 0x000000c1 /*KeyEvent.META_SHIFT_MASK*/) != 0) { metaState |= SHIFT_ON_MASK; } if ((eventMetaState & 0x00007000 /*KeyEvent.META_CTRL_MASK*/) != 0) { metaState |= CTRL_ON_MASK; } if ((eventMetaState & altMask) !=0) { metaState |= ALT_ON_MASK; } if ((eventMetaState & 0x00070000 /*KeyEvent.META_META_MASK*/) != 0) { metaState |= SUPER_ON_MASK; } return metaState; } }