aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java15
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java122
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java191
-rw-r--r--java/src/com/android/inputmethod/latin/CandidateView.java742
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsDictionary.java16
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java18
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java7
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java279
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java51
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java26
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java6
-rw-r--r--java/src/com/android/inputmethod/latin/UserDictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java60
-rw-r--r--java/src/com/android/inputmethod/latin/WhitelistDictionary.java5
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java174
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java (renamed from java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java)21
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java78
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java115
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java94
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java43
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java41
23 files changed, 1358 insertions, 759 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 9748d6006..6a6a0a4ee 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -156,10 +156,11 @@ public class BinaryDictionary extends Dictionary {
}
}
+ // proximityInfo may not be null.
@Override
- public void getWords(final WordComposer codes, final WordCallback callback) {
- final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(),
- mOutputChars, mScores);
+ public void getWords(final WordComposer codes, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
+ final int count = getSuggestions(codes, proximityInfo, mOutputChars, mScores);
for (int j = 0; j < count; ++j) {
if (mScores[j] < 1) break;
@@ -179,8 +180,9 @@ public class BinaryDictionary extends Dictionary {
return mNativeDict != 0;
}
- /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
- char[] outputChars, int[] scores) {
+ // proximityInfo may not be null.
+ /* package for test */ int getSuggestions(final WordComposer codes,
+ final ProximityInfo proximityInfo, char[] outputChars, int[] scores) {
if (!isValidDictionary()) return -1;
final int codesSize = codes.size();
@@ -196,9 +198,8 @@ public class BinaryDictionary extends Dictionary {
Arrays.fill(outputChars, (char) 0);
Arrays.fill(scores, 0);
- final int proximityInfo = keyboard == null ? 0 : keyboard.getProximityInfo();
return getSuggestionsNative(
- mNativeDict, proximityInfo,
+ mNativeDict, proximityInfo.getNativeProximityInfo(),
codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
mFlags, outputChars, scores);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 41b577cf3..3da670e2e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -20,16 +20,18 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
+import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
+import android.util.Log;
-import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -38,89 +40,83 @@ import java.util.Locale;
* file from the dictionary provider
*/
public class BinaryDictionaryFileDumper {
+ private static final String TAG = BinaryDictionaryFileDumper.class.getSimpleName();
+
/**
* The size of the temporary buffer to copy files.
*/
static final int FILE_READ_BUFFER_SIZE = 1024;
+ private static final String DICTIONARY_PROJECTION[] = { "id" };
+
// Prevents this class to be accidentally instantiated.
private BinaryDictionaryFileDumper() {
}
/**
- * Generates a file name that matches the locale passed as an argument.
- * The file name is basically the result of the .toString() method, except we replace
- * any @File.separator with an underscore to avoid generating a file name that may not
- * be created.
- * @param locale the locale for which to get the file name
- * @param context the context to use for getting the directory
- * @return the name of the file to be created
+ * Return for a given locale or dictionary id the provider URI to get the dictionary.
*/
- private static String getCacheFileNameForLocale(Locale locale, Context context) {
- // The following assumes two things :
- // 1. That File.separator is not the same character as "_"
- // I don't think any android system will ever use "_" as a path separator
- // 2. That no two locales differ by only a File.separator versus a "_"
- // Since "_" can't be part of locale components this should be safe.
- // Examples:
- // en -> en
- // en_US_POSIX -> en_US_POSIX
- // en__foo/bar -> en__foo_bar
- final String[] separator = { File.separator };
- final String[] empty = { "_" };
- final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty);
- return context.getFilesDir() + File.separator + basename;
+ private static Uri getProviderUri(String path) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath(
+ path).build();
}
/**
- * Return for a given locale the provider URI to query to get the dictionary.
+ * Queries a content provider for the list of dictionaries for a specific locale
+ * available to copy into Latin IME.
*/
- public static Uri getProviderUri(Locale locale) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
- .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath(
- locale.toString()).build();
+ private static List<String> getDictIdList(final Locale locale, final Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ final Uri dictionaryPackUri = getProviderUri(locale.toString());
+
+ final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null);
+ if (null == c) return Collections.<String>emptyList();
+ if (c.getCount() <= 0 || !c.moveToFirst()) {
+ c.close();
+ return Collections.<String>emptyList();
+ }
+
+ final List<String> list = new ArrayList<String>();
+ do {
+ final String id = c.getString(0);
+ if (TextUtils.isEmpty(id)) continue;
+ list.add(id);
+ } while (c.moveToNext());
+ c.close();
+ return list;
}
/**
- * Queries a content provider for dictionary data for some locale and returns the file addresses
+ * Queries a content provider for dictionary data for some locale and cache the returned files
*
- * This will query a content provider for dictionary data for a given locale, and return
- * the addresses of a file set the members of which are suitable to be mmap'ed. It will copy
- * them to local storage if needed.
- * It should also check the dictionary versions to avoid unnecessary copies but this is
- * still in TODO state.
- * This will make the data from the content provider the cached dictionary for this locale,
- * overwriting any previous cached data.
+ * This will query a content provider for dictionary data for a given locale, and copy the
+ * files locally so that they can be mmap'ed. This may overwrite previously cached dictionaries
+ * with newer versions if a newer version is made available by the content provider.
* @returns the addresses of the files, or null if no data could be obtained.
* @throw FileNotFoundException if the provider returns non-existent data.
* @throw IOException if the provider-returned data could not be read.
*/
- public static List<AssetFileAddress> getDictSetFromContentProvider(Locale locale,
- Context context) throws FileNotFoundException, IOException {
- // TODO: check whether the dictionary is the same or not and if it is, return the cached
- // file.
- // TODO: This should be able to read a number of files from the dictionary pack, copy
- // them all and return them.
+ public static List<AssetFileAddress> cacheDictionariesFromContentProvider(final Locale locale,
+ final Context context) throws FileNotFoundException, IOException {
final ContentResolver resolver = context.getContentResolver();
- final Uri dictionaryPackUri = getProviderUri(locale);
- final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(dictionaryPackUri, "r");
- if (null == afd) return null;
- final String fileName =
- copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context));
- afd.close();
- return Arrays.asList(AssetFileAddress.makeFromFileName(fileName));
- }
-
- /**
- * Accepts a file as dictionary data for some locale and returns the name of a file.
- *
- * This will make the data in the input file the cached dictionary for this locale, overwriting
- * any previous cached data.
- */
- public static String getDictionaryFileFromFile(String fileName, Locale locale,
- Context context) throws FileNotFoundException, IOException {
- return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale,
- context));
+ final List<String> idList = getDictIdList(locale, context);
+ final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
+ for (String id : idList) {
+ final Uri wordListUri = getProviderUri(id);
+ final AssetFileDescriptor afd =
+ resolver.openAssetFileDescriptor(wordListUri, "r");
+ if (null == afd) continue;
+ final String fileName = copyFileTo(afd.createInputStream(),
+ BinaryDictionaryGetter.getCacheFileName(id, locale, context));
+ afd.close();
+ if (0 >= resolver.delete(wordListUri, null, null)) {
+ // I'd rather not print the word list ID to the log here out of security concerns
+ Log.e(TAG, "Could not have the dictionary pack delete a word list");
+ }
+ fileAddressList.add(AssetFileAddress.makeFromFileName(fileName));
+ }
+ return fileAddressList;
}
/**
@@ -135,7 +131,9 @@ public class BinaryDictionaryFileDumper {
final Locale savedLocale = Utils.setSystemLocale(res, locale);
final InputStream stream = res.openRawResource(resource);
Utils.setSystemLocale(res, savedLocale);
- return copyFileTo(stream, getCacheFileNameForLocale(locale, context));
+ return copyFileTo(stream,
+ BinaryDictionaryGetter.getCacheFileName(Integer.toString(resource),
+ locale, context));
}
/**
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 989a0e9a0..360cf21ca 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -17,13 +17,17 @@
package com.android.inputmethod.latin;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.util.Log;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
+import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -37,10 +41,105 @@ class BinaryDictionaryGetter {
*/
private static final String TAG = BinaryDictionaryGetter.class.getSimpleName();
+ /**
+ * Name of the common preferences name to know which word list are on and which are off.
+ */
+ private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
+
// Prevents this from being instantiated
private BinaryDictionaryGetter() {}
/**
+ * Returns whether we may want to use this character as part of a file name.
+ *
+ * This basically only accepts ascii letters and numbers, and rejects everything else.
+ */
+ private static boolean isFileNameCharacter(int codePoint) {
+ if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit
+ if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase
+ if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase
+ return codePoint == '_'; // Underscore
+ }
+
+ /**
+ * Escapes a string for any characters that may be suspicious for a file or directory name.
+ *
+ * Concretely this does a sort of URL-encoding except it will encode everything that's not
+ * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which
+ * we cannot allow here)
+ */
+ // TODO: create a unit test for this method
+ private static String replaceFileNameDangerousCharacters(final String name) {
+ // This assumes '%' is fully available as a non-separator, normal
+ // character in a file name. This is probably true for all file systems.
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < name.length(); ++i) {
+ final int codePoint = name.codePointAt(i);
+ if (isFileNameCharacter(codePoint)) {
+ sb.appendCodePoint(codePoint);
+ } else {
+ // 6 digits - unicode is limited to 21 bits
+ sb.append(String.format((Locale)null, "%%%1$06x", codePoint));
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Reverse escaping done by replaceFileNameDangerousCharacters.
+ */
+ private static String getWordListIdFromFileName(final String fname) {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < fname.length(); ++i) {
+ final int codePoint = fname.codePointAt(i);
+ if ('%' != codePoint) {
+ sb.appendCodePoint(codePoint);
+ } else {
+ final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16);
+ i += 6;
+ sb.appendCodePoint(encodedCodePoint);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Find out the cache directory associated with a specific locale.
+ */
+ private static String getCacheDirectoryForLocale(Locale locale, Context context) {
+ final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toString());
+ final String absoluteDirectoryName = context.getFilesDir() + File.separator
+ + relativeDirectoryName;
+ final File directory = new File(absoluteDirectoryName);
+ if (!directory.exists()) {
+ if (!directory.mkdirs()) {
+ Log.e(TAG, "Could not create the directory for locale" + locale);
+ }
+ }
+ return absoluteDirectoryName;
+ }
+
+ /**
+ * Generates a file name for the id and locale passed as an argument.
+ *
+ * In the current implementation the file name returned will always be unique for
+ * any id/locale pair, but please do not expect that the id can be the same for
+ * different dictionaries with different locales. An id should be unique for any
+ * dictionary.
+ * The file name is pretty much an URL-encoded version of the id inside a directory
+ * named like the locale, except it will also escape characters that look dangerous
+ * to some file systems.
+ * @param id the id of the dictionary for which to get a file name
+ * @param locale the locale for which to get the file name
+ * @param context the context to use for getting the directory
+ * @return the name of the file to be created
+ */
+ public static String getCacheFileName(String id, Locale locale, Context context) {
+ final String fileName = replaceFileNameDangerousCharacters(id);
+ return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
+ }
+
+ /**
* Returns a file address from a resource, or null if it cannot be opened.
*/
private static AssetFileAddress loadFallbackResource(final Context context,
@@ -60,12 +159,51 @@ class BinaryDictionaryGetter {
}
/**
+ * Returns the list of cached files for a specific locale.
+ *
+ * @param locale the locale to find the dictionary files for.
+ * @param context the context on which to open the files upon.
+ * @return a list of binary dictionary files, which may be null but may not be empty.
+ */
+ private static List<AssetFileAddress> getCachedDictionaryList(final Locale locale,
+ final Context context) {
+ final String directoryName = getCacheDirectoryForLocale(locale, context);
+ final File[] cacheFiles = new File(directoryName).listFiles();
+ // TODO: Never return null. Fallback on the built-in dictionary, and if that's
+ // not present or disabled, then return an empty list.
+ if (null == cacheFiles) return null;
+
+ final SharedPreferences dictPackSettings;
+ try {
+ final String dictPackName = context.getString(R.string.dictionary_pack_package_name);
+ final Context dictPackContext = context.createPackageContext(dictPackName, 0);
+ dictPackSettings = dictPackContext.getSharedPreferences(COMMON_PREFERENCES_NAME,
+ Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS);
+ } catch (NameNotFoundException e) {
+ // The dictionary pack is not installed...
+ // TODO: fallback on the built-in dict, see the TODO above
+ Log.e(TAG, "Could not find a dictionary pack");
+ return null;
+ }
+
+ final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
+ for (File f : cacheFiles) {
+ final String wordListId = getWordListIdFromFileName(f.getName());
+ final boolean isActive = dictPackSettings.getBoolean(wordListId, true);
+ if (!isActive) continue;
+ if (f.canRead()) {
+ fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
+ } else {
+ Log.e(TAG, "Found a cached dictionary file but cannot read it");
+ }
+ }
+ return fileList.size() > 0 ? fileList : null;
+ }
+
+ /**
* Returns a list of file addresses for a given locale, trying relevant methods in order.
*
* Tries to get binary dictionaries from various sources, in order:
- * - Uses a private method of getting a private dictionaries, as implemented by the
- * PrivateBinaryDictionaryGetter class.
- * If that fails:
* - Uses a content provider to get a public dictionary set, as per the protocol described
* in BinaryDictionaryFileDumper.
* If that fails:
@@ -74,33 +212,28 @@ class BinaryDictionaryGetter {
* - Returns null.
* @return The address of a valid file, or null.
*/
- public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context,
- int fallbackResId) {
- // Try first to query a private package signed the same way for private files.
- final List<AssetFileAddress> privateFiles =
- PrivateBinaryDictionaryGetter.getDictionaryFiles(locale, context);
- if (null != privateFiles) {
- return privateFiles;
- } else {
- try {
- // If that was no-go, try to find a publicly exported dictionary.
- List<AssetFileAddress> listFromContentProvider =
- BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context);
- if (null != listFromContentProvider) {
- return listFromContentProvider;
- }
- // If the list is null, fall through and return the fallback
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Unable to create dictionary file from provider for locale "
- + locale.toString() + ": falling back to internal dictionary");
- } catch (IOException e) {
- Log.e(TAG, "Unable to read source data for locale "
- + locale.toString() + ": falling back to internal dictionary");
+ public static List<AssetFileAddress> getDictionaryFiles(final Locale locale,
+ final Context context, final int fallbackResId) {
+ try {
+ // cacheDictionariesFromContentProvider returns the list of files it copied to local
+ // storage, but we don't really care about what was copied NOW: what we want is the
+ // list of everything we ever cached, so we ignore the return value.
+ BinaryDictionaryFileDumper.cacheDictionariesFromContentProvider(locale, context);
+ List<AssetFileAddress> cachedDictionaryList = getCachedDictionaryList(locale, context);
+ if (null != cachedDictionaryList) {
+ return cachedDictionaryList;
}
- final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId,
- locale);
- if (null == fallbackAsset) return null;
- return Arrays.asList(fallbackAsset);
+ // If the list is null, fall through and return the fallback
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Unable to create dictionary file from provider for locale "
+ + locale.toString() + ": falling back to internal dictionary");
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read source data for locale "
+ + locale.toString() + ": falling back to internal dictionary");
}
+ final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId,
+ locale);
+ if (null == fallbackAsset) return null;
+ return Arrays.asList(fallbackAsset);
}
}
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 4baf52e52..b5b59ac12 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -38,6 +38,7 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
@@ -50,15 +51,12 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
import java.util.List;
-public class CandidateView extends LinearLayout implements OnClickListener {
-
+public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener {
public interface Listener {
public boolean addWordToDictionary(String word);
public void pickSuggestionManually(int index, CharSequence word);
}
- private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
- private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
private static final int MAX_SUGGESTIONS = 18;
private static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -67,11 +65,6 @@ public class CandidateView extends LinearLayout implements OnClickListener {
private static final boolean DBG = LatinImeLogger.sDBG;
private final ViewGroup mCandidatesStrip;
- private final int mCandidateCountInStrip;
- private static final int DEFAULT_CANDIDATE_COUNT_IN_STRIP = 3;
- private final ViewGroup mCandidatesPaneControl;
- private final TextView mExpandCandidatesPane;
- private final TextView mCloseCandidatesPane;
private ViewGroup mCandidatesPane;
private ViewGroup mCandidatesPaneContainer;
private View mKeyboardView;
@@ -80,17 +73,6 @@ public class CandidateView extends LinearLayout implements OnClickListener {
private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
private final ArrayList<View> mDividers = new ArrayList<View>();
- private final int mCandidateStripHeight;
- private final CharacterStyle mInvertedForegroundColorSpan;
- private final CharacterStyle mInvertedBackgroundColorSpan;
- private final int mAutoCorrectHighlight;
- private static final int AUTO_CORRECT_BOLD = 0x01;
- private static final int AUTO_CORRECT_UNDERLINE = 0x02;
- private static final int AUTO_CORRECT_INVERT = 0x04;
- private final int mColorTypedWord;
- private final int mColorAutoCorrect;
- private final int mColorSuggestedCandidate;
-
private final PopupWindow mPreviewPopup;
private final TextView mPreviewText;
@@ -102,9 +84,9 @@ public class CandidateView extends LinearLayout implements OnClickListener {
private boolean mShowingAutoCorrectionInverted;
private boolean mShowingAddToDictionary;
- private final CandidateViewLayoutParams mParams;
- private static final int PUNCTUATIONS_IN_STRIP = 6;
- private static final float MIN_TEXT_XSCALE = 0.75f;
+ private final SuggestionsStripParams mStripParams;
+ private final SuggestionsPaneParams mPaneParams;
+ private static final float MIN_TEXT_XSCALE = 0.70f;
private final UiHandler mHandler = new UiHandler(this);
@@ -157,118 +139,358 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
}
- private static class CandidateViewLayoutParams {
- public final TextPaint mPaint;
+ private static class CandidateViewParams {
public final int mPadding;
public final int mDividerWidth;
public final int mDividerHeight;
- public final int mControlWidth;
- private final int mAutoCorrectHighlight;
+ public final int mCandidateStripHeight;
- public final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+ protected final List<TextView> mWords;
+ protected final List<View> mDividers;
+ protected final List<TextView> mInfos;
- public int mCountInStrip;
- // True if the mCountInStrip suggestions can fit in suggestion strip in equally divided
- // width without squeezing the text.
- public boolean mCanUseFixedWidthColumns;
- public int mMaxWidth;
- public int mAvailableWidthForWords;
- public int mConstantWidthForPaddings;
- public int mVariableWidthForWords;
- public float mScaleX;
+ protected CandidateViewParams(List<TextView> words, List<View> dividers,
+ List<TextView> infos) {
+ mWords = words;
+ mDividers = dividers;
+ mInfos = infos;
- public CandidateViewLayoutParams(Resources res, TextView word, View divider, View control,
- int autoCorrectHighlight) {
- mPaint = new TextPaint();
- final float textSize = res.getDimension(R.dimen.candidate_text_size);
- mPaint.setTextSize(textSize);
+ final TextView word = words.get(0);
+ final View divider = dividers.get(0);
mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
divider.measure(WRAP_CONTENT, MATCH_PARENT);
mDividerWidth = divider.getMeasuredWidth();
mDividerHeight = divider.getMeasuredHeight();
- mControlWidth = control.getMeasuredWidth();
- mAutoCorrectHighlight = autoCorrectHighlight;
+
+ final Resources res = word.getResources();
+ mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height);
}
+ }
- public void layoutStrip(SuggestedWords suggestions, int maxWidth, int maxCount) {
- final int size = suggestions.size();
- if (size == 0) return;
- setupTexts(suggestions, size, mAutoCorrectHighlight);
- mCountInStrip = Math.min(maxCount, size);
- mScaleX = 1.0f;
+ private static class SuggestionsPaneParams extends CandidateViewParams {
+ public SuggestionsPaneParams(List<TextView> words, List<View> dividers,
+ List<TextView> infos) {
+ super(words, dividers, infos);
+ }
+
+ public int layout(SuggestedWords suggestions, ViewGroup paneView, int from, int textColor,
+ int paneWidth) {
+ final int count = Math.min(mWords.size(), suggestions.size());
+ View centeringFrom = null, lastView = null;
+ int x = 0, y = 0;
+ for (int index = from; index < count; index++) {
+ final int pos = index;
+ final TextView word = mWords.get(pos);
+ final View divider = mDividers.get(pos);
+ final TextPaint paint = word.getPaint();
+ word.setTextColor(textColor);
+ final CharSequence styled = suggestions.getWord(pos);
+
+ final TextView info;
+ if (DBG) {
+ final CharSequence debugInfo = getDebugInfo(suggestions, index);
+ if (debugInfo != null) {
+ info = mInfos.get(index);
+ info.setText(debugInfo);
+ } else {
+ info = null;
+ }
+ } else {
+ info = null;
+ }
- do {
- mMaxWidth = maxWidth;
- if (size > mCountInStrip) {
- mMaxWidth -= mControlWidth;
+ final CharSequence text;
+ final float scaleX;
+ paint.setTextScaleX(1.0f);
+ final int textWidth = getTextWidth(styled, paint);
+ int available = paneWidth - x - mPadding;
+ if (textWidth >= available) {
+ // Needs new row, centering previous row.
+ centeringCandidates(paneView, centeringFrom, lastView, x, paneWidth);
+ x = 0;
+ y += mCandidateStripHeight;
}
+ if (x != 0) {
+ // Add divider if this isn't the left most suggestion in current row.
+ paneView.addView(divider);
+ FrameLayoutCompatUtils.placeViewAt(divider, x, y
+ + (mCandidateStripHeight - mDividerHeight) / 2, mDividerWidth,
+ mDividerHeight);
+ x += mDividerWidth;
+ }
+ available = paneWidth - x - mPadding;
+ text = getEllipsizedText(styled, available, paint);
+ scaleX = paint.getTextScaleX();
+ word.setText(text);
+ word.setTextScaleX(scaleX);
+ paneView.addView(word);
+ lastView = word;
+ if (x == 0)
+ centeringFrom = word;
+ word.measure(WRAP_CONTENT,
+ MeasureSpec.makeMeasureSpec(mCandidateStripHeight, MeasureSpec.EXACTLY));
+ final int width = word.getMeasuredWidth();
+ final int height = word.getMeasuredHeight();
+ FrameLayoutCompatUtils.placeViewAt(word, x, y + (mCandidateStripHeight - height)
+ / 2, width, height);
+ x += width;
+ if (info != null) {
+ paneView.addView(info);
+ lastView = info;
+ info.measure(WRAP_CONTENT, WRAP_CONTENT);
+ final int infoWidth = info.getMeasuredWidth();
+ FrameLayoutCompatUtils.placeViewAt(info, x - infoWidth, y, infoWidth,
+ info.getMeasuredHeight());
+ }
+ }
+ if (x != 0) {
+ // Centering last candidates row.
+ centeringCandidates(paneView, centeringFrom, lastView, x, paneWidth);
+ }
+
+ return count - from;
+ }
+ }
+
+ private static class SuggestionsStripParams extends CandidateViewParams {
+ private static final int DEFAULT_CANDIDATE_COUNT_IN_STRIP = 3;
+ private static final int DEFAULT_CENTER_CANDIDATE_PERCENTILE = 40;
+ private static final int PUNCTUATIONS_IN_STRIP = 6;
+
+ private final int mColorTypedWord;
+ private final int mColorAutoCorrect;
+ private final int mColorSuggestedCandidate;
+ private final int mCandidateCountInStrip;
+ private final float mCenterCandidateWeight;
+ private final int mCenterCandidateIndex;
+ private final Drawable mMoreCandidateHint;
+
+ private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
+ private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
+ private final CharacterStyle mInvertedForegroundColorSpan;
+ private final CharacterStyle mInvertedBackgroundColorSpan;
+ private static final int AUTO_CORRECT_BOLD = 0x01;
+ private static final int AUTO_CORRECT_UNDERLINE = 0x02;
+ private static final int AUTO_CORRECT_INVERT = 0x04;
+
+ private final TextPaint mPaint;
+ private final int mAutoCorrectHighlight;
- tryLayout();
+ private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+
+ public final boolean mAutoCorrectionVisualFlashEnabled;
+ public boolean mMoreSuggestionsAvailable;
+
+ public SuggestionsStripParams(Context context, AttributeSet attrs, int defStyle,
+ List<TextView> words, List<View> dividers, List<TextView> infos) {
+ super(words, dividers, infos);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle);
+ mAutoCorrectionVisualFlashEnabled = a.getBoolean(
+ R.styleable.CandidateView_autoCorrectionVisualFlashEnabled, false);
+ mAutoCorrectHighlight = a.getInt(R.styleable.CandidateView_autoCorrectHighlight, 0);
+ mColorTypedWord = a.getColor(R.styleable.CandidateView_colorTypedWord, 0);
+ mColorAutoCorrect = a.getColor(R.styleable.CandidateView_colorAutoCorrect, 0);
+ mColorSuggestedCandidate = a.getColor(R.styleable.CandidateView_colorSuggested, 0);
+ mCandidateCountInStrip = a.getInt(
+ R.styleable.CandidateView_candidateCountInStrip,
+ DEFAULT_CANDIDATE_COUNT_IN_STRIP);
+ mCenterCandidateWeight = a.getInt(
+ R.styleable.CandidateView_centerCandidatePercentile,
+ DEFAULT_CENTER_CANDIDATE_PERCENTILE) / 100.0f;
+ a.recycle();
+
+ mCenterCandidateIndex = mCandidateCountInStrip / 2;
+ final Resources res = context.getResources();
+ mMoreCandidateHint = res.getDrawable(R.drawable.more_suggestions_hint);
+
+ mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
+ mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
- if (mCanUseFixedWidthColumns) {
- return;
+ mPaint = new TextPaint();
+ final float textSize = res.getDimension(R.dimen.candidate_text_size);
+ mPaint.setTextSize(textSize);
+ }
+
+ public int getTextColor() {
+ return mColorTypedWord;
+ }
+
+ private CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect) {
+ if (!isAutoCorrect)
+ return word;
+ final int len = word.length();
+ final Spannable spannedWord = new SpannableString(word);
+ if ((mAutoCorrectHighlight & AUTO_CORRECT_BOLD) != 0)
+ spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ if ((mAutoCorrectHighlight & AUTO_CORRECT_UNDERLINE) != 0)
+ spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ return spannedWord;
+ }
+
+ private static boolean willAutoCorrect(SuggestedWords suggestions) {
+ return !suggestions.mTypedWordValid && suggestions.mHasMinimalSuggestion;
+ }
+
+ private int getWordPosition(int index, SuggestedWords suggestions) {
+ // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
+ // suggestions.
+ final int centerPos = willAutoCorrect(suggestions) ? 1 : 0;
+ if (index == mCenterCandidateIndex) {
+ return centerPos;
+ } else if (index == centerPos) {
+ return mCenterCandidateIndex;
+ } else {
+ return index;
+ }
+ }
+
+ private int getCandidateTextColor(int index, SuggestedWords suggestions, int pos) {
+ // TODO: Need to revisit this logic with bigram suggestions
+ final boolean isSuggestedCandidate = (pos != 0);
+
+ final int color;
+ if (index == mCenterCandidateIndex && willAutoCorrect(suggestions)) {
+ color = mColorAutoCorrect;
+ } else if (isSuggestedCandidate) {
+ color = mColorSuggestedCandidate;
+ } else {
+ color = mColorTypedWord;
+ }
+
+ final SuggestedWordInfo info = (pos < suggestions.size())
+ ? suggestions.getInfo(pos) : null;
+ if (info != null && info.isPreviousSuggestedWord()) {
+ return applyAlpha(color, 0.5f);
+ } else {
+ return color;
+ }
+ }
+
+ private static int applyAlpha(final int color, final float alpha) {
+ final int newAlpha = (int)(Color.alpha(color) * alpha);
+ return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ public CharSequence getInvertedText(CharSequence text) {
+ if ((mAutoCorrectHighlight & AUTO_CORRECT_INVERT) == 0)
+ return null;
+ final int len = text.length();
+ final Spannable word = new SpannableString(text);
+ word.setSpan(mInvertedBackgroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedForegroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ return word;
+ }
+
+ public int layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup paneView,
+ int stripWidth) {
+ if (suggestions.isPunctuationSuggestions()) {
+ return layoutPunctuationSuggestions(suggestions, stripView);
+ }
+
+ final int countInStrip = mCandidateCountInStrip;
+ setupTexts(suggestions, countInStrip);
+ mMoreSuggestionsAvailable = (suggestions.size() > countInStrip);
+ int x = 0;
+ for (int index = 0; index < countInStrip; index++) {
+ final int pos = getWordPosition(index, suggestions);
+
+ if (index != 0) {
+ final View divider = mDividers.get(pos);
+ // Add divider if this isn't the left most suggestion in candidate strip.
+ stripView.addView(divider);
}
- if (mVariableWidthForWords <= mAvailableWidthForWords) {
- return;
+
+ final CharSequence styled = mTexts.get(pos);
+ final TextView word = mWords.get(pos);
+ if (index == mCenterCandidateIndex && mMoreSuggestionsAvailable) {
+ // TODO: This "more suggestions hint" should have nicely designed icon.
+ word.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, null, mMoreCandidateHint);
+ } else {
+ word.setCompoundDrawables(null, null, null, null);
}
- final float scaleX = mAvailableWidthForWords / (float)mVariableWidthForWords;
- if (scaleX >= MIN_TEXT_XSCALE) {
- mScaleX = scaleX;
- return;
+ // Disable this candidate if the suggestion is null or empty.
+ word.setEnabled(!TextUtils.isEmpty(styled));
+ word.setTextColor(getCandidateTextColor(index, suggestions, pos));
+ final int width = getCandidateWidth(index, stripWidth);
+ final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
+ final float scaleX = word.getTextScaleX();
+ word.setText(text); // TextView.setText() resets text scale x to 1.0.
+ word.setTextScaleX(scaleX);
+ stripView.addView(word);
+ setLayoutWeight(word, getCandidateWeight(index), mCandidateStripHeight);
+
+ if (DBG) {
+ final CharSequence debugInfo = getDebugInfo(suggestions, pos);
+ if (debugInfo != null) {
+ final TextView info = mInfos.get(pos);
+ info.setText(debugInfo);
+ paneView.addView(info);
+ info.measure(WRAP_CONTENT, WRAP_CONTENT);
+ final int infoWidth = info.getMeasuredWidth();
+ final int y = info.getMeasuredHeight();
+ FrameLayoutCompatUtils.placeViewAt(info, x, 0, infoWidth, y);
+ x += infoWidth * 2;
+ }
}
+ }
+
+ return countInStrip;
+ }
+
+ private int getCandidateWidth(int index, int maxWidth) {
+ final int paddings = mPadding * mCandidateCountInStrip;
+ final int dividers = mDividerWidth * (mCandidateCountInStrip - 1);
+ final int availableWidth = maxWidth - paddings - dividers;
+ return (int)(availableWidth * getCandidateWeight(index));
+ }
- mCountInStrip--;
- } while (mCountInStrip > 1);
- }
-
- public void tryLayout() {
- final int maxCount = mCountInStrip;
- final int dividers = mDividerWidth * (maxCount - 1);
- mConstantWidthForPaddings = dividers + mPadding * maxCount;
- mAvailableWidthForWords = mMaxWidth - mConstantWidthForPaddings;
-
- mPaint.setTextScaleX(mScaleX);
- final int maxFixedWidthForWord = (mMaxWidth - dividers) / maxCount - mPadding;
- mCanUseFixedWidthColumns = true;
- mVariableWidthForWords = 0;
- for (int i = 0; i < maxCount; i++) {
- final int width = getTextWidth(mTexts.get(i), mPaint);
- if (width > maxFixedWidthForWord)
- mCanUseFixedWidthColumns = false;
- mVariableWidthForWords += width;
+ private float getCandidateWeight(int index) {
+ if (index == mCenterCandidateIndex) {
+ return mCenterCandidateWeight;
+ } else {
+ // TODO: Revisit this for cases of 5 or more suggestions
+ return (1.0f - mCenterCandidateWeight) / (mCandidateCountInStrip - 1);
}
}
- private void setupTexts(SuggestedWords suggestions, int count, int autoCorrectHighlight) {
+ private void setupTexts(SuggestedWords suggestions, int countInStrip) {
mTexts.clear();
- for (int i = 0; i < count; i++) {
- final CharSequence suggestion = suggestions.getWord(i);
- if (suggestion == null) {
- // Skip an empty suggestion, but we need to add a place-holder for it in order
- // to avoid an exception in the loop in updateSuggestions().
- mTexts.add("");
- continue;
- }
-
- final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
- && ((i == 1 && !suggestions.mTypedWordValid)
- || (i == 0 && suggestions.mTypedWordValid));
- // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1
- // and there are multiple suggestions, such as the default punctuation list.
- // TODO: Need to revisit this logic with bigram suggestions
- final CharSequence styled = getStyledCandidateWord(suggestion, isAutoCorrect,
- autoCorrectHighlight);
+ final int count = Math.min(suggestions.size(), countInStrip);
+ for (int pos = 0; pos < count; pos++) {
+ final CharSequence word = suggestions.getWord(pos);
+ final boolean isAutoCorrect = pos == 1 && willAutoCorrect(suggestions);
+ final CharSequence styled = getStyledCandidateWord(word, isAutoCorrect);
mTexts.add(styled);
}
+ for (int pos = count; pos < countInStrip; pos++) {
+ // Make this inactive for touches in layout().
+ mTexts.add(null);
+ }
}
- @Override
- public String toString() {
- return String.format(
- "count=%d width=%d avail=%d fixcol=%s scaleX=%4.2f const=%d var=%d",
- mCountInStrip, mMaxWidth, mAvailableWidthForWords, mCanUseFixedWidthColumns,
- mScaleX, mConstantWidthForPaddings, mVariableWidthForWords);
+ private int layoutPunctuationSuggestions(SuggestedWords suggestions, ViewGroup stripView) {
+ final int countInStrip = Math.min(suggestions.size(), PUNCTUATIONS_IN_STRIP);
+ for (int index = 0; index < countInStrip; index++) {
+ if (index != 0) {
+ // Add divider if this isn't the left most suggestion in candidate strip.
+ stripView.addView(mDividers.get(index));
+ }
+
+ final TextView word = mWords.get(index);
+ word.setEnabled(true);
+ word.setTextColor(mColorTypedWord);
+ final CharSequence text = suggestions.getWord(index);
+ word.setText(text);
+ word.setTextScaleX(1.0f);
+ word.setCompoundDrawables(null, null, null, null);
+ stripView.addView(word);
+ setLayoutWeight(word, 1.0f, mCandidateStripHeight);
+ }
+ mMoreSuggestionsAvailable = false;
+ return countInStrip;
}
}
@@ -295,18 +517,7 @@ public class CandidateView extends LinearLayout implements OnClickListener {
setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable(
context, attrs, defStyle, R.style.CandidateViewStyle));
- final TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle);
- mAutoCorrectHighlight = a.getInt(R.styleable.CandidateView_autoCorrectHighlight, 0);
- mColorTypedWord = a.getColor(R.styleable.CandidateView_colorTypedWord, 0);
- mColorAutoCorrect = a.getColor(R.styleable.CandidateView_colorAutoCorrect, 0);
- mColorSuggestedCandidate = a.getColor(R.styleable.CandidateView_colorSuggested, 0);
- mCandidateCountInStrip = a.getInt(
- R.styleable.CandidateView_candidateCountInStrip, DEFAULT_CANDIDATE_COUNT_IN_STRIP);
- a.recycle();
-
- Resources res = context.getResources();
- LayoutInflater inflater = LayoutInflater.from(context);
+ final LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.candidates_strip, this);
mPreviewPopup = new PopupWindow(context);
@@ -317,56 +528,26 @@ public class CandidateView extends LinearLayout implements OnClickListener {
mPreviewPopup.setBackgroundDrawable(null);
mCandidatesStrip = (ViewGroup)findViewById(R.id.candidates_strip);
- mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height);
- for (int i = 0; i < MAX_SUGGESTIONS; i++) {
+ for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) {
final TextView word = (TextView)inflater.inflate(R.layout.candidate_word, null);
- word.setTag(i);
+ word.setTag(pos);
word.setOnClickListener(this);
+ word.setOnLongClickListener(this);
mWords.add(word);
+ final View divider = inflater.inflate(R.layout.candidate_divider, null);
+ divider.setTag(pos);
+ divider.setOnClickListener(this);
+ mDividers.add(divider);
mInfos.add((TextView)inflater.inflate(R.layout.candidate_info, null));
- mDividers.add(inflater.inflate(R.layout.candidate_divider, null));
}
mTouchToSave = findViewById(R.id.touch_to_save);
mWordToSave = (TextView)findViewById(R.id.word_to_save);
mWordToSave.setOnClickListener(this);
- mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
- mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
-
- final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
- attrs, R.styleable.KeyboardView, R.attr.keyboardViewStyle, R.style.KeyboardView);
- final Drawable expandBackground = keyboardViewAttr.getDrawable(
- R.styleable.KeyboardView_keyBackground);
- final Drawable closeBackground = keyboardViewAttr.getDrawable(
- R.styleable.KeyboardView_keyBackground);
- final int keyTextColor = keyboardViewAttr.getColor(
- R.styleable.KeyboardView_keyTextColor, 0xFF000000);
- keyboardViewAttr.recycle();
-
- mCandidatesPaneControl = (ViewGroup)findViewById(R.id.candidates_pane_control);
- mExpandCandidatesPane = (TextView)findViewById(R.id.expand_candidates_pane);
- mExpandCandidatesPane.setBackgroundDrawable(expandBackground);
- mExpandCandidatesPane.setTextColor(keyTextColor);
- mExpandCandidatesPane.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- expandCandidatesPane();
- }
- });
- mCloseCandidatesPane = (TextView)findViewById(R.id.close_candidates_pane);
- mCloseCandidatesPane.setBackgroundDrawable(closeBackground);
- mCloseCandidatesPane.setTextColor(keyTextColor);
- mCloseCandidatesPane.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- closeCandidatesPane();
- }
- });
- mCandidatesPaneControl.measure(WRAP_CONTENT, WRAP_CONTENT);
-
- mParams = new CandidateViewLayoutParams(res,
- mWords.get(0), mDividers.get(0), mCandidatesPaneControl, mAutoCorrectHighlight);
+ mStripParams = new SuggestionsStripParams(context, attrs, defStyle, mWords, mDividers,
+ mInfos);
+ mPaneParams = new SuggestionsPaneParams(mWords, mDividers, mInfos);
}
/**
@@ -387,7 +568,6 @@ public class CandidateView extends LinearLayout implements OnClickListener {
if (suggestions == null)
return;
mSuggestions = suggestions;
- mExpandCandidatesPane.setEnabled(false);
if (mShowingAutoCorrectionInverted) {
mHandler.postUpdateSuggestions();
} else {
@@ -395,181 +575,30 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
}
- private static CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect,
- int autoCorrectHighlight) {
- if (!isAutoCorrect)
- return word;
- final Spannable spannedWord = new SpannableString(word);
- if ((autoCorrectHighlight & AUTO_CORRECT_BOLD) != 0)
- spannedWord.setSpan(BOLD_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- if ((autoCorrectHighlight & AUTO_CORRECT_UNDERLINE) != 0)
- spannedWord.setSpan(UNDERLINE_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- return spannedWord;
- }
-
- private int getCandidateTextColor(boolean isAutoCorrect, boolean isSuggestedCandidate,
- SuggestedWordInfo info) {
- final int color;
- if (isAutoCorrect) {
- color = mColorAutoCorrect;
- } else if (isSuggestedCandidate) {
- color = mColorSuggestedCandidate;
- } else {
- color = mColorTypedWord;
- }
- if (info != null && info.isPreviousSuggestedWord()) {
- final int newAlpha = (int)(Color.alpha(color) * 0.5f);
- return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
- } else {
- return color;
- }
- }
-
private void updateSuggestions() {
- final SuggestedWords suggestions = mSuggestions;
- final List<SuggestedWordInfo> suggestedWordInfoList = suggestions.mSuggestedWordInfoList;
- final int paneWidth = getWidth();
- final CandidateViewLayoutParams params = mParams;
-
clear();
closeCandidatesPane();
- if (suggestions.size() == 0)
+ if (mSuggestions.size() == 0)
return;
- params.layoutStrip(suggestions, paneWidth, suggestions.isPunctuationSuggestions()
- ? PUNCTUATIONS_IN_STRIP : mCandidateCountInStrip);
-
- final int count = Math.min(mWords.size(), suggestions.size());
- if (count <= params.mCountInStrip && !DBG) {
- mCandidatesPaneControl.setVisibility(GONE);
- } else {
- mCandidatesPaneControl.setVisibility(VISIBLE);
- mExpandCandidatesPane.setVisibility(VISIBLE);
- mExpandCandidatesPane.setEnabled(true);
- }
-
- final int countInStrip = params.mCountInStrip;
- View centeringFrom = null, lastView = null;
- int x = 0, y = 0, infoX = 0;
- for (int i = 0; i < count; i++) {
- final int pos;
- if (i <= 1) {
- final boolean willAutoCorrect = !suggestions.mTypedWordValid
- && suggestions.mHasMinimalSuggestion;
- pos = willAutoCorrect ? 1 - i : i;
- } else {
- pos = i;
- }
- final CharSequence suggestion = suggestions.getWord(pos);
- if (suggestion == null) continue;
-
- final SuggestedWordInfo suggestionInfo = (suggestedWordInfoList != null)
- ? suggestedWordInfoList.get(pos) : null;
- final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
- && ((pos == 1 && !suggestions.mTypedWordValid)
- || (pos == 0 && suggestions.mTypedWordValid));
- // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1
- // and there are multiple suggestions, such as the default punctuation list.
- // TODO: Need to revisit this logic with bigram suggestions
- final boolean isSuggestedCandidate = (pos != 0);
- final boolean isPunctuationSuggestions = (suggestion.length() == 1 && count > 1);
-
- final TextView word = mWords.get(pos);
- final TextPaint paint = word.getPaint();
- // TODO: Reorder candidates in strip as appropriate. The center candidate should hold
- // the word when space is typed (valid typed word or auto corrected word).
- word.setTextColor(getCandidateTextColor(isAutoCorrect,
- isSuggestedCandidate || isPunctuationSuggestions, suggestionInfo));
- final CharSequence styled = params.mTexts.get(pos);
-
- final TextView info;
- if (DBG && suggestionInfo != null
- && !TextUtils.isEmpty(suggestionInfo.getDebugString())) {
- info = mInfos.get(i);
- info.setText(suggestionInfo.getDebugString());
- } else {
- info = null;
- }
+ final int width = getWidth();
+ final int countInStrip = mStripParams.layout(
+ mSuggestions, mCandidatesStrip, mCandidatesPane, width);
+ final int countInPane = mPaneParams.layout(
+ mSuggestions, mCandidatesPane, countInStrip, mStripParams.getTextColor(), width);
+ }
- final CharSequence text;
- final float scaleX;
- if (i < countInStrip) {
- if (i == 0 && params.mCountInStrip == 1) {
- text = getEllipsizedText(styled, params.mMaxWidth, paint);
- scaleX = paint.getTextScaleX();
- } else {
- text = styled;
- scaleX = params.mScaleX;
- }
- word.setText(text);
- word.setTextScaleX(scaleX);
- if (i != 0) {
- // Add divider if this isn't the left most suggestion in candidate strip.
- mCandidatesStrip.addView(mDividers.get(i));
- }
- mCandidatesStrip.addView(word);
- if (params.mCanUseFixedWidthColumns) {
- setLayoutWeight(word, 1.0f, mCandidateStripHeight);
- } else {
- final int width = getTextWidth(text, paint) + params.mPadding;
- setLayoutWeight(word, width, mCandidateStripHeight);
- }
- if (info != null) {
- mCandidatesPane.addView(info);
- info.measure(WRAP_CONTENT, WRAP_CONTENT);
- final int width = info.getMeasuredWidth();
- y = info.getMeasuredHeight();
- FrameLayoutCompatUtils.placeViewAt(info, infoX, 0, width, y);
- infoX += width * 2;
- }
- } else {
- paint.setTextScaleX(1.0f);
- final int textWidth = getTextWidth(styled, paint);
- int available = paneWidth - x - params.mPadding;
- if (textWidth >= available) {
- // Needs new row, centering previous row.
- centeringCandidates(centeringFrom, lastView, x, paneWidth);
- x = 0;
- y += mCandidateStripHeight;
- }
- if (x != 0) {
- // Add divider if this isn't the left most suggestion in current row.
- final View divider = mDividers.get(i);
- mCandidatesPane.addView(divider);
- FrameLayoutCompatUtils.placeViewAt(
- divider, x, y + (mCandidateStripHeight - params.mDividerHeight) / 2,
- params.mDividerWidth, params.mDividerHeight);
- x += params.mDividerWidth;
- }
- available = paneWidth - x - params.mPadding;
- text = getEllipsizedText(styled, available, paint);
- scaleX = paint.getTextScaleX();
- word.setText(text);
- word.setTextScaleX(scaleX);
- mCandidatesPane.addView(word);
- lastView = word;
- if (x == 0) centeringFrom = word;
- word.measure(WRAP_CONTENT,
- MeasureSpec.makeMeasureSpec(mCandidateStripHeight, MeasureSpec.EXACTLY));
- final int width = word.getMeasuredWidth();
- final int height = word.getMeasuredHeight();
- FrameLayoutCompatUtils.placeViewAt(
- word, x, y + (mCandidateStripHeight - height) / 2, width, height);
- x += width;
- if (info != null) {
- mCandidatesPane.addView(info);
- lastView = info;
- info.measure(WRAP_CONTENT, WRAP_CONTENT);
- final int infoWidth = info.getMeasuredWidth();
- FrameLayoutCompatUtils.placeViewAt(
- info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
+ private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) {
+ if (DBG && pos < suggestions.size()) {
+ final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
+ if (wordInfo != null) {
+ final CharSequence debugInfo = wordInfo.getDebugString();
+ if (!TextUtils.isEmpty(debugInfo)) {
+ return debugInfo;
}
}
}
- if (x != 0) {
- // Centering last candidates row.
- centeringCandidates(centeringFrom, lastView, x, paneWidth);
- }
+ return null;
}
private static void setLayoutWeight(View v, float weight, int height) {
@@ -582,13 +611,13 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
}
- private void centeringCandidates(View from, View to, int width, int paneWidth) {
- final ViewGroup pane = mCandidatesPane;
- final int fromIndex = pane.indexOfChild(from);
- final int toIndex = pane.indexOfChild(to);
- final int offset = (paneWidth - width) / 2;
+ private static void centeringCandidates(ViewGroup parent, View from, View to, int width,
+ int parentWidth) {
+ final int fromIndex = parent.indexOfChild(from);
+ final int toIndex = parent.indexOfChild(to);
+ final int offset = (parentWidth - width) / 2;
for (int index = fromIndex; index <= toIndex; index++) {
- offsetMargin(pane.getChildAt(index), offset, 0);
+ offsetMargin(parent.getChildAt(index), offset, 0);
}
}
@@ -604,16 +633,20 @@ public class CandidateView extends LinearLayout implements OnClickListener {
private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
TextPaint paint) {
+ if (text == null) return null;
paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint);
- final float scaleX = Math.min(maxWidth / (float)width, 1.0f);
+ if (width <= maxWidth) {
+ return text;
+ }
+ final float scaleX = maxWidth / (float)width;
if (scaleX >= MIN_TEXT_XSCALE) {
paint.setTextScaleX(scaleX);
return text;
}
// Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To get
- // squeezed and ellipsezed text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
+ // squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
final CharSequence ellipsized = TextUtils.ellipsize(
text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
paint.setTextScaleX(MIN_TEXT_XSCALE);
@@ -652,31 +685,33 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
private void expandCandidatesPane() {
- mExpandCandidatesPane.setVisibility(GONE);
- mCloseCandidatesPane.setVisibility(VISIBLE);
mCandidatesPaneContainer.setMinimumHeight(mKeyboardView.getMeasuredHeight());
mCandidatesPaneContainer.setVisibility(VISIBLE);
mKeyboardView.setVisibility(GONE);
}
private void closeCandidatesPane() {
- mExpandCandidatesPane.setVisibility(VISIBLE);
- mCloseCandidatesPane.setVisibility(GONE);
mCandidatesPaneContainer.setVisibility(GONE);
mKeyboardView.setVisibility(VISIBLE);
}
+ private void toggleCandidatesPane() {
+ if (mCandidatesPaneContainer.getVisibility() == VISIBLE) {
+ closeCandidatesPane();
+ } else {
+ expandCandidatesPane();
+ }
+ }
+
public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) {
- if ((mAutoCorrectHighlight & AUTO_CORRECT_INVERT) == 0)
+ if (!mStripParams.mAutoCorrectionVisualFlashEnabled) {
+ return;
+ }
+ final CharSequence inverted = mStripParams.getInvertedText(autoCorrectedWord);
+ if (inverted == null)
return;
final TextView tv = mWords.get(1);
- final Spannable word = new SpannableString(autoCorrectedWord);
- final int wordLength = word.length();
- word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- word.setSpan(mInvertedForegroundColorSpan, 0, wordLength,
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- tv.setText(word);
+ tv.setText(inverted);
mShowingAutoCorrectionInverted = true;
}
@@ -688,7 +723,6 @@ public class CandidateView extends LinearLayout implements OnClickListener {
mWordToSave.setText(word);
mShowingAddToDictionary = true;
mCandidatesStrip.setVisibility(GONE);
- mCandidatesPaneControl.setVisibility(GONE);
mTouchToSave.setVisibility(VISIBLE);
}
@@ -721,7 +755,7 @@ public class CandidateView extends LinearLayout implements OnClickListener {
return;
final TextView previewText = mPreviewText;
- previewText.setTextColor(mColorTypedWord);
+ previewText.setTextColor(mStripParams.mColorTypedWord);
previewText.setText(word);
previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
@@ -747,6 +781,15 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
@Override
+ public boolean onLongClick(View view) {
+ if (mStripParams.mMoreSuggestionsAvailable) {
+ toggleCandidatesPane();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public void onClick(View view) {
if (view == mWordToSave) {
addToDictionary(((TextView)view).getText());
@@ -754,6 +797,11 @@ public class CandidateView extends LinearLayout implements OnClickListener {
return;
}
+ if (view == mCandidatesPane) {
+ closeCandidatesPane();
+ return;
+ }
+
final Object tag = view.getTag();
if (!(tag instanceof Integer))
return;
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
index 66a041508..8a7dfb839 100644
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
@@ -49,20 +49,28 @@ public class ContactsDictionary extends ExpandableDictionary {
private long mLastLoadedContacts;
- public ContactsDictionary(Context context, int dicTypeId) {
+ public ContactsDictionary(final Context context, final int dicTypeId) {
super(context, dicTypeId);
+ registerObserver(context);
+ loadDictionary();
+ }
+
+ private synchronized void registerObserver(final Context context) {
// Perform a managed query. The Activity will handle closing and requerying the cursor
// when needed.
+ if (mObserver != null) return;
ContentResolver cres = context.getContentResolver();
-
cres.registerContentObserver(
- Contacts.CONTENT_URI, true,mObserver = new ContentObserver(null) {
+ Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
@Override
public void onChange(boolean self) {
setRequiresReload(true);
}
});
- loadDictionary();
+ }
+
+ public void reopen(final Context context) {
+ registerObserver(context);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index c7737b9a2..c35b42877 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2008 The Android Open Source Project
- *
+ *
* 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
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
/**
* Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
* strokes.
@@ -25,7 +27,7 @@ public abstract class Dictionary {
* Whether or not to replicate the typed word in the suggested list, even if it's valid.
*/
protected static final boolean INCLUDE_TYPED_WORD_IF_VALID = false;
-
+
/**
* The weight to give to a word if it's length is the same as the number of typed characters.
*/
@@ -57,13 +59,15 @@ public abstract class Dictionary {
}
/**
- * Searches for words in the dictionary that match the characters in the composer. Matched
+ * Searches for words in the dictionary that match the characters in the composer. Matched
* words are added through the callback object.
* @param composer the key sequence to match
* @param callback the callback object to send matched words to as possible candidates
+ * @param proximityInfo the object for key proximity. May be ignored by some implementations.
* @see WordCallback#addWord(char[], int, int, int, int, DataType)
*/
- abstract public void getWords(final WordComposer composer, final WordCallback callback);
+ abstract public void getWords(final WordComposer composer, final WordCallback callback,
+ final ProximityInfo proximityInfo);
/**
* Searches for pairs in the bigram dictionary that matches the previous word and all the
@@ -83,7 +87,7 @@ public abstract class Dictionary {
* @return true if the word exists, false otherwise
*/
abstract public boolean isValidWord(CharSequence word);
-
+
/**
* Compares the contents of the character array with the typed word and returns true if they
* are the same.
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 107840331..739153044 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -47,9 +49,10 @@ public class DictionaryCollection extends Dictionary {
}
@Override
- public void getWords(final WordComposer composer, final WordCallback callback) {
+ public void getWords(final WordComposer composer, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
for (final Dictionary dict : mDictionaries)
- dict.getWords(composer, callback);
+ dict.getWords(composer, callback, proximityInfo);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 97a4a1816..35d1541ff 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.os.AsyncTask;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.ProximityInfo;
import java.util.LinkedList;
@@ -193,7 +194,8 @@ public class ExpandableDictionary extends Dictionary {
}
@Override
- public void getWords(final WordComposer codes, final WordCallback callback) {
+ public void getWords(final WordComposer codes, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
synchronized (mUpdatingLock) {
// If we need to update, start off a background task
if (mRequiresReload) startDictionaryLoadingTaskLocked();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d9d421411..a932f03ac 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -64,6 +64,7 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.KeyboardSwitcher.KeyboardLayoutState;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.LatinKeyboard;
import com.android.inputmethod.keyboard.LatinKeyboardView;
@@ -112,6 +113,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Key events coming any faster than this are long-presses.
private static final int QUICK_PRESS = 200;
+ private static final int SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY = 2;
+ private static final int ACCUMULATE_START_INPUT_VIEW_DELAY = 20;
+ private static final int RESTORE_KEYBOARD_STATE_DELAY = 500;
+
/**
* The name of the scheme used by the Package Manager to warn of a new package installation,
* replacement or removal.
@@ -165,7 +170,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private WordComposer mWordComposer = new WordComposer();
private CharSequence mBestWord;
private boolean mHasUncommittedTypedChars;
- private boolean mHasDictionary;
// Magic space: a space that should disappear on space/apostrophe insertion, move after the
// punctuation on punctuation insertion, and become a real space on alpha char insertion.
private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space.
@@ -186,8 +190,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private long mLastKeyTime;
private AudioManager mAudioManager;
- // Align sound effect volume on music volume
- private static final float FX_VOLUME = -1.0f;
+ private static float mFxVolume = -1.0f; // just a default value to be updated runtime
private boolean mSilentModeOn; // System-wide current configuration
// TODO: Move this flag to VoiceProxy
@@ -218,6 +221,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
private static final int MSG_CONFIRM_ORIENTATION_CHANGE = 8;
private static final int MSG_START_INPUT_VIEW = 9;
+ private static final int MSG_RESTORE_KEYBOARD_LAYOUT = 10;
private static class OrientationChangeArgs {
public final int mOldWidth;
@@ -302,6 +306,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
case MSG_START_INPUT_VIEW:
latinIme.onStartInputView((EditorInfo)msg.obj, false);
break;
+ case MSG_RESTORE_KEYBOARD_LAYOUT:
+ removeMessages(MSG_UPDATE_SHIFT_STATE);
+ ((KeyboardLayoutState)msg.obj).restore();
+ break;
}
}
@@ -392,22 +400,38 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return hasMessages(MSG_SPACE_TYPED);
}
+ public void postRestoreKeyboardLayout() {
+ final LatinIME latinIme = getOuterInstance();
+ final KeyboardLayoutState state = latinIme.mKeyboardSwitcher.getKeyboardState();
+ if (state.isValid()) {
+ removeMessages(MSG_RESTORE_KEYBOARD_LAYOUT);
+ sendMessageDelayed(
+ obtainMessage(MSG_RESTORE_KEYBOARD_LAYOUT, state),
+ RESTORE_KEYBOARD_STATE_DELAY);
+ }
+ }
+
private void postConfirmOrientationChange(OrientationChangeArgs args) {
removeMessages(MSG_CONFIRM_ORIENTATION_CHANGE);
- // Will confirm whether orientation change has finished or not after 2ms again.
- sendMessageDelayed(obtainMessage(MSG_CONFIRM_ORIENTATION_CHANGE, args), 2);
+ // Will confirm whether orientation change has finished or not again.
+ sendMessageDelayed(obtainMessage(MSG_CONFIRM_ORIENTATION_CHANGE, args),
+ SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY);
}
public void startOrientationChanging(int oldw, int oldh) {
postConfirmOrientationChange(new OrientationChangeArgs(oldw, oldh));
+ final LatinIME latinIme = getOuterInstance();
+ latinIme.mKeyboardSwitcher.getKeyboardState().save();
+ postRestoreKeyboardLayout();
}
public boolean postStartInputView(EditorInfo attribute) {
if (hasMessages(MSG_CONFIRM_ORIENTATION_CHANGE) || hasMessages(MSG_START_INPUT_VIEW)) {
removeMessages(MSG_START_INPUT_VIEW);
- // Postpone onStartInputView 20ms afterward and see if orientation change has
- // finished.
- sendMessageDelayed(obtainMessage(MSG_START_INPUT_VIEW, attribute), 20);
+ // Postpone onStartInputView by ACCUMULATE_START_INPUT_VIEW_DELAY and see if
+ // orientation change has finished.
+ sendMessageDelayed(obtainMessage(MSG_START_INPUT_VIEW, attribute),
+ ACCUMULATE_START_INPUT_VIEW_DELAY);
return true;
}
return false;
@@ -484,7 +508,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
- resetContactsDictionary();
+ resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+ updateSoundEffectVolume();
}
private void initSuggest() {
@@ -493,8 +518,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final Resources res = mResources;
final Locale savedLocale = Utils.setSystemLocale(res, keyboardLocale);
+ final ContactsDictionary oldContactsDictionary;
if (mSuggest != null) {
+ oldContactsDictionary = mSuggest.getContactsDictionary();
mSuggest.close();
+ } else {
+ oldContactsDictionary = null;
}
int mainDicResId = Utils.getMainDictionaryResourceId(res);
@@ -508,7 +537,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mSuggest.setUserDictionary(mUserDictionary);
mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
- resetContactsDictionary();
+ resetContactsDictionary(oldContactsDictionary);
mUserUnigramDictionary
= new UserUnigramDictionary(this, this, localeStr, Suggest.DIC_USER_UNIGRAM);
@@ -523,11 +552,36 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
Utils.setSystemLocale(res, savedLocale);
}
- private void resetContactsDictionary() {
- if (null == mSuggest) return;
- ContactsDictionary contactsDictionary = mSettingsValues.mUseContactsDict
- ? new ContactsDictionary(this, Suggest.DIC_CONTACTS) : null;
- mSuggest.setContactsDictionary(contactsDictionary);
+ /**
+ * Resets the contacts dictionary in mSuggest according to the user settings.
+ *
+ * This method takes an optional contacts dictionary to use. Since the contacts dictionary
+ * does not depend on the locale, it can be reused across different instances of Suggest.
+ * The dictionary will also be opened or closed as necessary depending on the settings.
+ *
+ * @param oldContactsDictionary an optional dictionary to use, or null
+ */
+ private void resetContactsDictionary(final ContactsDictionary oldContactsDictionary) {
+ final boolean shouldSetDictionary = (null != mSuggest && mSettingsValues.mUseContactsDict);
+
+ final ContactsDictionary dictionaryToUse;
+ if (!shouldSetDictionary) {
+ // Make sure the dictionary is closed. If it is already closed, this is a no-op,
+ // so it's safe to call it anyways.
+ if (null != oldContactsDictionary) oldContactsDictionary.close();
+ dictionaryToUse = null;
+ } else if (null != oldContactsDictionary) {
+ // Make sure the old contacts dictionary is opened. If it is already open, this is a
+ // no-op, so it's safe to call it anyways.
+ oldContactsDictionary.reopen(this);
+ dictionaryToUse = oldContactsDictionary;
+ } else {
+ dictionaryToUse = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
+ }
+
+ if (null != mSuggest) {
+ mSuggest.setContactsDictionary(dictionaryToUse);
+ }
}
/* package private */ void resetSuggestMainDict() {
@@ -557,7 +611,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// If orientation changed while predicting, commit the change
if (conf.orientation != mDisplayOrientation) {
mHandler.startOrientationChanging(mDisplayWidth, mDisplayHeight);
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
commitTyped(ic);
if (ic != null) ic.finishComposingText(); // For voice input
if (isShowingOptionDialog())
@@ -596,6 +650,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void onStartInputView(EditorInfo attribute, boolean restarting) {
+ mHandler.postRestoreKeyboardLayout();
if (mHandler.postStartInputView(attribute)) {
return;
}
@@ -649,7 +704,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (mSubtypeSwitcher.isKeyboardMode()) {
switcher.loadKeyboard(attribute, mSettingsValues);
- switcher.updateShiftState();
}
if (mCandidateView != null)
@@ -658,8 +712,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Delay updating suggestions because keyboard input view may not be shown at this point.
mHandler.postUpdateSuggestions();
- updateCorrectionMode();
-
inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn,
mSettingsValues.mKeyPreviewPopupDismissDelay);
inputView.setProximityCorrectionEnabled(true);
@@ -738,7 +790,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
super.onFinishInput();
LatinImeLogger.commit();
- mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
@@ -751,6 +802,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void onFinishInputView(boolean finishingInput) {
super.onFinishInputView(finishingInput);
+ mKeyboardSwitcher.onFinishInputView();
KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null) inputView.cancelAllMessages();
// Remove pending messages related to update suggestions
@@ -789,7 +841,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final boolean selectionChanged = (newSelStart != candidatesEnd
|| newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
- if (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
+ if (!mExpectingUpdateSelection
+ && ((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
|| mVoiceProxy.isVoiceInputHighlighted())
&& (selectionChanged || candidatesCleared)) {
if (candidatesCleared) {
@@ -807,7 +860,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
setPunctuationSuggestions();
}
TextEntryState.reset();
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.finishComposingText();
}
@@ -872,7 +925,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void hideWindow() {
LatinImeLogger.commit();
- mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
+ mKeyboardSwitcher.onHideWindow();
if (TRACE) Debug.stopMethodTracing();
if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
@@ -917,7 +970,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (onEvaluateInputViewShown() && mCandidateViewContainer != null) {
final boolean shouldShowCandidates = shown
&& (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true);
- if (isExtractViewShown()) {
+ if (isFullscreenMode()) {
// No need to have extra space to show the key preview.
mCandidateViewContainer.setMinimumHeight(0);
mCandidateViewContainer.setVisibility(
@@ -1012,7 +1065,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
event.getAction(), event.getKeyCode(), event.getRepeatCount(),
event.getDeviceId(), event.getScanCode(),
KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null)
ic.sendKeyEvent(newEvent);
return true;
@@ -1022,12 +1075,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return super.onKeyUp(keyCode, event);
}
- public void commitTyped(InputConnection inputConnection) {
+ public void commitTyped(final InputConnection ic) {
if (!mHasUncommittedTypedChars) return;
mHasUncommittedTypedChars = false;
if (mComposingStringBuilder.length() > 0) {
- if (inputConnection != null) {
- inputConnection.commitText(mComposingStringBuilder, 1);
+ if (ic != null) {
+ ic.commitText(mComposingStringBuilder, 1);
}
mCommittedLength = mComposingStringBuilder.length();
TextEntryState.acceptedTyped(mComposingStringBuilder);
@@ -1038,7 +1091,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
public boolean getCurrentAutoCapsState() {
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
EditorInfo ei = getCurrentInputEditorInfo();
if (mSettingsValues.mAutoCap && ic != null && ei != null
&& ei.inputType != InputType.TYPE_NULL) {
@@ -1062,25 +1115,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
- private static boolean canBeFollowedByPeriod(final int codePoint) {
- // TODO: Check again whether there really ain't a better way to check this.
- // TODO: This should probably be language-dependant...
- return Character.isLetterOrDigit(codePoint)
- || codePoint == Keyboard.CODE_SINGLE_QUOTE
- || codePoint == Keyboard.CODE_DOUBLE_QUOTE
- || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
- || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
- || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
- || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
- }
-
private void maybeDoubleSpace() {
if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
if (lastThree != null && lastThree.length() == 3
- && canBeFollowedByPeriod(lastThree.charAt(0))
+ && Utils.canBeFollowedByPeriod(lastThree.charAt(0))
&& lastThree.charAt(1) == Keyboard.CODE_SPACE
&& lastThree.charAt(2) == Keyboard.CODE_SPACE
&& mHandler.isAcceptingDoubleSpaces()) {
@@ -1096,10 +1137,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
- private void maybeRemovePreviousPeriod(CharSequence text) {
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
-
+ // "ic" must not null
+ private void maybeRemovePreviousPeriod(final InputConnection ic, CharSequence text) {
// When the text's first character is '.', remove the previous period
// if there is one.
CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
@@ -1139,25 +1178,31 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
private void onSettingsKeyPressed() {
- if (isShowingOptionDialog())
- return;
+ if (isShowingOptionDialog()) return;
if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
showSubtypeSelectorAndSettings();
- } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
+ } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, false /* exclude aux subtypes */)) {
showOptionsMenu();
} else {
launchSettings();
}
}
- private void onSettingsKeyLongPressed() {
- if (!isShowingOptionDialog()) {
- if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
+ // Virtual codes representing custom requests. These are used in onCustomRequest() below.
+ public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
+
+ @Override
+ public boolean onCustomRequest(int requestCode) {
+ if (isShowingOptionDialog()) return false;
+ switch (requestCode) {
+ case CODE_SHOW_INPUT_METHOD_PICKER:
+ if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, true /* include aux subtypes */)) {
mImm.showInputMethodPicker();
- } else {
- launchSettings();
+ return true;
}
+ return false;
}
+ return false;
}
private boolean isShowingOptionDialog() {
@@ -1201,9 +1246,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
case Keyboard.CODE_SETTINGS:
onSettingsKeyPressed();
break;
- case Keyboard.CODE_SETTINGS_LONGPRESS:
- onSettingsKeyLongPressed();
- break;
case Keyboard.CODE_CAPSLOCK:
switcher.toggleCapsLock();
break;
@@ -1238,12 +1280,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void onTextInput(CharSequence text) {
mVoiceProxy.commitVoiceInput();
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
mRecorrection.abortRecorrection(false);
ic.beginBatchEdit();
commitTyped(ic);
- maybeRemovePreviousPeriod(text);
+ maybeRemovePreviousPeriod(ic, text);
ic.commitText(text, 1);
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
@@ -1293,14 +1335,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
TextEntryState.backspace();
if (TextEntryState.isUndoCommit()) {
- revertLastWord(deleteChar);
+ revertLastWord(ic);
ic.endBatchEdit();
return;
}
if (justReplacedDoubleSpace) {
- if (revertDoubleSpace()) {
- ic.endBatchEdit();
- return;
+ if (revertDoubleSpace(ic)) {
+ ic.endBatchEdit();
+ return;
}
}
@@ -1315,7 +1357,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// different behavior only in the case of picking the first
// suggestion (typed word). It's intentional to have made this
// inconsistent with backspacing after selecting other suggestions.
- revertLastWord(true /* deleteChar */);
+ revertLastWord(ic);
} else {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
if (mDeleteCount > DELETE_ACCELERATE_AT) {
@@ -1361,7 +1403,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
int code = primaryCode;
- if (isAlphabet(code) && isSuggestionsRequested() && !isCursorTouchingWord()) {
+ if ((isAlphabet(code) || mSettingsValues.isSymbolExcludedFromWordSeparators(code))
+ && isSuggestionsRequested() && !isCursorTouchingWord()) {
if (!mHasUncommittedTypedChars) {
mHasUncommittedTypedChars = true;
mComposingStringBuilder.setLength(0);
@@ -1398,7 +1441,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
mComposingStringBuilder.append((char) code);
mWordComposer.add(code, keyCodes, x, y);
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
@@ -1443,7 +1486,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// in Italian dov' should not be expanded to dove' because the elision
// requires the last vowel to be removed.
final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
- && !mInputTypeNoAutoCorrect && mHasDictionary;
+ && !mInputTypeNoAutoCorrect;
if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
pickedDefault = pickDefaultSuggestion(primaryCode);
} else {
@@ -1586,7 +1629,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
// getSuggestedWordBuilder handles gracefully a null value of prevWord
final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
- mKeyboardSwitcher.getKeyboardView(), wordComposer, prevWord);
+ mKeyboardSwitcher.getKeyboardView(), wordComposer, prevWord,
+ mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
final CharSequence typedWord = wordComposer.getTypedWord();
@@ -1663,15 +1707,15 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mSettingsValues.mWordSeparators);
final boolean recorrecting = TextEntryState.isRecorrecting();
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
}
if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null
&& index >= 0 && index < mApplicationSpecifiedCompletions.length) {
- CompletionInfo ci = mApplicationSpecifiedCompletions[index];
if (ic != null) {
- ic.commitCompletion(ci);
+ final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
+ ic.commitCompletion(completionInfo);
}
mCommittedLength = suggestion.length();
if (mCandidateView != null) {
@@ -1698,7 +1742,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final int rawPrimaryCode = suggestion.charAt(0);
// Maybe apply the "bidi mirrored" conversions for parentheses
final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
- final int primaryCode = keyboard.isRtlKeyboard()
+ final int primaryCode = keyboard.mIsRtlKeyboard
? Key.getRtlParenthesisCode(rawPrimaryCode) : rawPrimaryCode;
final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : "";
@@ -1737,9 +1781,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
sendMagicSpace();
}
- // We should show the hint if the user pressed the first entry AND either:
+ // We should show the "Touch again to save" hint if the user pressed the first entry
+ // AND either:
// - There is no dictionary (we know that because we tried to load it => null != mSuggest
- // AND mHasDictionary is false)
+ // AND mSuggest.hasMainDictionary() is false)
// - There is a dictionary and the word is not in it
// Please note that if mSuggest is null, it means that everything is off: suggestion
// and correction, so we shouldn't try to show the hint
@@ -1747,7 +1792,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// to do with the autocorrection setting.
final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null
// If there is no dictionary the hint should be shown.
- && (!mHasDictionary
+ && (!mSuggest.hasMainDictionary()
// If "suggestion" is not in the dictionary, the hint should be shown.
|| !AutoCorrection.isValidWord(
mSuggest.getUnigramDictionaries(), suggestion, true));
@@ -1783,10 +1828,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
* Commits the chosen word to the text field and saves it for later retrieval.
*/
private void commitBestWord(CharSequence bestWord) {
- KeyboardSwitcher switcher = mKeyboardSwitcher;
+ final KeyboardSwitcher switcher = mKeyboardSwitcher;
if (!switcher.isKeyboardAvailable())
return;
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators);
SuggestedWords suggestedWords = mCandidateView.getSuggestions();
@@ -1811,7 +1856,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
mSettingsValues.mWordSeparators);
SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
- mKeyboardSwitcher.getKeyboardView(), sEmptyWordComposer, prevWord);
+ mKeyboardSwitcher.getKeyboardView(), sEmptyWordComposer, prevWord,
+ mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
if (builder.size() > 0) {
// Explicitly supply an empty typed word (the no-second-arg version of
@@ -1878,7 +1924,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
public boolean isCursorTouchingWord() {
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic == null) return false;
CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
CharSequence toRight = ic.getTextAfterCursor(1, 0);
@@ -1895,36 +1941,34 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return false;
}
- private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) {
+ // "ic" must not null
+ private boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
return TextUtils.equals(text, beforeText);
}
- private void revertLastWord(boolean deleteChar) {
+ // "ic" must not null
+ private void revertLastWord(final InputConnection ic) {
if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
return;
}
- final InputConnection ic = getCurrentInputConnection();
- final CharSequence punctuation = ic.getTextBeforeCursor(1, 0);
- if (deleteChar) ic.deleteSurroundingText(1, 0);
+ final CharSequence separator = ic.getTextBeforeCursor(1, 0);
+ ic.deleteSurroundingText(1, 0);
final CharSequence textToTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
- final int toDeleteLength = (!TextUtils.isEmpty(textToTheLeft)
- && mSettingsValues.isWordSeparator(textToTheLeft.charAt(0)))
- ? mCommittedLength - 1 : mCommittedLength;
- ic.deleteSurroundingText(toDeleteLength, 0);
-
- // Re-insert punctuation only when the deleted character was word separator and the
- // composing text wasn't equal to the auto-corrected text.
- if (deleteChar
- && !TextUtils.isEmpty(punctuation)
- && mSettingsValues.isWordSeparator(punctuation.charAt(0))
+ ic.deleteSurroundingText(mCommittedLength, 0);
+
+ // Re-insert "separator" only when the deleted character was word separator and the
+ // composing text wasn't equal to the auto-corrected text which can be found before
+ // the cursor.
+ if (!TextUtils.isEmpty(separator)
+ && mSettingsValues.isWordSeparator(separator.charAt(0))
&& !TextUtils.equals(mComposingStringBuilder, textToTheLeft)) {
ic.commitText(mComposingStringBuilder, 1);
TextEntryState.acceptedTyped(mComposingStringBuilder);
- ic.commitText(punctuation, 1);
- TextEntryState.typedCharacter(punctuation.charAt(0), true,
+ ic.commitText(separator, 1);
+ TextEntryState.typedCharacter(separator.charAt(0), true,
WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
// Clear composing text
mComposingStringBuilder.setLength(0);
@@ -1937,9 +1981,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mHandler.postUpdateSuggestions();
}
- private boolean revertDoubleSpace() {
+ // "ic" must not null
+ private boolean revertDoubleSpace(final InputConnection ic) {
mHandler.cancelDoubleSpacesTimer();
- final InputConnection ic = getCurrentInputConnection();
// Here we test whether we indeed have a period and a space before us. This should not
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
@@ -1978,16 +2022,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettingsValues);
initSuggest();
loadSettings();
- mKeyboardSwitcher.updateShiftState();
}
@Override
public void onPress(int primaryCode, boolean withSliding) {
- if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) {
+ final KeyboardSwitcher switcher = mKeyboardSwitcher;
+ switcher.registerWindowWidth();
+ if (switcher.isVibrateAndSoundFeedbackRequired()) {
vibrate();
playKeyClick(primaryCode);
}
- KeyboardSwitcher switcher = mKeyboardSwitcher;
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
switcher.onPressShift(withSliding);
@@ -2024,14 +2068,24 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
};
+ // update sound effect volume
+ private void updateSoundEffectVolume() {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (mAudioManager == null) return;
+ }
+ // This aligns with the current media volume minus 6dB
+ mFxVolume = (float) mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
+ / (float) mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) / 4.0f;
+ }
+
// update flags for silent mode
private void updateRingerMode() {
if (mAudioManager == null) {
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (mAudioManager == null) return;
}
- if (mAudioManager != null) {
- mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
- }
+ mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
}
private void playKeyClick(int primaryCode) {
@@ -2043,8 +2097,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
if (isSoundOn()) {
- // FIXME: Volume and enable should come from UI settings
- // FIXME: These should be triggered after auto-repeat logic
int sound = AudioManager.FX_KEYPRESS_STANDARD;
switch (primaryCode) {
case Keyboard.CODE_DELETE:
@@ -2057,7 +2109,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
sound = AudioManager.FX_KEYPRESS_SPACEBAR;
break;
}
- mAudioManager.playSoundEffect(sound, FX_VOLUME);
+ mAudioManager.playSoundEffect(sound, mFxVolume);
}
}
@@ -2083,9 +2135,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private void updateCorrectionMode() {
// TODO: cleanup messy flags
- mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
- && !mInputTypeNoAutoCorrect && mHasDictionary;
+ && !mInputTypeNoAutoCorrect;
mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled)
? Suggest.CORRECTION_FULL
: (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
@@ -2122,14 +2173,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
protected void launchSettings() {
- launchSettings(Settings.class);
+ launchSettingsClass(Settings.class);
}
public void launchDebugSettings() {
- launchSettings(DebugSettings.class);
+ launchSettingsClass(DebugSettings.class);
}
- protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) {
+ protected void launchSettingsClass(Class<? extends PreferenceActivity> settingsClass) {
handleClose();
Intent intent = new Intent();
intent.setClass(LatinIME.this, settingsClass);
@@ -2163,8 +2214,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
};
final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setIcon(R.drawable.ic_dialog_keyboard)
- .setNegativeButton(android.R.string.cancel, null)
.setItems(items, listener)
.setTitle(title);
showOptionDialogInternal(builder.create());
@@ -2191,8 +2240,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
};
final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setIcon(R.drawable.ic_dialog_keyboard)
- .setNegativeButton(android.R.string.cancel, null)
.setItems(items, listener)
.setTitle(title);
showOptionDialogInternal(builder.create());
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index e44ae29d9..4c2627be3 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -61,7 +61,7 @@ public class Settings extends InputMethodSettingsActivity
public static final String PREF_KEY_PREVIEW_POPUP_ON = "popup_on";
public static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
public static final String PREF_AUTO_CAP = "auto_cap";
- public static final String PREF_SETTINGS_KEY = "settings_key";
+ public static final String PREF_SHOW_SETTINGS_KEY = "show_settings_key";
public static final String PREF_VOICE_SETTINGS_KEY = "voice_mode";
public static final String PREF_INPUT_LANGUAGE = "input_language";
public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
@@ -103,6 +103,7 @@ public class Settings extends InputMethodSettingsActivity
public final String mMagicSpaceSwappers;
public final String mSuggestPuncs;
public final SuggestedWords mSuggestPuncList;
+ private final String mSymbolsExcludedFromWordSeparators;
// From preferences:
public final boolean mSoundOn; // Sound setting private to Latin IME (see mSilentModeOn)
@@ -118,6 +119,7 @@ public class Settings extends InputMethodSettingsActivity
public final boolean mBigramPredictionEnabled;
public final boolean mUseContactsDict;
+ private final boolean mShowSettingsKey;
private final boolean mVoiceKeyEnabled;
private final boolean mVoiceKeyOnMain;
@@ -151,10 +153,13 @@ public class Settings extends InputMethodSettingsActivity
mMagicSpaceSwappers = res.getString(R.string.magic_space_swapping_symbols);
String wordSeparators = mMagicSpaceStrippers + mMagicSpaceSwappers
+ res.getString(R.string.magic_space_promoting_symbols);
- final String notWordSeparators = res.getString(R.string.non_word_separator_symbols);
- for (int i = notWordSeparators.length() - 1; i >= 0; --i) {
- wordSeparators = wordSeparators.replace(notWordSeparators.substring(i, i + 1), "");
+ final String symbolsExcludedFromWordSeparators =
+ res.getString(R.string.symbols_excluded_from_word_separators);
+ for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) {
+ wordSeparators = wordSeparators.replace(
+ symbolsExcludedFromWordSeparators.substring(i, i + 1), "");
}
+ mSymbolsExcludedFromWordSeparators = symbolsExcludedFromWordSeparators;
mWordSeparators = wordSeparators;
mSuggestPuncs = res.getString(R.string.suggested_punctuations);
// TODO: it would be nice not to recreate this each time we change the configuration
@@ -165,21 +170,20 @@ public class Settings extends InputMethodSettingsActivity
mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
res.getBoolean(R.bool.config_default_sound_enabled));
-
mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
-
mAutoCorrectEnabled = isAutoCorrectEnabled(prefs, res);
mBigramSuggestionEnabled = mAutoCorrectEnabled
&& isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
mBigramPredictionEnabled = mBigramSuggestionEnabled
&& isBigramPredictionEnabled(prefs, res);
-
mAutoCorrectionThreshold = getAutoCorrectionThreshold(prefs, res);
-
mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
-
+ final boolean defaultShowSettingsKey = res.getBoolean(
+ R.bool.config_default_show_settings_key);
+ mShowSettingsKey = prefs.getBoolean(Settings.PREF_SHOW_SETTINGS_KEY,
+ defaultShowSettingsKey);
final String voiceModeMain = res.getString(R.string.voice_mode_main);
final String voiceModeOff = res.getString(R.string.voice_mode_off);
final String voiceMode = prefs.getString(PREF_VOICE_SETTINGS_KEY, voiceModeMain);
@@ -197,6 +201,10 @@ public class Settings extends InputMethodSettingsActivity
return mWordSeparators.contains(String.valueOf((char)code));
}
+ public boolean isSymbolExcludedFromWordSeparators(int code) {
+ return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
+ }
+
public boolean isMagicSpaceStripper(int code) {
return mMagicSpaceStrippers.contains(String.valueOf((char)code));
}
@@ -284,6 +292,10 @@ public class Settings extends InputMethodSettingsActivity
return builder.setIsPunctuationSuggestions().build();
}
+ public boolean isSettingsKeyEnabled(EditorInfo attribute) {
+ return mShowSettingsKey;
+ }
+
public boolean isVoiceKeyEnabled(EditorInfo attribute) {
final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
final int inputType = (attribute != null) ? attribute.inputType : 0;
@@ -298,9 +310,9 @@ public class Settings extends InputMethodSettingsActivity
private PreferenceScreen mInputLanguageSelection;
private ListPreference mVoicePreference;
- private ListPreference mSettingsKeyPreference;
+ private CheckBoxPreference mShowSettingsKeyPreference;
private ListPreference mShowCorrectionSuggestionsPreference;
- private ListPreference mAutoCorrectionThreshold;
+ private ListPreference mAutoCorrectionThresholdPreference;
private ListPreference mKeyPreviewPopupDismissDelay;
// Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
private CheckBoxPreference mBigramSuggestion;
@@ -317,7 +329,7 @@ public class Settings extends InputMethodSettingsActivity
private void ensureConsistencyOfAutoCorrectionSettings() {
final String autoCorrectionOff = getResources().getString(
R.string.auto_correction_threshold_mode_index_off);
- final String currentSetting = mAutoCorrectionThreshold.getValue();
+ final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
mBigramSuggestion.setEnabled(!currentSetting.equals(autoCorrectionOff));
mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
}
@@ -345,7 +357,7 @@ public class Settings extends InputMethodSettingsActivity
mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES);
mInputLanguageSelection.setOnPreferenceClickListener(this);
mVoicePreference = (ListPreference) findPreference(PREF_VOICE_SETTINGS_KEY);
- mSettingsKeyPreference = (ListPreference) findPreference(PREF_SETTINGS_KEY);
+ mShowSettingsKeyPreference = (CheckBoxPreference) findPreference(PREF_SHOW_SETTINGS_KEY);
mShowCorrectionSuggestionsPreference =
(ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING);
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
@@ -355,7 +367,8 @@ public class Settings extends InputMethodSettingsActivity
mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
.equals(mVoiceModeOff));
- mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
+ mAutoCorrectionThresholdPreference =
+ (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
@@ -376,7 +389,7 @@ public class Settings extends InputMethodSettingsActivity
final boolean showSettingsKeyOption = res.getBoolean(
R.bool.config_enable_show_settings_key_option);
if (!showSettingsKeyOption) {
- generalSettings.removePreference(mSettingsKeyPreference);
+ generalSettings.removePreference(mShowSettingsKeyPreference);
}
final boolean showVoiceKeyOption = res.getBoolean(
@@ -457,7 +470,6 @@ public class Settings extends InputMethodSettingsActivity
} else {
getPreferenceScreen().removePreference(mVoicePreference);
}
- updateSettingsKeySummary();
updateShowCorrectionSuggestionsSummary();
updateKeyPreviewPopupDelaySummary();
}
@@ -489,7 +501,6 @@ public class Settings extends InputMethodSettingsActivity
mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
.equals(mVoiceModeOff));
updateVoiceModeSummary();
- updateSettingsKeySummary();
updateShowCorrectionSuggestionsSummary();
updateKeyPreviewPopupDelaySummary();
}
@@ -513,12 +524,6 @@ public class Settings extends InputMethodSettingsActivity
mShowCorrectionSuggestionsPreference.getValue())]);
}
- private void updateSettingsKeySummary() {
- mSettingsKeyPreference.setSummary(
- getResources().getStringArray(R.array.settings_key_modes)
- [mSettingsKeyPreference.findIndexOfValue(mSettingsKeyPreference.getValue())]);
- }
-
private void updateKeyPreviewPopupDelaySummary() {
final ListPreference lp = mKeyPreviewPopupDismissDelay;
lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]);
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index f10b1b845..0a391a77e 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -318,7 +318,7 @@ public class SubtypeSwitcher {
// when the API level is 10 or previous.
mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
}
- }.execute();
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public Drawable getShortcutIcon() {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index c2452b947..a2d66f398 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,6 +22,8 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.View;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -86,6 +88,7 @@ public class Suggest implements Dictionary.WordCallback {
private AutoCorrection mAutoCorrection;
private Dictionary mMainDict;
+ private ContactsDictionary mContactsDict;
private WhitelistDictionary mWhiteListDictionary;
private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
@@ -189,10 +192,16 @@ public class Suggest implements Dictionary.WordCallback {
mCorrectionMode = mode;
}
+ // The main dictionary could have been loaded asynchronously. Don't cache the return value
+ // of this method.
public boolean hasMainDictionary() {
return mMainDict != null;
}
+ public ContactsDictionary getContactsDictionary() {
+ return mContactsDict;
+ }
+
public Map<String, Dictionary> getUnigramDictionaries() {
return mUnigramDictionaries;
}
@@ -214,7 +223,8 @@ public class Suggest implements Dictionary.WordCallback {
* the contacts dictionary by passing null to this method. In this case no contacts dictionary
* won't be used.
*/
- public void setContactsDictionary(Dictionary contactsDictionary) {
+ public void setContactsDictionary(ContactsDictionary contactsDictionary) {
+ mContactsDict = contactsDictionary;
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
}
@@ -263,9 +273,10 @@ public class Suggest implements Dictionary.WordCallback {
* @param prevWordForBigram previous word (used only for bigram)
* @return suggested words object.
*/
- public SuggestedWords getSuggestions(View view, WordComposer wordComposer,
- CharSequence prevWordForBigram) {
- return getSuggestedWordBuilder(view, wordComposer, prevWordForBigram).build();
+ public SuggestedWords getSuggestions(final View view, final WordComposer wordComposer,
+ final CharSequence prevWordForBigram, final ProximityInfo proximityInfo) {
+ return getSuggestedWordBuilder(view, wordComposer, prevWordForBigram,
+ proximityInfo).build();
}
private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
@@ -299,8 +310,9 @@ public class Suggest implements Dictionary.WordCallback {
}
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
- public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
- CharSequence prevWordForBigram) {
+ public SuggestedWords.Builder getSuggestedWordBuilder(final View view,
+ final WordComposer wordComposer, CharSequence prevWordForBigram,
+ final ProximityInfo proximityInfo) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
mAutoCorrection.init();
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
@@ -365,7 +377,7 @@ public class Suggest implements Dictionary.WordCallback {
if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
continue;
final Dictionary dictionary = mUnigramDictionaries.get(key);
- dictionary.getWords(wordComposer, this);
+ dictionary.getWords(wordComposer, this, proximityInfo);
}
}
CharSequence autoText = null;
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index b77cbd199..c1c46fa47 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -31,7 +31,7 @@ public class SuggestedWords {
public final boolean mTypedWordValid;
public final boolean mHasMinimalSuggestion;
public final boolean mIsPunctuationSuggestions;
- public final List<SuggestedWordInfo> mSuggestedWordInfoList;
+ private final List<SuggestedWordInfo> mSuggestedWordInfoList;
private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
boolean hasMinimalSuggestion, boolean isPunctuationSuggestions,
@@ -55,6 +55,10 @@ public class SuggestedWords {
return mWords.get(pos);
}
+ public SuggestedWordInfo getInfo(int pos) {
+ return mSuggestedWordInfoList != null ? mSuggestedWordInfoList.get(pos) : null;
+ }
+
public boolean hasAutoCorrectionWord() {
return mHasMinimalSuggestion && size() > 1 && !mTypedWordValid;
}
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index f93d24fe6..6608d8268 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -26,6 +26,8 @@ import android.net.Uri;
import android.os.RemoteException;
import android.provider.UserDictionary.Words;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
public class UserDictionary extends ExpandableDictionary {
private static final String[] PROJECTION_QUERY = {
@@ -150,8 +152,9 @@ public class UserDictionary extends ExpandableDictionary {
}
@Override
- public synchronized void getWords(final WordComposer codes, final WordCallback callback) {
- super.getWords(codes, callback);
+ public synchronized void getWords(final WordComposer codes, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
+ super.getWords(codes, callback, proximityInfo);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 6bdc0a857..1a6260a4e 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -111,35 +111,43 @@ public class Utils {
}
}
- public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) {
+ public static boolean hasMultipleEnabledIMEsOrSubtypes(
+ final InputMethodManagerCompatWrapper imm,
+ final boolean shouldIncludeAuxiliarySubtypes) {
final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
- // Filters out IMEs that have auxiliary subtypes only (including either implicitly or
- // explicitly enabled ones).
- final ArrayList<InputMethodInfoCompatWrapper> filteredImis =
- new ArrayList<InputMethodInfoCompatWrapper>();
+ // Number of the filtered IMEs
+ int filteredImisCount = 0;
- outerloop:
for (InputMethodInfoCompatWrapper imi : enabledImis) {
// We can return true immediately after we find two or more filtered IMEs.
- if (filteredImis.size() > 1) return true;
+ if (filteredImisCount > 1) return true;
final List<InputMethodSubtypeCompatWrapper> subtypes =
imm.getEnabledInputMethodSubtypeList(imi, true);
- // IMEs that have no subtypes should be included.
+ // IMEs that have no subtypes should be counted.
if (subtypes.isEmpty()) {
- filteredImis.add(imi);
+ ++filteredImisCount;
continue;
}
- // IMEs that have one or more non-auxiliary subtypes should be included.
+
+ int auxCount = 0;
for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
- if (!subtype.isAuxiliary()) {
- filteredImis.add(imi);
- continue outerloop;
+ if (subtype.isAuxiliary()) {
+ ++auxCount;
}
}
+ final int nonAuxCount = subtypes.size() - auxCount;
+
+ // IMEs that have one or more non-auxiliary subtypes should be counted.
+ // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+ // subtypes should be counted as well.
+ if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+ ++filteredImisCount;
+ continue;
+ }
}
- return filteredImis.size() > 1
+ return filteredImisCount > 1
// imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
// input method subtype (The current IME should be LatinIME.)
|| imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
@@ -190,6 +198,18 @@ public class Utils {
}
}
+ public static boolean canBeFollowedByPeriod(final int codePoint) {
+ // TODO: Check again whether there really ain't a better way to check this.
+ // TODO: This should probably be language-dependant...
+ return Character.isLetterOrDigit(codePoint)
+ || codePoint == Keyboard.CODE_SINGLE_QUOTE
+ || codePoint == Keyboard.CODE_DOUBLE_QUOTE
+ || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
+ || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
+ || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
+ || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
+ }
+
/* package */ static class RingCharBuffer {
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -546,11 +566,11 @@ public class Utils {
}
}
- public static int getKeyboardMode(EditorInfo attribute) {
- if (attribute == null)
+ public static int getKeyboardMode(EditorInfo editorInfo) {
+ if (editorInfo == null)
return KeyboardId.MODE_TEXT;
- final int inputType = attribute.inputType;
+ final int inputType = editorInfo.inputType;
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
switch (inputType & InputType.TYPE_MASK_CLASS) {
@@ -587,11 +607,11 @@ public class Utils {
}
public static boolean inPrivateImeOptions(String packageName, String key,
- EditorInfo attribute) {
- if (attribute == null)
+ EditorInfo editorInfo) {
+ if (editorInfo == null)
return false;
return containsInCsv(packageName != null ? packageName + "." + key : key,
- attribute.privateImeOptions);
+ editorInfo.privateImeOptions);
}
/**
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
index 4377373d2..639c96681 100644
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
@@ -21,6 +21,8 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
import java.util.HashMap;
public class WhitelistDictionary extends Dictionary {
@@ -89,7 +91,8 @@ public class WhitelistDictionary extends Dictionary {
// Not used for WhitelistDictionary. We use getWhitelistedWord() in Suggest.java instead
@Override
- public void getWords(WordComposer composer, WordCallback callback) {
+ public void getWords(final WordComposer composer, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 156510b40..649774d78 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -16,19 +16,183 @@
package com.android.inputmethod.latin.spellcheck;
+import android.content.Intent;
+import android.content.res.Resources;
import android.service.textservice.SpellCheckerService;
+import android.service.textservice.SpellCheckerService.Session;
+import android.util.Log;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
+import com.android.inputmethod.compat.ArraysCompatUtils;
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.Dictionary.DataType;
+import com.android.inputmethod.latin.Dictionary.WordCallback;
+import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.WordComposer;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
*/
public class AndroidSpellCheckerService extends SpellCheckerService {
+ private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final int POOL_SIZE = 2;
+
+ private final static String[] emptyArray = new String[0];
+ private Map<String, DictionaryPool> mDictionaryPools =
+ Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+
@Override
- public SuggestionsInfo getSuggestions(TextInfo textInfo, int suggestionsLimit,
- String locale) {
- // TODO: implement this
- String[] candidates = new String[] {"candidate1", "candidate2", "candidate3"};
- return new SuggestionsInfo(0, candidates);
+ public Session createSession() {
+ return new AndroidSpellCheckerSession();
+ }
+
+ private static class SuggestionsGatherer implements WordCallback {
+ private final int DEFAULT_SUGGESTION_LENGTH = 16;
+ private final String[] mSuggestions;
+ private final int[] mScores;
+ private final int mMaxLength;
+ private int mLength = 0;
+
+ SuggestionsGatherer(final int maxLength) {
+ mMaxLength = maxLength;
+ mSuggestions = new String[mMaxLength];
+ mScores = new int[mMaxLength];
+ }
+
+ @Override
+ synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
+ int dicTypeId, DataType dataType) {
+ final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score);
+ // binarySearch returns the index if the element exists, and -<insertion index> - 1
+ // if it doesn't. See documentation for binarySearch.
+ final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
+
+ if (mLength < mMaxLength) {
+ final int copyLen = mLength - insertIndex;
+ ++mLength;
+ System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
+ System.arraycopy(mSuggestions, insertIndex, mSuggestions, insertIndex + 1, copyLen);
+ } else {
+ if (insertIndex == 0) return true;
+ System.arraycopy(mScores, 1, mScores, 0, insertIndex);
+ System.arraycopy(mSuggestions, 1, mSuggestions, 0, insertIndex);
+ }
+ mScores[insertIndex] = score;
+ mSuggestions[insertIndex] = new String(word, wordOffset, wordLength);
+
+ return true;
+ }
+
+ public String[] getGatheredSuggestions() {
+ if (0 == mLength) return null;
+
+ final String[] results = new String[mLength];
+ for (int i = mLength - 1; i >= 0; --i) {
+ results[mLength - i - 1] = mSuggestions[i];
+ }
+ return results;
+ }
+ }
+
+ @Override
+ public boolean onUnbind(final Intent intent) {
+ final Map<String, DictionaryPool> oldPools = mDictionaryPools;
+ mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+ for (DictionaryPool pool : oldPools.values()) {
+ pool.close();
+ }
+ return false;
+ }
+
+ private DictionaryPool getDictionaryPool(final String locale) {
+ DictionaryPool pool = mDictionaryPools.get(locale);
+ if (null == pool) {
+ final Locale localeObject = Utils.constructLocaleFromString(locale);
+ pool = new DictionaryPool(POOL_SIZE, this, localeObject);
+ mDictionaryPools.put(locale, pool);
+ }
+ return pool;
+ }
+
+ public DictAndProximity createDictAndProximity(final Locale locale) {
+ final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo();
+ final Resources resources = getResources();
+ final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
+ final Dictionary dictionary =
+ DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId);
+ return new DictAndProximity(dictionary, proximityInfo);
+ }
+
+ private class AndroidSpellCheckerSession extends Session {
+ // Immutable, but need the locale which is not available in the constructor yet
+ DictionaryPool mDictionaryPool;
+
+ @Override
+ public void onCreate() {
+ mDictionaryPool = getDictionaryPool(getLocale());
+ }
+
+ // Note : this must be reentrant
+ /**
+ * Gets a list of suggestions for a specific string. This returns a list of possible
+ * corrections for the text passed as an argument. It may split or group words, and
+ * even perform grammatical analysis.
+ */
+ @Override
+ public SuggestionsInfo onGetSuggestions(final TextInfo textInfo,
+ final int suggestionsLimit) {
+ final String text = textInfo.getText();
+
+ final SuggestionsGatherer suggestionsGatherer =
+ new SuggestionsGatherer(suggestionsLimit);
+ final WordComposer composer = new WordComposer();
+ final int length = text.length();
+ for (int i = 0; i < length; ++i) {
+ final int character = text.codePointAt(i);
+ final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character);
+ final int[] proximities;
+ if (-1 == proximityIndex) {
+ proximities = new int[] { character };
+ } else {
+ proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
+ proximityIndex, proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
+ }
+ composer.add(character, proximities,
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ }
+
+ boolean isInDict = true;
+ try {
+ final DictAndProximity dictInfo = mDictionaryPool.take();
+ dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
+ dictInfo.mProximityInfo);
+ isInDict = dictInfo.mDictionary.isValidWord(text);
+ if (!mDictionaryPool.offer(dictInfo)) {
+ Log.e(TAG, "Can't re-insert a dictionary into its pool");
+ }
+ } catch (InterruptedException e) {
+ // I don't think this can happen.
+ return new SuggestionsInfo(0, new String[0]);
+ }
+
+ final String[] suggestions = suggestionsGatherer.getGatheredSuggestions();
+
+ final int flags =
+ (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0)
+ | (null != suggestions
+ ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0);
+ return new SuggestionsInfo(flags, suggestions);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
index eb740e111..3dbbd40cd 100644
--- a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
@@ -14,16 +14,19 @@
* the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.spellcheck;
-import android.content.Context;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.keyboard.ProximityInfo;
-import java.util.List;
-import java.util.Locale;
-
-class PrivateBinaryDictionaryGetter {
- private PrivateBinaryDictionaryGetter() {}
- public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context) {
- return null;
+/**
+ * A simple container for both a Dictionary and a ProximityInfo.
+ */
+public class DictAndProximity {
+ public final Dictionary mDictionary;
+ public final ProximityInfo mProximityInfo;
+ public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) {
+ mDictionary = dictionary;
+ mProximityInfo = proximityInfo;
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
new file mode 100644
index 000000000..ee294f6b0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.android.inputmethod.latin.spellcheck;
+
+import android.content.Context;
+
+import java.util.Locale;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A blocking queue that creates dictionaries up to a certain limit as necessary.
+ */
+public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
+ private final AndroidSpellCheckerService mService;
+ private final int mMaxSize;
+ private final Locale mLocale;
+ private int mSize;
+ private volatile boolean mClosed;
+
+ public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
+ final Locale locale) {
+ super();
+ mMaxSize = maxSize;
+ mService = service;
+ mLocale = locale;
+ mSize = 0;
+ mClosed = false;
+ }
+
+ @Override
+ public DictAndProximity take() throws InterruptedException {
+ final DictAndProximity dict = poll();
+ if (null != dict) return dict;
+ synchronized(this) {
+ if (mSize >= mMaxSize) {
+ // Our pool is already full. Wait until some dictionary is ready.
+ return super.take();
+ } else {
+ ++mSize;
+ return mService.createDictAndProximity(mLocale);
+ }
+ }
+ }
+
+ public void close() {
+ synchronized(this) {
+ mClosed = true;
+ for (DictAndProximity dict : this) {
+ dict.mDictionary.close();
+ }
+ clear();
+ }
+ }
+
+ @Override
+ public boolean offer(final DictAndProximity dict) {
+ if (mClosed) {
+ dict.mDictionary.close();
+ return false;
+ } else {
+ return super.offer(dict);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java
deleted file mode 100644
index 63c6d69d7..000000000
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.inputmethod.latin.spellcheck;
-
-import android.content.Context;
-import android.content.res.Resources;
-
-import com.android.inputmethod.compat.ArraysCompatUtils;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.Dictionary.DataType;
-import com.android.inputmethod.latin.Dictionary.WordCallback;
-import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.Utils;
-import com.android.inputmethod.latin.WordComposer;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Implements spell checking methods.
- */
-public class SpellChecker {
-
- public final Dictionary mDictionary;
-
- public SpellChecker(final Context context, final Locale locale) {
- final Resources resources = context.getResources();
- final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
- mDictionary = DictionaryFactory.createDictionaryFromManager(context, locale,
- fallbackResourceId);
- }
-
- // Note : this must be reentrant
- /**
- * Finds out whether a word is in the dictionary or not.
- *
- * @param text the sequence containing the word to check for.
- * @param start the index of the first character of the word in text.
- * @param end the index of the next-to-last character in text.
- * @return true if the word is in the dictionary, false otherwise.
- */
- public boolean isCorrect(final CharSequence text, final int start, final int end) {
- return mDictionary.isValidWord(text.subSequence(start, end));
- }
-
- private static class SuggestionsGatherer implements WordCallback {
- private final int DEFAULT_SUGGESTION_LENGTH = 16;
- private final List<String> mSuggestions = new LinkedList<String>();
- private int[] mScores = new int[DEFAULT_SUGGESTION_LENGTH];
- private int mLength = 0;
-
- @Override
- synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
- int dicTypeId, DataType dataType) {
- if (mLength >= mScores.length) {
- final int newLength = mScores.length * 2;
- mScores = new int[newLength];
- }
- final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score);
- // binarySearch returns the index if the element exists, and -<insertion index> - 1
- // if it doesn't. See documentation for binarySearch.
- final int insertionIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
- System.arraycopy(mScores, insertionIndex, mScores, insertionIndex + 1,
- mLength - insertionIndex);
- mLength += 1;
- mScores[insertionIndex] = score;
- mSuggestions.add(insertionIndex, new String(word, wordOffset, wordLength));
- return true;
- }
-
- public List<String> getGatheredSuggestions() {
- return mSuggestions;
- }
- }
-
- // Note : this must be reentrant
- /**
- * Gets a list of suggestions for a specific string.
- *
- * This returns a list of possible corrections for the text passed as an
- * arguments. It may split or group words, and even perform grammatical
- * analysis.
- *
- * @param text the sequence containing the word to check for.
- * @param start the index of the first character of the word in text.
- * @param end the index of the next-to-last character in text.
- * @return a list of possible suggestions to replace the text.
- */
- public List<String> getSuggestions(final CharSequence text, final int start, final int end) {
- final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer();
- final WordComposer composer = new WordComposer();
- for (int i = start; i < end; ++i) {
- int character = text.charAt(i);
- composer.add(character, new int[] { character },
- WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
- }
- mDictionary.getWords(composer, suggestionsGatherer);
- return suggestionsGatherer.getGatheredSuggestions();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
new file mode 100644
index 000000000..abcf7e52a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.android.inputmethod.latin.spellcheck;
+
+import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public class SpellCheckerProximityInfo {
+ final private static int NUL = KeyDetector.NOT_A_CODE;
+
+ // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
+ // native code - this value is passed at creation of the binary object and reused
+ // as the size of the passed array afterwards so they can't be different.
+ final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
+
+ // This is a map from the code point to the index in the PROXIMITY array.
+ // At the time the native code to read the binary dictionary needs the proximity info be passed
+ // as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input character.
+ // Since we need to build such an array, we want to be able to search in our big proximity data
+ // quickly by character, and a map is probably the best way to do this.
+ final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+
+ // The proximity here is the union of
+ // - the proximity for a QWERTY keyboard.
+ // - the proximity for an AZERTY keyboard.
+ // - the proximity for a QWERTZ keyboard.
+ // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other.
+ //
+ // The reasoning behind this construction is, almost any alphabetic text we may want
+ // to spell check has been entered with one of the keyboards above. Also, specifically
+ // to English, many spelling errors consist of the last vowel of the word being wrong
+ // because in English vowels tend to merge with each other in pronunciation.
+ final public static int[] PROXIMITY = {
+ 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
+ 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
+ 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ };
+ static {
+ for (int i = 0; i < PROXIMITY.length; i += ROW_SIZE) {
+ if (NUL != PROXIMITY[i]) INDICES.put(PROXIMITY[i], i);
+ }
+ }
+ public static int getIndexOf(int characterCode) {
+ final Integer result = INDICES.get(characterCode);
+ if (null == result) return -1;
+ return result;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
new file mode 100644
index 000000000..483679a55
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.android.inputmethod.latin.spellcheck;
+
+import com.android.inputmethod.latin.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+import java.util.List;
+
+/**
+ * Spell checker preference screen.
+ */
+public class SpellCheckerSettingsActivity extends PreferenceActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public Intent getIntent() {
+ final Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SpellCheckerSettingsFragment.class.getName());
+ modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ return modIntent;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
new file mode 100644
index 000000000..9b821be35
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.android.inputmethod.latin.spellcheck;
+
+import com.android.inputmethod.latin.R;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+/**
+ * Preference screen.
+ */
+public class SpellCheckerSettingsFragment extends PreferenceFragment {
+ private static final String TAG = SpellCheckerSettingsFragment.class.getSimpleName();
+
+ /**
+ * Empty constructor for fragment generation.
+ */
+ public SpellCheckerSettingsFragment() {
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ addPreferencesFromResource(R.xml.spell_checker_settings);
+ }
+}