diff --git a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/kbbridge/ImeSwitcher.java b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/kbbridge/ImeSwitcher.java new file mode 100644 index 00000000..13af1b2e --- /dev/null +++ b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/kbbridge/ImeSwitcher.java @@ -0,0 +1,90 @@ +package keepass2android.kbbridge; + +import java.util.List; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; +import android.inputmethodservice.InputMethodService; +import android.os.Bundle; +import android.util.Log; +import android.view.inputmethod.InputMethod; +import android.view.inputmethod.InputMethodManager; +import android.widget.Toast; + +public class ImeSwitcher { + private static final String PREVIOUS_KEYBOARD = "previous_keyboard"; + private static final String KP2A_SWITCHER = "KP2A_Switcher"; + private static final String Tag = "KP2A_SWITCHER"; + + public static void switchToPreviousKeyboard(Context ctx) + { + SharedPreferences prefs = ctx.getSharedPreferences(KP2A_SWITCHER, Context.MODE_PRIVATE); + switchToKeyboard(ctx, prefs.getString(PREVIOUS_KEYBOARD, null)); + } + + public static void switchToKeyboard(Context ctx, String newImeName) + { + if (newImeName == null) + { + showPicker(ctx); + return; + + } + Intent qi = new Intent("com.twofortyfouram.locale.intent.action.FIRE_SETTING"); + List pkgAppsList = ctx.getPackageManager().queryBroadcastReceivers(qi, 0); + boolean sentBroadcast = false; + for (ResolveInfo ri: pkgAppsList) + { + + if (ri.activityInfo.packageName.equals("com.intangibleobject.securesettings.plugin")) + { + + String currentIme = android.provider.Settings.Secure.getString( + ctx.getContentResolver(), + android.provider.Settings.Secure.DEFAULT_INPUT_METHOD); + + SharedPreferences prefs = ctx.getSharedPreferences(KP2A_SWITCHER, Context.MODE_PRIVATE); + Editor edit = prefs.edit(); + + edit.putString(PREVIOUS_KEYBOARD, currentIme); + edit.commit(); + + Intent i=new Intent("com.twofortyfouram.locale.intent.action.FIRE_SETTING"); + Bundle b = new Bundle(); + + b.putString("com.intangibleobject.securesettings.plugin.extra.BLURB", "Input Method/Switch IME"); + b.putString("com.intangibleobject.securesettings.plugin.extra.INPUT_METHOD", newImeName); + b.putString("com.intangibleobject.securesettings.plugin.extra.SETTING","default_input_method"); + i.putExtra("com.twofortyfouram.locale.intent.extra.BUNDLE", b); + ctx.sendBroadcast(i); + sentBroadcast = true; + break; + } + } + if (!sentBroadcast) + { + //report that switch failed: + try + { + Toast.makeText(ctx, "SecureSettings not found on system!", Toast.LENGTH_LONG).show(); + } + catch (Exception e) + { + Log.e(Tag, e.toString()); + } + + showPicker(ctx); + } + + } + + private static void showPicker(Context ctx) { + ((InputMethodManager) ctx.getSystemService(InputMethodService.INPUT_METHOD_SERVICE)) + .showInputMethodPicker(); + } +} diff --git a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/kbbridge/KeyboardDataBuilder.java b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/kbbridge/KeyboardDataBuilder.java index 32afc347..e2dee9f6 100644 --- a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/kbbridge/KeyboardDataBuilder.java +++ b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/kbbridge/KeyboardDataBuilder.java @@ -4,12 +4,13 @@ import java.util.HashMap; public class KeyboardDataBuilder { private ArrayList availableFields = new ArrayList(); - public void addPair(String displayName, String valueToType) + public void addString(String key, String displayName, String valueToType) { - StringForTyping pair = new StringForTyping(); - pair.displayName = displayName; - pair.value = valueToType; - availableFields.add(pair); + StringForTyping stringToType = new StringForTyping(); + stringToType.key = key; + stringToType.displayName = displayName; + stringToType.value = valueToType; + availableFields.add(stringToType); } public void commit() diff --git a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/BinaryDictionary.java b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/BinaryDictionary.java index 4784f0e0..c4598c5b 100644 --- a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/BinaryDictionary.java +++ b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/BinaryDictionary.java @@ -78,7 +78,20 @@ public class BinaryDictionary extends Dictionary { } mDicTypeId = dicTypeId; } - + + /** + * Create a dictionary from input streams + * @param context application context for reading resources + * @param streams the resource streams containing the raw binary dictionary + */ + public BinaryDictionary(Context context, InputStream[] streams, int dicTypeId) { + if (streams != null && streams.length > 0) { + loadDictionary(context, streams); + } + mDicTypeId = dicTypeId; + } + + /** * Create a dictionary from a byte buffer. This is used for testing. * @param context application context for reading resources @@ -115,28 +128,13 @@ public class BinaryDictionary extends Dictionary { InputStream[] is = null; try { // merging separated dictionary into one if dictionary is separated - int total = 0; is = new InputStream[resId.length]; for (int i = 0; i < resId.length; i++) { is[i] = context.getResources().openRawResource(resId[i]); - total += is[i].available(); } - - mNativeDictDirectBuffer = - ByteBuffer.allocateDirect(total).order(ByteOrder.nativeOrder()); - int got = 0; - for (int i = 0; i < resId.length; i++) { - got += Channels.newChannel(is[i]).read(mNativeDictDirectBuffer); - } - if (got != total) { - Log.e(TAG, "Read " + got + " bytes, expected " + total); - } else { - mNativeDict = openNative(mNativeDictDirectBuffer, - TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER); - mDictLength = total; - } - } catch (IOException e) { - Log.w(TAG, "No available memory for binary dictionary"); + loadDictionary(context, is); + + } finally { try { if (is != null) { @@ -151,7 +149,48 @@ public class BinaryDictionary extends Dictionary { } - @Override + private void loadDictionary(Context context, InputStream[] is) + { + try + { + int total = 0; + for (int i = 0; i < is.length; i++) + total += is[i].available(); + + mNativeDictDirectBuffer = + ByteBuffer.allocateDirect(total).order(ByteOrder.nativeOrder()); + int got = 0; + for (int i = 0; i < is.length; i++) { + got += Channels.newChannel(is[i]).read(mNativeDictDirectBuffer); + } + if (got != total) { + Log.e(TAG, "Read " + got + " bytes, expected " + total); + } else { + mNativeDict = openNative(mNativeDictDirectBuffer, + TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER); + mDictLength = total; + } + + } + catch (IOException e) { + Log.w(TAG, "No available memory for binary dictionary"); + } catch (UnsatisfiedLinkError e) { + Log.w(TAG, "Failed to load native dictionary", e); + } finally { + try { + if (is != null) { + for (int i = 0; i < is.length; i++) { + is[i].close(); + } + } + } catch (IOException e) { + Log.w(TAG, "Failed to close input stream"); + } + } + + } + + @Override public void getBigrams(final WordComposer codes, final CharSequence previousWord, final WordCallback callback, int[] nextLettersFrequencies) { diff --git a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/InputLanguageSelection.java b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/InputLanguageSelection.java index 8de188a7..b2927565 100644 --- a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/InputLanguageSelection.java +++ b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/InputLanguageSelection.java @@ -120,6 +120,15 @@ public class InputLanguageSelection extends PreferenceActivity { if (bd.getSize() > Suggest.LARGE_DICTIONARY_THRESHOLD / 4) { haveDictionary = true; } + else + { + BinaryDictionary plug = PluginManager.getDictionary(getApplicationContext(), locale.getLanguage()); + if (plug != null) { + bd.close(); + bd = plug; + haveDictionary = true; + } + } bd.close(); conf.locale = saveLocale; res.updateConfiguration(conf, res.getDisplayMetrics()); diff --git a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/KP2AKeyboard.java b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/KP2AKeyboard.java index f4cfce34..c88745e4 100644 --- a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/KP2AKeyboard.java +++ b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/KP2AKeyboard.java @@ -452,6 +452,8 @@ public class KP2AKeyboard extends InputMethodService }*/ unregisterReceiver(mReceiver); + unregisterReceiver(mClearKeyboardReceiver); + LatinImeLogger.commit(); LatinImeLogger.onDestroy(); super.onDestroy(); @@ -571,25 +573,29 @@ public class KP2AKeyboard extends InputMethodService int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; - if (keepass2android.kbbridge.KeyboardData.hasData() != - mHadKp2aData) - { - if (keepass2android.kbbridge.KeyboardData.hasData()) - { - //new data available -> show kp2a keyboard: - mShowKp2aKeyboard = true; - } - else - { - //data no longer available. hide kp2a keyboard: - mShowKp2aKeyboard = false; - } + if (!keepass2android.kbbridge.KeyboardData.hasData()) + { + //data no longer available. hide kp2a keyboard: + mShowKp2aKeyboard = false; + mHadKp2aData = false; + } + else + { + + if (!mHadKp2aData) + { + if (keepass2android.kbbridge.KeyboardData.hasData()) + { + //new data available -> show kp2a keyboard: + mShowKp2aKeyboard = true; + } + } mHadKp2aData = keepass2android.kbbridge.KeyboardData.hasData(); } Log.d("KP2AK", "show: " + mShowKp2aKeyboard); - if (mShowKp2aKeyboard) + if ((mShowKp2aKeyboard) && (mKp2aEnableSimpleKeyboard)) { mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_KP2A, attribute.imeOptions); mPredictionOn = false; @@ -2204,7 +2210,7 @@ public class KP2AKeyboard extends InputMethodService return mWord.isFirstCharCapitalized(); } - private void toggleLanguage(boolean reset, boolean next) { + void toggleLanguage(boolean reset, boolean next) { if (reset) { mLanguageSwitcher.reset(); } else { diff --git a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/PluginManager.java b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/PluginManager.java new file mode 100644 index 00000000..f24328f8 --- /dev/null +++ b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/PluginManager.java @@ -0,0 +1,259 @@ +package keepass2android.softkeyboard; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.xmlpull.v1.XmlPullParserException; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.util.Log; + +public class PluginManager extends BroadcastReceiver { + private static String TAG = "PCKeyboard"; + private static String HK_INTENT_DICT = "org.pocketworkstation.DICT"; + private static String SOFTKEYBOARD_INTENT_DICT = "com.menny.android.anysoftkeyboard.DICTIONARY"; + private KP2AKeyboard mIME; + + // Apparently anysoftkeyboard doesn't use ISO 639-1 language codes for its locales? + // Add exceptions as needed. + private static Map SOFTKEYBOARD_LANG_MAP = new HashMap(); + static { + SOFTKEYBOARD_LANG_MAP.put("dk", "da"); + } + + PluginManager(KP2AKeyboard ime) { + super(); + mIME = ime; + } + + private static Map mPluginDicts = + new HashMap(); + + static interface DictPluginSpec { + BinaryDictionary getDict(Context context); + } + + static private abstract class DictPluginSpecBase + implements DictPluginSpec { + String mPackageName; + + Resources getResources(Context context) { + PackageManager packageManager = context.getPackageManager(); + Resources res = null; + try { + ApplicationInfo appInfo = packageManager.getApplicationInfo(mPackageName, 0); + res = packageManager.getResourcesForApplication(appInfo); + } catch (NameNotFoundException e) { + Log.i(TAG, "couldn't get resources"); + } + return res; + } + + abstract InputStream[] getStreams(Resources res); + + public BinaryDictionary getDict(Context context) { + Resources res = getResources(context); + if (res == null) return null; + + InputStream[] dicts = getStreams(res); + if (dicts == null) return null; + BinaryDictionary dict = new BinaryDictionary( + context, dicts, Suggest.DIC_MAIN); + if (dict.getSize() == 0) return null; + //Log.i(TAG, "dict size=" + dict.getSize()); + return dict; + } + } + + static private class DictPluginSpecHK + extends DictPluginSpecBase { + + int[] mRawIds; + + public DictPluginSpecHK(String pkg, int[] ids) { + mPackageName = pkg; + mRawIds = ids; + } + + @Override + InputStream[] getStreams(Resources res) { + if (mRawIds == null || mRawIds.length == 0) return null; + InputStream[] streams = new InputStream[mRawIds.length]; + for (int i = 0; i < mRawIds.length; ++i) { + streams[i] = res.openRawResource(mRawIds[i]); + } + return streams; + } + } + + static private class DictPluginSpecSoftKeyboard + extends DictPluginSpecBase { + + String mAssetName; + + public DictPluginSpecSoftKeyboard(String pkg, String asset) { + mPackageName = pkg; + mAssetName = asset; + } + + @Override + InputStream[] getStreams(Resources res) { + if (mAssetName == null) return null; + try { + InputStream in = res.getAssets().open(mAssetName); + return new InputStream[] {in}; + } catch (IOException e) { + Log.e(TAG, "Dictionary asset loading failure"); + return null; + } + } + } + + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, "Package information changed, updating dictionaries."); + getPluginDictionaries(context); + Log.i(TAG, "Finished updating dictionaries."); + mIME.toggleLanguage(true, true); + } + + static void getSoftKeyboardDictionaries(PackageManager packageManager) { + Intent dictIntent = new Intent(SOFTKEYBOARD_INTENT_DICT); + List dictPacks = packageManager.queryBroadcastReceivers( + dictIntent, PackageManager.GET_RECEIVERS); + for (ResolveInfo ri : dictPacks) { + ApplicationInfo appInfo = ri.activityInfo.applicationInfo; + String pkgName = appInfo.packageName; + boolean success = false; + try { + Resources res = packageManager.getResourcesForApplication(appInfo); + Log.i("KP2AK", "Found dictionary plugin package: " + pkgName); + int dictId = res.getIdentifier("dictionaries", "xml", pkgName); + if (dictId == 0) continue; + XmlResourceParser xrp = res.getXml(dictId); + + String assetName = null; + String lang = null; + try { + int current = xrp.getEventType(); + while (current != XmlResourceParser.END_DOCUMENT) { + if (current == XmlResourceParser.START_TAG) { + String tag = xrp.getName(); + if (tag != null) { + if (tag.equals("Dictionary")) { + lang = xrp.getAttributeValue(null, "locale"); + String convLang = SOFTKEYBOARD_LANG_MAP.get(lang); + if (convLang != null) lang = convLang; + String type = xrp.getAttributeValue(null, "type"); + if (type == null || type.equals("raw") || type.equals("binary")) { + assetName = xrp.getAttributeValue(null, "dictionaryAssertName"); // sic + } else { + Log.w(TAG, "Unsupported AnySoftKeyboard dict type " + type); + } + //Log.i(TAG, "asset=" + assetName + " lang=" + lang); + } + } + } + xrp.next(); + current = xrp.getEventType(); + } + } catch (XmlPullParserException e) { + Log.e(TAG, "Dictionary XML parsing failure"); + } catch (IOException e) { + Log.e(TAG, "Dictionary XML IOException"); + } + + if (assetName == null || lang == null) continue; + DictPluginSpec spec = new DictPluginSpecSoftKeyboard(pkgName, assetName); + mPluginDicts.put(lang, spec); + Log.i("KP2AK", "Found plugin dictionary: lang=" + lang + ", pkg=" + pkgName); + success = true; + } catch (NameNotFoundException e) { + Log.i("KP2AK", "bad"); + } finally { + if (!success) { + Log.i("KP2AK", "failed to load plugin dictionary spec from " + pkgName); + } + } + } + } + + static void getHKDictionaries(PackageManager packageManager) { + Intent dictIntent = new Intent(HK_INTENT_DICT); + List dictPacks = packageManager.queryIntentActivities(dictIntent, 0); + for (ResolveInfo ri : dictPacks) { + ApplicationInfo appInfo = ri.activityInfo.applicationInfo; + String pkgName = appInfo.packageName; + boolean success = false; + try { + Resources res = packageManager.getResourcesForApplication(appInfo); + Log.i("KP2AK", "Found dictionary plugin package: " + pkgName); + int langId = res.getIdentifier("dict_language", "string", pkgName); + if (langId == 0) continue; + String lang = res.getString(langId); + int[] rawIds = null; + + // Try single-file version first + int rawId = res.getIdentifier("main", "raw", pkgName); + if (rawId != 0) { + rawIds = new int[] { rawId }; + } else { + // try multi-part version + int parts = 0; + List ids = new ArrayList(); + while (true) { + int id = res.getIdentifier("main" + parts, "raw", pkgName); + if (id == 0) break; + ids.add(id); + ++parts; + } + if (parts == 0) continue; // no parts found + rawIds = new int[parts]; + for (int i = 0; i < parts; ++i) rawIds[i] = ids.get(i); + } + DictPluginSpec spec = new DictPluginSpecHK(pkgName, rawIds); + mPluginDicts.put(lang, spec); + Log.i("KP2AK", "Found plugin dictionary: lang=" + lang + ", pkg=" + pkgName); + success = true; + } catch (NameNotFoundException e) { + Log.i("KP2AK", "bad"); + } finally { + if (!success) { + Log.i("KP2AK", "failed to load plugin dictionary spec from " + pkgName); + } + } + } + } + + static void getPluginDictionaries(Context context) { + mPluginDicts.clear(); + PackageManager packageManager = context.getPackageManager(); + getSoftKeyboardDictionaries(packageManager); + getHKDictionaries(packageManager); + } + + static BinaryDictionary getDictionary(Context context, String lang) { + Log.i("KP2AK", "Looking for plugin dictionary for lang=" + lang); + DictPluginSpec spec = mPluginDicts.get(lang); + if (spec == null) spec = mPluginDicts.get(lang.substring(0, 2)); + if (spec == null) { + Log.i("KP2AK", "No plugin found."); + return null; + } + BinaryDictionary dict = spec.getDict(context); + Log.i("KP2AK", "Found plugin dictionary for " + lang + (dict == null ? " is null" : ", size=" + dict.getSize())); + return dict; + } +} diff --git a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/Suggest.java b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/Suggest.java index 7b6c3447..8b6883e8 100644 --- a/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/Suggest.java +++ b/src/java/KP2ASoftKeyboard2/java/src/keepass2android/softkeyboard/Suggest.java @@ -26,6 +26,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; /** * This class loads a dictionary and provides a list of suggestions for a given sequence of @@ -105,14 +106,28 @@ public class Suggest implements Dictionary.WordCallback { public Suggest(Context context, int[] dictionaryResId) { mMainDict = new BinaryDictionary(context, dictionaryResId, DIC_MAIN); - Log.d("KP2AK", "main size: " + mMainDict.getSize()+ " " +dictionaryResId[0]); + + + Locale locale = context.getResources().getConfiguration().locale; + Log.d("KP2AK", "locale: " + locale.getISO3Language()); + + if (!hasMainDictionary() + || (!"eng".equals(locale.getISO3Language()))) + { + Log.d("KP2AK", "try get plug"); + BinaryDictionary plug = PluginManager.getDictionary(context, locale.getLanguage()); + if (plug != null) { + Log.d("KP2AK", "ok"); + mMainDict.close(); + mMainDict = plug; + } + } + + initPool(); } - public Suggest(Context context, ByteBuffer byteBuffer) { - mMainDict = new BinaryDictionary(context, byteBuffer, DIC_MAIN); - initPool(); - } + private void initPool() { for (int i = 0; i < mPrefMaxSuggestions; i++) {