Included PluginManager.java,

prepared for use in KP2A (KeyboardBridge)
Added ImeSwitcher.java for SecureSettings based switching
This commit is contained in:
Philipp Crocoll 2014-01-24 15:52:40 +01:00
parent b204433490
commit 9611622fb1
7 changed files with 464 additions and 45 deletions

View File

@ -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<ResolveInfo> 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();
}
}

View File

@ -4,12 +4,13 @@ import java.util.HashMap;
public class KeyboardDataBuilder {
private ArrayList<StringForTyping> availableFields = new ArrayList<StringForTyping>();
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()

View File

@ -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) {

View File

@ -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());

View File

@ -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 {

View File

@ -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<String, String> SOFTKEYBOARD_LANG_MAP = new HashMap<String, String>();
static {
SOFTKEYBOARD_LANG_MAP.put("dk", "da");
}
PluginManager(KP2AKeyboard ime) {
super();
mIME = ime;
}
private static Map<String, DictPluginSpec> mPluginDicts =
new HashMap<String, DictPluginSpec>();
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<ResolveInfo> 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<ResolveInfo> 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<Integer> ids = new ArrayList<Integer>();
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;
}
}

View File

@ -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++) {