/*
* Copyright (c) 2013 Menny Even-Danan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.anysoftkeyboard.keyboards;
import android.content.Context;
import android.content.res.Configuration;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Xml;
import com.anysoftkeyboard.AnySoftKeyboard;
import com.anysoftkeyboard.addons.AddOn;
import com.anysoftkeyboard.api.KeyCodes;
import com.anysoftkeyboard.keyboardextensions.KeyboardExtension;
import com.anysoftkeyboard.keyboardextensions.KeyboardExtensionFactory;
import com.anysoftkeyboard.keyboards.AnyKeyboard.HardKeyboardTranslator;
import com.anysoftkeyboard.utils.Log;
import com.menny.android.anysoftkeyboard.BuildConfig;
import org.xmlpull.v1.XmlPullParser;
import java.text.ParseException;
import java.util.HashSet;
public class ExternalAnyKeyboard extends AnyKeyboard implements
HardKeyboardTranslator {
public static final int KEYCODE_EXTENSION_KEYBOARD = -210;
private final static String TAG = "ASK - EAK";
private static final String XML_TRANSLATION_TAG = "PhysicalTranslation";
private static final String XML_QWERTY_ATTRIBUTE = "QwertyTranslation";
private static final String XML_SEQUENCE_TAG = "SequenceMapping";
private static final String XML_KEYS_ATTRIBUTE = "keySequence";
private static final String XML_TARGET_ATTRIBUTE = "targetChar";
private static final String XML_TARGET_CHAR_CODE_ATTRIBUTE = "targetCharCode";
private static final String XML_MULTITAP_TAG = "MultiTap";
private static final String XML_MULTITAP_KEY_ATTRIBUTE = "key";
private static final String XML_MULTITAP_CHARACTERS_ATTRIBUTE = "characters";
private static final String XML_ALT_ATTRIBUTE = "altModifier";
private static final String XML_SHIFT_ATTRIBUTE = "shiftModifier";
private final String mPrefId;
private final String mName;
private final int mIconId;
private final String mDefaultDictionary;
private final HardKeyboardSequenceHandler mHardKeyboardTranslator;
private final HashSet<Character> mAdditionalIsLetterExceptions;
private final HashSet<Character> mSentenceSeparators;
private KeyboardExtension mExtensionLayout = null;
public ExternalAnyKeyboard(Context askContext,
Context context, int xmlLayoutResId, int xmlLandscapeResId,
String prefId, String name, int iconResId,
int qwertyTranslationId, String defaultDictionary,
String additionalIsLetterExceptions, String sentenceSeparators,
int mode) {
super(askContext, context, getKeyboardId(
askContext, xmlLayoutResId,
xmlLandscapeResId), mode);
mPrefId = prefId;
mName = name;
mIconId = iconResId;
mDefaultDictionary = defaultDictionary;
if (qwertyTranslationId != AddOn.INVALID_RES_ID) {
Log.d(TAG, "Creating qwerty mapping:" + qwertyTranslationId);
mHardKeyboardTranslator = createPhysicalTranslatorFromResourceId(
context, qwertyTranslationId);
} else {
mHardKeyboardTranslator = null;
}
mAdditionalIsLetterExceptions = new HashSet<Character>(
additionalIsLetterExceptions != null ? additionalIsLetterExceptions
.length() : 0);
if (additionalIsLetterExceptions != null) {
for (int i = 0; i < additionalIsLetterExceptions.length(); i++)
mAdditionalIsLetterExceptions.add(additionalIsLetterExceptions
.charAt(i));
}
mSentenceSeparators = new HashSet<Character>(
sentenceSeparators != null ? sentenceSeparators.length() : 0);
if (sentenceSeparators != null) {
for (int i = 0; i < sentenceSeparators.length(); i++)
mSentenceSeparators.add(sentenceSeparators.charAt(i));
}
setExtensionLayout(KeyboardExtensionFactory
.getCurrentKeyboardExtension(
askContext.getApplicationContext(),
KeyboardExtension.TYPE_EXTENSION));
}
protected void setExtensionLayout(KeyboardExtension extKbd) {
mExtensionLayout = extKbd;
}
public KeyboardExtension getExtensionLayout() {
return mExtensionLayout;
}
private HardKeyboardSequenceHandler createPhysicalTranslatorFromResourceId(
Context context, int qwertyTranslationId) {
HardKeyboardSequenceHandler translator = new HardKeyboardSequenceHandler();
XmlPullParser parser = context.getResources().getXml(
qwertyTranslationId);
final String TAG = "ASK Hard Translation Parser";
try {
int event;
boolean inTranslations = false;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
String tag = parser.getName();
if (event == XmlPullParser.START_TAG) {
if (XML_TRANSLATION_TAG.equals(tag)) {
inTranslations = true;
AttributeSet attrs = Xml.asAttributeSet(parser);
final String qwerty = attrs.getAttributeValue(null,
XML_QWERTY_ATTRIBUTE);
if (qwerty != null)
translator.addQwertyTranslation(qwerty);
} else if (inTranslations && XML_SEQUENCE_TAG.equals(tag)) {
AttributeSet attrs = Xml.asAttributeSet(parser);
final int[] keyCodes = getKeyCodesFromPhysicalSequence(attrs
.getAttributeValue(null, XML_KEYS_ATTRIBUTE));
final boolean isAlt = attrs.getAttributeBooleanValue(
null, XML_ALT_ATTRIBUTE, false);
final boolean isShift = attrs.getAttributeBooleanValue(
null, XML_SHIFT_ATTRIBUTE, false);
final String targetChar = attrs.getAttributeValue(null,
XML_TARGET_ATTRIBUTE);
final String targetCharCode = attrs.getAttributeValue(
null, XML_TARGET_CHAR_CODE_ATTRIBUTE);
final Integer target;
if (targetCharCode == null)
target = (int) targetChar.charAt(0);
else
target = Integer.valueOf(targetCharCode);
// asserting
if ((keyCodes == null) || (keyCodes.length == 0)
|| (target == null)) {
Log.e(TAG,
"Physical translator sequence does not include mandatory fields "
+ XML_KEYS_ATTRIBUTE + " or "
+ XML_TARGET_ATTRIBUTE);
} else {
if (!isAlt && !isShift) {
translator.addSequence(keyCodes, target.intValue());
// http://code.google.com/p/softkeyboard/issues/detail?id=734
translator.addShiftSequence(keyCodes, Character.toUpperCase(target.intValue()));
} else if (isAlt) {
translator.addAltSequence(keyCodes, target.intValue());
} else if (isShift) {
translator.addShiftSequence(keyCodes, target.intValue());
}
}
} else if (inTranslations && XML_MULTITAP_TAG.equals(tag)) {
AttributeSet attrs = Xml.asAttributeSet(parser);
final int[] keyCodes = getKeyCodesFromPhysicalSequence(attrs.getAttributeValue(null, XML_MULTITAP_KEY_ATTRIBUTE));
if (keyCodes.length != 1)
throw new ParseException("attribute " + XML_MULTITAP_KEY_ATTRIBUTE + " should contain exactly one key-code when used in " + XML_MULTITAP_TAG + " tag!", parser.getLineNumber());
final boolean isAlt = attrs.getAttributeBooleanValue(
null, XML_ALT_ATTRIBUTE, false);
final boolean isShift = attrs.getAttributeBooleanValue(
null, XML_SHIFT_ATTRIBUTE, false);
final String targetCharacters = attrs.getAttributeValue(null,
XML_MULTITAP_CHARACTERS_ATTRIBUTE);
if (TextUtils.isEmpty(targetCharacters) || targetCharacters.length() < 2)
throw new ParseException("attribute " + XML_MULTITAP_CHARACTERS_ATTRIBUTE + " should contain more than one character when used in " + XML_MULTITAP_TAG + " tag!", parser.getLineNumber());
for (int characterIndex = 0; characterIndex <= targetCharacters.length(); characterIndex++) {
int[] multiTapCodes = new int[characterIndex + 1];
for (int tapIndex = 0; tapIndex < multiTapCodes.length; tapIndex++) {
multiTapCodes[tapIndex] = keyCodes[0];
}
if (characterIndex < targetCharacters.length()) {
final int target = targetCharacters.charAt(characterIndex);
if (!isAlt && !isShift) {
translator.addSequence(multiTapCodes, target);
translator.addShiftSequence(multiTapCodes, Character.toUpperCase(target));
} else if (isAlt) {
translator.addAltSequence(keyCodes, target);
} else if (isShift) {
translator.addShiftSequence(keyCodes, target);
}
} else {
//and adding the rewind character
if (!isAlt && !isShift) {
translator.addSequence(multiTapCodes, KeyEventStateMachine.KEYCODE_FIRST_CHAR);
translator.addShiftSequence(multiTapCodes, KeyEventStateMachine.KEYCODE_FIRST_CHAR);
} else if (isAlt) {
translator.addAltSequence(keyCodes, KeyEventStateMachine.KEYCODE_FIRST_CHAR);
} else if (isShift) {
translator.addShiftSequence(keyCodes, KeyEventStateMachine.KEYCODE_FIRST_CHAR);
}
}
}
}
} else if (event == XmlPullParser.END_TAG) {
if (XML_TRANSLATION_TAG.equals(tag)) {
break;
}
}
}
} catch (Exception e) {
Log.e(TAG, "Parse error:" + e);
e.printStackTrace();
if (BuildConfig.DEBUG)
throw new RuntimeException("Failed to parse keyboard layout.", e);
}
return translator;
}
private int[] getKeyCodesFromPhysicalSequence(String keyCodesArray) {
String[] splitted = keyCodesArray.split(",");
int[] keyCodes = new int[splitted.length];
for (int i = 0; i < keyCodes.length; i++) {
try {
keyCodes[i] = Integer.parseInt(splitted[i]);// try parsing as an
// integer
} catch (final NumberFormatException nfe) {// no an integer
final String v = splitted[i];
try {
keyCodes[i] = android.view.KeyEvent.class.getField(v)
.getInt(null);// here comes the reflection. No
// bother of performance.
// First hit takes just 20 milliseconds, the next hits <2
// Milliseconds.
} catch (final Exception ex) {// crap :(
throw new RuntimeException(ex);// bum
}
}
}
return keyCodes;
}
@Override
public String getDefaultDictionaryLocale() {
return mDefaultDictionary;
}
@Override
public String getKeyboardPrefId() {
return mPrefId;
}
@Override
public int getKeyboardIconResId() {
return mIconId;
}
@Override
public String getKeyboardName() {
return mName;
}
private static int getKeyboardId(Context context, int portraitId,
int landscapeId) {
final boolean inPortraitMode = (context.getResources()
.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
if (inPortraitMode)
return portraitId;
else
return landscapeId;
}
// this class implements the HardKeyboardTranslator interface in an empty
// way, the physical keyboard is Latin...
public void translatePhysicalCharacter(HardKeyboardAction action,
AnySoftKeyboard ime) {
if (mHardKeyboardTranslator != null) {
final int translated;
if (action.isAltActive())
if (!mHardKeyboardTranslator.addSpecialKey(KeyCodes.ALT))
return;
if (action.isShiftActive())
if (!mHardKeyboardTranslator.addSpecialKey(KeyCodes.SHIFT))
return;
translated = mHardKeyboardTranslator.getCurrentCharacter(
action.getKeyCode(), ime);
if (translated != 0)
action.setNewKeyCode(translated);
}
}
@Override
public boolean isInnerWordLetter(char keyValue) {
return super.isInnerWordLetter(keyValue)
|| mAdditionalIsLetterExceptions.contains(keyValue);
}
@Override
public HashSet<Character> getSentenceSeparators() {
return mSentenceSeparators;
}
protected void setPopupKeyChars(Key aKey) {
if (aKey.popupResId > 0)
return;// if the keyboard XML already specified the popup, then no
// need to override
// filling popup res for external keyboards
// if ((aKey.popupCharacters != null) && (aKey.popupCharacters.length()
// > 0)){
if (aKey.popupCharacters != null) {
if (aKey.popupCharacters.length() > 0) {
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
}
return;
}
if ((aKey.codes != null) && (aKey.codes.length > 0)) {
switch ((char) aKey.codes[0]) {
case 'a':
aKey.popupCharacters = "\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u0105";// "àáâãäåæą";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'c':
aKey.popupCharacters = "\u00e7\u0107\u0109\u010d";// "çćĉč";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'd':
aKey.popupCharacters = "\u0111"; // "đ";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'e':
aKey.popupCharacters = "\u00e8\u00e9\u00ea\u00eb\u0119\u20ac\u0113";// "èéêëę€";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'g':
aKey.popupCharacters = "\u011d";// "ĝ";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'h':
aKey.popupCharacters = "\u0125";// "ĥ";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'i':
aKey.popupCharacters = "\u00ec\u00ed\u00ee\u00ef\u0142\u012B";// "ìíîïł";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'j':
aKey.popupCharacters = "\u0135";// "ĵ";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'l':
aKey.popupCharacters = "\u0142";// "ł";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'o':
aKey.popupCharacters = "\u00f2\u00f3\u00f4\u00f5\u00f6\u00f8\u0151\u0153\u014D";// "òóôõöøőœ";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 's':
aKey.popupCharacters = "\u00a7\u00df\u015b\u015d\u0161";// "§ßśŝš";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'u':
aKey.popupCharacters = "\u00f9\u00fa\u00fb\u00fc\u016d\u0171\u016B";// "ùúûüŭű";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'n':
aKey.popupCharacters = "\u00f1";// "ñ";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'y':
aKey.popupCharacters = "\u00fd\u00ff";// "ýÿ";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
case 'z':
aKey.popupCharacters = "\u017c\u017e\u017a";// "żžź";
aKey.popupResId = com.menny.android.anysoftkeyboard.R.xml.popup_one_row;
break;
default:
super.setPopupKeyChars(aKey);
}
}
}
}