From 9a5d2ba03e7c8caf82cce64aa254d2177a72a8cf Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Thu, 10 Feb 2011 16:26:38 +0900 Subject: Stop listening on shared preferences change. It appears it is not useful to listen to this change, because in any case, settings will be reloaded when the view is shown again or when IME is changed. Change-Id: I08dddccc630b01718da7fab011ce14f189dd31c3 --- java/src/com/android/inputmethod/latin/LatinIME.java | 17 +---------------- .../com/android/inputmethod/latin/SubtypeSwitcher.java | 8 -------- 2 files changed, 1 insertion(+), 24 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 67a9f3203..fcd9229ec 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -81,8 +81,7 @@ import java.util.Locale; /** * Input method implementation for Qwerty'ish keyboard. */ -public class LatinIME extends InputMethodService implements KeyboardActionListener, - SharedPreferences.OnSharedPreferenceChangeListener { +public class LatinIME extends InputMethodService implements KeyboardActionListener { private static final String TAG = "LatinIME"; private static final boolean PERF_DEBUG = false; private static final boolean TRACE = false; @@ -395,7 +394,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mReceiver, filter); mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler); - prefs.registerOnSharedPreferenceChangeListener(this); } /** @@ -1941,19 +1939,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen switcher.updateShiftState(); } - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) { - mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key); - if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) { - mRefreshKeyboardRequired = true; - } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) { - mReCorrectionEnabled = sharedPreferences.getBoolean( - Settings.PREF_RECORRECTION_ENABLED, - mResources.getBoolean(R.bool.config_default_recorrection_enabled)); - } - } - @Override public void onSwipeDown() { if (mConfigSwipeDownDismissKeyboardEnabled) diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index d8c89a0d4..dee3da425 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -472,14 +472,6 @@ public class SubtypeSwitcher { } } - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (mConfigUseSpacebarLanguageSwitcher) { - if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) { - mLanguageSwitcher.loadLocales(sharedPreferences); - } - } - } - /** * Change system locale for this application * @param newLocale -- cgit v1.2.3-83-g751a From 5b85d032cd0ade501fa967fde33d4309f2b78f07 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Thu, 10 Feb 2011 16:28:42 +0900 Subject: Add stack dumper helper function. Change-Id: I78e5aef3cc6abfeb764a7e8a432f063f18d56da2 --- java/src/com/android/inputmethod/latin/Utils.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index e980d3a30..aafafbf2f 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -261,6 +261,19 @@ public class Utils { return dp[sl][tl]; } + // Get the current stack trace + public static String getStackTrace() { + StringBuilder sb = new StringBuilder(); + try { + throw new RuntimeException(); + } catch (RuntimeException e) { + StackTraceElement[] frames = e.getStackTrace(); + // Start at 1 because the first frame is here and we don't care about it + for (int j = 1; j < frames.length; ++j) sb.append(frames[j].toString() + "\n"); + } + return sb.toString(); + } + // In dictionary.cpp, getSuggestion() method, // suggestion scores are computed using the below formula. // original score (called 'frequency') -- cgit v1.2.3-83-g751a From b1e05d6638b7eab3c43d9b6b83970cd7a1867eee Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Mon, 14 Feb 2011 23:55:16 +0900 Subject: Fix NPE bug: 3446590 Change-Id: Iea5bd8f63f3322496b5d68539f0215d5d01e68d1 --- java/src/com/android/inputmethod/latin/UserDictionary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java index 56ee5b9e7..db5d9244f 100644 --- a/java/src/com/android/inputmethod/latin/UserDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserDictionary.java @@ -138,7 +138,7 @@ public class UserDictionary extends ExpandableDictionary { private void addWords(Cursor cursor) { clearDictionary(); - + if (cursor == null) return; final int maxWordLength = getMaxWordLength(); if (cursor.moveToFirst()) { final int indexWord = cursor.getColumnIndex(Words.WORD); -- cgit v1.2.3-83-g751a From a7b2ac26ee2b9aef6f8cd95849c7d8edbff5082b Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Tue, 15 Feb 2011 17:13:52 +0900 Subject: Remove a useless flag. This has become useless when LatinIME stopped being a SharedPreferenceListener. Change-Id: I708ec3c3a299ead18548c85b435b45558c984e8d --- java/src/com/android/inputmethod/latin/LatinIME.java | 6 ------ 1 file changed, 6 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index fcd9229ec..cb1ff41dd 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -185,7 +185,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Keeps track of most recently inserted text (multi-character key) for reverting private CharSequence mEnteredText; - private boolean mRefreshKeyboardRequired; private final ArrayList mWordHistory = new ArrayList(); @@ -521,11 +520,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeSwitcher.updateParametersOnStartInputView(); - if (mRefreshKeyboardRequired) { - mRefreshKeyboardRequired = false; - onRefreshKeyboard(); - } - TextEntryState.newSession(this); // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to -- cgit v1.2.3-83-g751a From 887f11ee43ad621aa6ad93d535ab7f48dec73fc7 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Thu, 10 Feb 2011 20:53:58 +0900 Subject: Remove next letters frequency handling Bug: 3428942 Change-Id: Id62f467ce4e50c60a56d59bf96770e799a4659e2 --- .../android/inputmethod/keyboard/KeyDetector.java | 9 +- .../inputmethod/keyboard/KeyboardSwitcher.java | 6 -- .../inputmethod/keyboard/LatinKeyboard.java | 116 +-------------------- .../keyboard/MiniKeyboardKeyDetector.java | 18 ++-- .../inputmethod/keyboard/ProximityKeyDetector.java | 101 ++++++++++++++---- .../inputmethod/latin/BinaryDictionary.java | 13 +-- .../com/android/inputmethod/latin/Dictionary.java | 13 +-- .../inputmethod/latin/ExpandableDictionary.java | 16 +-- .../com/android/inputmethod/latin/LatinIME.java | 7 -- .../src/com/android/inputmethod/latin/Suggest.java | 26 ++--- .../android/inputmethod/latin/UserDictionary.java | 5 +- .../android/inputmethod/latin/WordComposer.java | 4 + ..._android_inputmethod_latin_BinaryDictionary.cpp | 12 +-- native/src/defines.h | 3 + native/src/dictionary.h | 6 +- native/src/unigram_dictionary.cpp | 12 +-- native/src/unigram_dictionary.h | 4 +- .../inputmethod/latin/UserBigramSuggestHelper.java | 2 +- 18 files changed, 137 insertions(+), 236 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java index 3979fb53e..1a4f90195 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java @@ -22,6 +22,7 @@ import java.util.List; public abstract class KeyDetector { public static final int NOT_A_KEY = -1; + public static final int NOT_A_CODE = -1; protected Keyboard mKeyboard; @@ -105,10 +106,10 @@ public abstract class KeyDetector { * * @param x The x-coordinate of a touch point * @param y The y-coordinate of a touch point - * @param allKeys All nearby key indices are returned in this array + * @param allCodes All nearby key code except functional key are returned in this array * @return The nearest key index */ - abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys); + abstract public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes); /** * Compute the most common key width in order to use it as proximity key detection threshold. @@ -116,14 +117,14 @@ public abstract class KeyDetector { * @param keyboard The keyboard to compute the most common key width * @return The most common key width in the keyboard */ - public static int getMostCommonKeyWidth(Keyboard keyboard) { + public static int getMostCommonKeyWidth(final Keyboard keyboard) { if (keyboard == null) return 0; final List keys = keyboard.getKeys(); if (keys == null || keys.size() == 0) return 0; final HashMap histogram = new HashMap(); int maxCount = 0; int mostCommonWidth = 0; - for (Key key : keys) { + for (final Key key : keys) { final Integer width = key.mWidth + key.mGap; Integer count = histogram.get(width); if (count == null) diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 2648ff3d4..9b98f2878 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -288,12 +288,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return null; } - public void setPreferredLetters(int[] frequencies) { - LatinKeyboard latinKeyboard = getLatinKeyboard(); - if (latinKeyboard != null) - latinKeyboard.setPreferredLetters(frequencies); - } - public void keyReleased() { LatinKeyboard latinKeyboard = getLatinKeyboard(); if (latinKeyboard != null) diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java index ffb8d6410..b6eab1f3b 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java @@ -21,6 +21,7 @@ import com.android.inputmethod.latin.SubtypeSwitcher; import android.content.Context; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -31,17 +32,12 @@ import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.util.Log; import java.util.List; import java.util.Locale; // TODO: We should remove this class public class LatinKeyboard extends Keyboard { - - private static final boolean DEBUG_PREFERRED_LETTER = false; - private static final String TAG = "LatinKeyboard"; - public static final int OPACITY_FULLY_OPAQUE = 255; private static final int SPACE_LED_LENGTH_PERCENT = 80; @@ -69,15 +65,7 @@ public class LatinKeyboard extends Keyboard { private final Drawable mEnabledShortcutIcon; private final Drawable mDisabledShortcutIcon; - private int[] mPrefLetterFrequencies; - private int mPrefLetter; - private int mPrefLetterX; - private int mPrefLetterY; - private int mPrefDistance; - private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f; - private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f; - private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f; // Minimum width of space key preview (proportional to keyboard width) private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f; // Height in space key the language name will be drawn. (proportional to space key height) @@ -265,7 +253,7 @@ public class LatinKeyboard extends Keyboard { final boolean allowVariableTextSize = true; final String language = layoutSpacebar(paint, subtypeSwitcher.getInputLocale(), mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height, - getTextSizeFromTheme(textStyle, defaultTextSize), + getTextSizeFromTheme(mContext.getTheme(), textStyle, defaultTextSize), allowVariableTextSize); // Draw language text with shadow @@ -334,18 +322,9 @@ public class LatinKeyboard extends Keyboard { return mSpaceDragLastDiff > 0 ? 1 : -1; } - public void setPreferredLetters(int[] frequencies) { - mPrefLetterFrequencies = frequencies; - mPrefLetter = 0; - } - public void keyReleased() { mCurrentlyInSpace = false; mSpaceDragLastDiff = 0; - mPrefLetter = 0; - mPrefLetterX = 0; - mPrefLetterY = 0; - mPrefDistance = Integer.MAX_VALUE; if (mSpaceKey != null) { updateLocaleDrag(Integer.MAX_VALUE); } @@ -381,80 +360,6 @@ public class LatinKeyboard extends Keyboard { return isOnSpace; } } - } else if (mPrefLetterFrequencies != null) { - // New coordinate? Reset - if (mPrefLetterX != x || mPrefLetterY != y) { - mPrefLetter = 0; - mPrefDistance = Integer.MAX_VALUE; - } - // Handle preferred next letter - final int[] pref = mPrefLetterFrequencies; - if (mPrefLetter > 0) { - if (DEBUG_PREFERRED_LETTER) { - if (mPrefLetter == code && !key.isOnKey(x, y)) { - Log.d(TAG, "CORRECTED !!!!!!"); - } - } - return mPrefLetter == code; - } else { - final boolean isOnKey = key.isOnKey(x, y); - int[] nearby = getNearestKeys(x, y); - List nearbyKeys = getKeys(); - if (isOnKey) { - // If it's a preferred letter - if (inPrefList(code, pref)) { - // Check if its frequency is much lower than a nearby key - mPrefLetter = code; - mPrefLetterX = x; - mPrefLetterY = y; - for (int i = 0; i < nearby.length; i++) { - Key k = nearbyKeys.get(nearby[i]); - if (k != key && inPrefList(k.mCode, pref)) { - final int dist = distanceFrom(k, x, y); - if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_LOW_PROB) && - (pref[k.mCode] > pref[mPrefLetter] * 3)) { - mPrefLetter = k.mCode; - mPrefDistance = dist; - if (DEBUG_PREFERRED_LETTER) { - Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!"); - } - break; - } - } - } - - return mPrefLetter == code; - } - } - - // Get the surrounding keys and intersect with the preferred list - // For all in the intersection - // if distance from touch point is within a reasonable distance - // make this the pref letter - // If no pref letter - // return inside; - // else return thiskey == prefletter; - - for (int i = 0; i < nearby.length; i++) { - Key k = nearbyKeys.get(nearby[i]); - if (inPrefList(k.mCode, pref)) { - final int dist = distanceFrom(k, x, y); - if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_HIGH_PROB) - && dist < mPrefDistance) { - mPrefLetter = k.mCode; - mPrefLetterX = x; - mPrefLetterY = y; - mPrefDistance = dist; - } - } - } - // Didn't find any - if (mPrefLetter == 0) { - return isOnKey; - } else { - return mPrefLetter == code; - } - } } // Lock into the spacebar @@ -463,19 +368,6 @@ public class LatinKeyboard extends Keyboard { return key.isOnKey(x, y); } - private boolean inPrefList(int code, int[] pref) { - if (code < pref.length && code >= 0) return pref[code] > 0; - return false; - } - - private int distanceFrom(Key k, int x, int y) { - if (y > k.mY && y < k.mY + k.mHeight) { - return Math.abs(k.mX + k.mWidth / 2 - x); - } else { - return Integer.MAX_VALUE; - } - } - @Override public int[] getNearestKeys(int x, int y) { if (mCurrentlyInSpace) { @@ -487,8 +379,8 @@ public class LatinKeyboard extends Keyboard { } } - private int getTextSizeFromTheme(int style, int defValue) { - TypedArray array = mContext.getTheme().obtainStyledAttributes( + private static int getTextSizeFromTheme(Theme theme, int style, int defValue) { + TypedArray array = theme.obtainStyledAttributes( style, new int[] { android.R.attr.textSize }); int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue); return textSize; diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java index f04991eb7..a8750d378 100644 --- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java @@ -35,24 +35,24 @@ public class MiniKeyboardKeyDetector extends KeyDetector { } @Override - public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { + public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) { final Key[] keys = getKeys(); final int touchX = getTouchX(x); final int touchY = getTouchY(y); - int closestKeyIndex = NOT_A_KEY; - int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; + int nearestIndex = NOT_A_KEY; + int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; final int keyCount = keys.length; for (int index = 0; index < keyCount; index++) { final int dist = keys[index].squaredDistanceToEdge(touchX, touchY); - if (dist < closestKeyDist) { - closestKeyIndex = index; - closestKeyDist = dist; + if (dist < nearestDist) { + nearestIndex = index; + nearestDist = dist; } } - if (allKeys != null && closestKeyIndex != NOT_A_KEY) - allKeys[0] = keys[closestKeyIndex].mCode; - return closestKeyIndex; + if (allCodes != null && nearestIndex != NOT_A_KEY) + allCodes[0] = keys[nearestIndex].mCode; + return nearestIndex; } } diff --git a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java index 0920da2cb..c3fd1984b 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java @@ -16,49 +16,106 @@ package com.android.inputmethod.keyboard; +import android.util.Log; + import java.util.Arrays; public class ProximityKeyDetector extends KeyDetector { + private static final String TAG = ProximityKeyDetector.class.getSimpleName(); + private static final boolean DEBUG = false; + private static final int MAX_NEARBY_KEYS = 12; // working area - private int[] mDistances = new int[MAX_NEARBY_KEYS]; + private final int[] mDistances = new int[MAX_NEARBY_KEYS]; + private final int[] mIndices = new int[MAX_NEARBY_KEYS]; @Override protected int getMaxNearbyKeys() { return MAX_NEARBY_KEYS; } + private void initializeNearbyKeys() { + Arrays.fill(mDistances, Integer.MAX_VALUE); + Arrays.fill(mIndices, NOT_A_KEY); + } + + /** + * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance. + * + * @param keyIndex index of the key. + * @param distance distance between the key's edge and user touched point. + * @return order of the key in the nearby buffer, 0 if it is the nearest key. + */ + private int sortNearbyKeys(int keyIndex, int distance) { + final int[] distances = mDistances; + final int[] indices = mIndices; + for (int insertPos = 0; insertPos < distances.length; insertPos++) { + if (distance < distances[insertPos]) { + final int nextPos = insertPos + 1; + if (nextPos < distances.length) { + System.arraycopy(distances, insertPos, distances, nextPos, + distances.length - nextPos); + System.arraycopy(indices, insertPos, indices, nextPos, + indices.length - nextPos); + } + distances[insertPos] = distance; + indices[insertPos] = keyIndex; + return insertPos; + } + } + return distances.length; + } + + private void getNearbyKeyCodes(final int[] allCodes) { + final Key[] keys = getKeys(); + final int[] indices = mIndices; + + // allCodes[0] should always have the key code even if it is a non-letter key. + if (indices[0] == NOT_A_KEY) { + allCodes[0] = NOT_A_CODE; + return; + } + + int numCodes = 0; + for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) { + final int index = indices[j]; + if (index == NOT_A_KEY) + break; + final int code = keys[index].mCode; + // filter out a non-letter key from nearby keys + if (code < Keyboard.CODE_SPACE) + continue; + allCodes[numCodes++] = code; + } + } + @Override - public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { + public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) { final Key[] keys = getKeys(); final int touchX = getTouchX(x); final int touchY = getTouchY(y); + initializeNearbyKeys(); int primaryIndex = NOT_A_KEY; - final int[] distances = mDistances; - Arrays.fill(distances, Integer.MAX_VALUE); for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) { final Key key = keys[index]; final boolean isInside = key.isInside(touchX, touchY); - if (isInside) - primaryIndex = index; - final int dist = key.squaredDistanceToEdge(touchX, touchY); - if (isInside || (mProximityCorrectOn && dist < mProximityThresholdSquare)) { - if (allKeys == null) continue; - // Find insertion point - for (int j = 0; j < distances.length; j++) { - if (distances[j] > dist) { - final int nextPos = j + 1; - System.arraycopy(distances, j, distances, nextPos, - distances.length - nextPos); - System.arraycopy(allKeys, j, allKeys, nextPos, - allKeys.length - nextPos); - distances[j] = dist; - allKeys[j] = key.mCode; - break; - } - } + final int distance = key.squaredDistanceToEdge(touchX, touchY); + if (isInside || (mProximityCorrectOn && distance < mProximityThresholdSquare)) { + final int insertedPosition = sortNearbyKeys(index, distance); + if (insertedPosition == 0 && isInside) + primaryIndex = index; + } + } + + if (allCodes != null && allCodes.length > 0) { + getNearbyKeyCodes(allCodes); + if (DEBUG) { + Log.d(TAG, "x=" + x + " y=" + y + + " primary=" + + (primaryIndex == NOT_A_KEY ? "none" : keys[primaryIndex].mCode) + + " codes=" + Arrays.toString(allCodes)); } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index a7e95a080..ff7e2b88a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -118,8 +118,7 @@ public class BinaryDictionary extends Dictionary { private native void closeNative(int dict); private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, - char[] outputChars, int[] frequencies, - int[] nextLettersFrequencies, int nextLettersSize); + char[] outputChars, int[] frequencies); private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies, int maxWordLength, int maxBigrams, int maxAlternatives); @@ -133,7 +132,7 @@ public class BinaryDictionary extends Dictionary { @Override public void getBigrams(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback, int[] nextLettersFrequencies) { + final WordCallback callback) { if (mNativeDict == 0) return; char[] chars = previousWord.toString().toCharArray(); @@ -165,15 +164,14 @@ public class BinaryDictionary extends Dictionary { } @Override - public void getWords(final WordComposer codes, final WordCallback callback, - int[] nextLettersFrequencies) { + public void getWords(final WordComposer codes, final WordCallback callback) { if (mNativeDict == 0) return; final int codesSize = codes.size(); // Won't deal with really long words. if (codesSize > MAX_WORD_LENGTH - 1) return; - Arrays.fill(mInputCodes, -1); + Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE); for (int i = 0; i < codesSize; i++) { int[] alternatives = codes.getCodesAt(i); System.arraycopy(alternatives, 0, mInputCodes, i * MAX_ALTERNATIVES, @@ -183,8 +181,7 @@ public class BinaryDictionary extends Dictionary { Arrays.fill(mFrequencies, 0); int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars, - mFrequencies, nextLettersFrequencies, - nextLettersFrequencies != null ? nextLettersFrequencies.length : 0); + mFrequencies); for (int j = 0; j < count; ++j) { if (mFrequencies[j] < 1) break; diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 74933595c..56f0cc503 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -61,14 +61,9 @@ public abstract class Dictionary { * 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 nextLettersFrequencies array of frequencies of next letters that could follow the - * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have - * a non-zero value on returning from this method. - * Pass in null if you don't want the dictionary to look up next letters. * @see WordCallback#addWord(char[], int, int) */ - abstract public void getWords(final WordComposer composer, final WordCallback callback, - int[] nextLettersFrequencies); + abstract public void getWords(final WordComposer composer, final WordCallback callback); /** * Searches for pairs in the bigram dictionary that matches the previous word and all the @@ -76,13 +71,9 @@ public abstract class Dictionary { * @param composer the key sequence to match * @param previousWord the word before * @param callback the callback object to send possible word following previous word - * @param nextLettersFrequencies array of frequencies of next letters that could follow the - * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have - * a non-zero value on returning from this method. - * Pass in null if you don't want the dictionary to look up next letters. */ public void getBigrams(final WordComposer composer, final CharSequence previousWord, - final WordCallback callback, int[] nextLettersFrequencies) { + final WordCallback callback) { // empty base implementation } diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 0fc86c335..b10e7a61e 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -37,7 +37,6 @@ public class ExpandableDictionary extends Dictionary { private int mDicTypeId; private int mMaxDepth; private int mInputLength; - private int[] mNextLettersFrequencies; private StringBuilder sb = new StringBuilder(MAX_WORD_LENGTH); private static final char QUOTE = '\''; @@ -191,8 +190,7 @@ public class ExpandableDictionary extends Dictionary { } @Override - public void getWords(final WordComposer codes, final WordCallback callback, - int[] nextLettersFrequencies) { + public void getWords(final WordComposer codes, final WordCallback callback) { synchronized (mUpdatingLock) { // If we need to update, start off a background task if (mRequiresReload) startDictionaryLoadingTaskLocked(); @@ -201,7 +199,6 @@ public class ExpandableDictionary extends Dictionary { } mInputLength = codes.size(); - mNextLettersFrequencies = nextLettersFrequencies; if (mCodes.length < mInputLength) mCodes = new int[mInputLength][]; // Cache the codes so that we don't have to lookup an array list for (int i = 0; i < mInputLength; i++) { @@ -282,11 +279,6 @@ public class ExpandableDictionary extends Dictionary { DataType.UNIGRAM)) { return; } - // Add to frequency of next letters for predictive correction - if (mNextLettersFrequencies != null && depth >= inputIndex && skipPos < 0 - && mNextLettersFrequencies.length > word[inputIndex]) { - mNextLettersFrequencies[word[inputIndex]]++; - } } if (children != null) { getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, @@ -427,7 +419,7 @@ public class ExpandableDictionary extends Dictionary { @Override public void getBigrams(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback, int[] nextLettersFrequencies) { + final WordCallback callback) { if (!reloadDictionaryIfRequired()) { runReverseLookUp(previousWord, callback); } @@ -516,7 +508,7 @@ public class ExpandableDictionary extends Dictionary { } } - static char toLowerCase(char c) { + private static char toLowerCase(char c) { char baseChar = c; if (c < BASE_CHARS.length) { baseChar = BASE_CHARS[c]; @@ -535,7 +527,7 @@ public class ExpandableDictionary extends Dictionary { * if c is not a combined character, or the base character if it * is combined. */ - static final char BASE_CHARS[] = { + private static final char BASE_CHARS[] = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index cb1ff41dd..648c1bab8 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1500,8 +1500,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void updateSuggestions() { - mKeyboardSwitcher.setPreferredLetters(null); - // Check if we have a suggestion engine attached. if ((mSuggest == null || !isSuggestionsRequested()) && !mVoiceConnector.isVoiceInputHighlighted()) { @@ -1520,7 +1518,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void showCorrections(WordAlternatives alternatives) { - mKeyboardSwitcher.setPreferredLetters(null); SuggestedWords.Builder builder = alternatives.getAlternatives(); builder.setTypedWordValid(false).setHasMinimalSuggestion(false); showSuggestions(builder.build(), alternatives.getOriginalWord()); @@ -1533,9 +1530,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( mKeyboardSwitcher.getInputView(), word, prevWord); - int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies(); - mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies); - boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted && mSuggest.hasAutoCorrection(); final CharSequence typedWord = word.getTypedWord(); @@ -1704,7 +1698,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen saveWordInHistory(suggestion); mHasValidSuggestions = false; mCommittedLength = suggestion.length(); - switcher.setPreferredLetters(null); } /** diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index c9e57d0a5..6466f7980 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -87,12 +87,6 @@ public class Suggest implements Dictionary.WordCallback { private int[] mPriorities = new int[mPrefMaxSuggestions]; private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS]; - // Handle predictive correction for only the first 1280 characters for performance reasons - // If we support scripts that need latin characters beyond that, we should probably use some - // kind of a sparse array or language specific list with a mapping lookup table. - // 1280 is the size of the BASE_CHARS array in ExpandableDictionary, which is a basic set of - // latin characters. - private int[] mNextLettersFrequencies = new int[1280]; private ArrayList mSuggestions = new ArrayList(); ArrayList mBigramSuggestions = new ArrayList(); private ArrayList mStringPool = new ArrayList(); @@ -216,7 +210,6 @@ public class Suggest implements Dictionary.WordCallback { mIsAllUpperCase = wordComposer.isAllUpperCase(); collectGarbage(mSuggestions, mPrefMaxSuggestions); Arrays.fill(mPriorities, 0); - Arrays.fill(mNextLettersFrequencies, 0); // Save a lowercase version of the original word CharSequence typedWord = wordComposer.getTypedWord(); @@ -244,16 +237,13 @@ public class Suggest implements Dictionary.WordCallback { prevWordForBigram = lowerPrevWord; } if (mUserBigramDictionary != null) { - mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this, - mNextLettersFrequencies); + mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this); } if (mContactsDictionary != null) { - mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this, - mNextLettersFrequencies); + mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this); } if (mMainDict != null) { - mMainDict.getBigrams(wordComposer, prevWordForBigram, this, - mNextLettersFrequencies); + mMainDict.getBigrams(wordComposer, prevWordForBigram, this); } char currentChar = wordComposer.getTypedWord().charAt(0); char currentCharUpper = Character.toUpperCase(currentChar); @@ -278,10 +268,10 @@ public class Suggest implements Dictionary.WordCallback { // At second character typed, search the unigrams (scores being affected by bigrams) if (mUserDictionary != null || mContactsDictionary != null) { if (mUserDictionary != null) { - mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies); + mUserDictionary.getWords(wordComposer, this); } if (mContactsDictionary != null) { - mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies); + mContactsDictionary.getWords(wordComposer, this); } if (mSuggestions.size() > 0 && isValidWord(typedWord) @@ -293,7 +283,7 @@ public class Suggest implements Dictionary.WordCallback { mHasAutoCorrection = true; } } - if (mMainDict != null) mMainDict.getWords(wordComposer, this, mNextLettersFrequencies); + if (mMainDict != null) mMainDict.getWords(wordComposer, this); if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM) && mSuggestions.size() > 0 && mPriorities.length > 0) { // TODO: when the normalized score of the first suggestion is nearly equals to @@ -388,10 +378,6 @@ public class Suggest implements Dictionary.WordCallback { } } - public int[] getNextLettersFrequencies() { - return mNextLettersFrequencies; - } - private void removeDupes() { final ArrayList suggestions = mSuggestions; if (suggestions.size() < 2) return; diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java index db5d9244f..c06bd736e 100644 --- a/java/src/com/android/inputmethod/latin/UserDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserDictionary.java @@ -126,9 +126,8 @@ public class UserDictionary extends ExpandableDictionary { } @Override - public synchronized void getWords(final WordComposer codes, final WordCallback callback, - int[] nextLettersFrequencies) { - super.getWords(codes, callback, nextLettersFrequencies); + public synchronized void getWords(final WordComposer codes, final WordCallback callback) { + super.getWords(codes, callback); } @Override diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 2e415b771..e003dcd5b 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -16,12 +16,16 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.KeyDetector; + import java.util.ArrayList; /** * A place to store the currently composing word with information such as adjacent key codes as well */ public class WordComposer { + public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE; + /** * The list of unicode values for each keystroke (including surrounding keys) */ diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index 25580f4b1..4660103b1 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -123,26 +123,20 @@ static jint latinime_BinaryDictionary_open(JNIEnv *env, jobject object, } static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jint dict, - jintArray inputArray, jint arraySize, jcharArray outputArray, jintArray frequencyArray, - jintArray nextLettersArray, jint nextLettersSize) { + jintArray inputArray, jint arraySize, jcharArray outputArray, jintArray frequencyArray) { Dictionary *dictionary = (Dictionary*)dict; if (!dictionary) return 0; int *frequencies = env->GetIntArrayElements(frequencyArray, NULL); int *inputCodes = env->GetIntArrayElements(inputArray, NULL); jchar *outputChars = env->GetCharArrayElements(outputArray, NULL); - int *nextLetters = nextLettersArray != NULL ? env->GetIntArrayElements(nextLettersArray, NULL) - : NULL; int count = dictionary->getSuggestions(inputCodes, arraySize, (unsigned short*) outputChars, - frequencies, nextLetters, nextLettersSize); + frequencies); env->ReleaseIntArrayElements(frequencyArray, frequencies, 0); env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT); env->ReleaseCharArrayElements(outputArray, outputChars, 0); - if (nextLetters) { - env->ReleaseIntArrayElements(nextLettersArray, nextLetters, 0); - } return count; } @@ -209,7 +203,7 @@ static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jint di static JNINativeMethod gMethods[] = { {"openNative", "(Ljava/lang/String;JJIIIII)I", (void*)latinime_BinaryDictionary_open}, {"closeNative", "(I)V", (void*)latinime_BinaryDictionary_close}, - {"getSuggestionsNative", "(I[II[C[I[II)I", (void*)latinime_BinaryDictionary_getSuggestions}, + {"getSuggestionsNative", "(I[II[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions}, {"isValidWordNative", "(I[CI)Z", (void*)latinime_BinaryDictionary_isValidWord}, {"getBigramsNative", "(I[CI[II[C[IIII)I", (void*)latinime_BinaryDictionary_getBigrams} }; diff --git a/native/src/defines.h b/native/src/defines.h index c1eaf0df2..7fa7e35e0 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -151,6 +151,9 @@ static void prof_out(void) { #define MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION 3 #define MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION 3 +// The size of next letters frequency array. Zero will disable the feature. +#define NEXT_LETTERS_SIZE 0 + #define min(a,b) ((a)<(b)?(a):(b)) #endif // LATINIME_DEFINES_H diff --git a/native/src/dictionary.h b/native/src/dictionary.h index cef1cf9eb..941bd191a 100644 --- a/native/src/dictionary.h +++ b/native/src/dictionary.h @@ -27,10 +27,8 @@ class Dictionary { public: Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler, int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives); - int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies, - int *nextLetters, int nextLettersSize) { - return mUnigramDictionary->getSuggestions(codes, codesSize, outWords, frequencies, - nextLetters, nextLettersSize); + int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies) { + return mUnigramDictionary->getSuggestions(codes, codesSize, outWords, frequencies); } // TODO: Call mBigramDictionary instead of mUnigramDictionary diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index dfbe8228e..3d5683ed9 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -32,7 +32,7 @@ namespace latinime { UnigramDictionary::UnigramDictionary(const unsigned char *dict, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion) - : DICT(dict), MAX_WORD_LENGTH(maxWordLength),MAX_WORDS(maxWords), + : DICT(dict), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords), MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion), TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier), ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0) { @@ -42,7 +42,7 @@ UnigramDictionary::UnigramDictionary(const unsigned char *dict, int typedLetterM UnigramDictionary::~UnigramDictionary() {} int UnigramDictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWords, - int *frequencies, int *nextLetters, int nextLettersSize) { + int *frequencies) { PROF_OPEN; PROF_START(0); initSuggestions(codes, codesSize, outWords, frequencies); @@ -52,7 +52,7 @@ int UnigramDictionary::getSuggestions(int *codes, int codesSize, unsigned short PROF_END(0); PROF_START(1); - getSuggestionCandidates(-1, -1, -1, nextLetters, nextLettersSize, MAX_DEPTH); + getSuggestionCandidates(-1, -1, -1, mNextLettersFrequency, NEXT_LETTERS_SIZE, MAX_DEPTH); PROF_END(1); PROF_START(2); @@ -108,9 +108,9 @@ int UnigramDictionary::getSuggestions(int *codes, int codesSize, unsigned short if (DEBUG_DICT) { LOGI("Returning %d words", suggestedWordsCount); LOGI("Next letters: "); - for (int k = 0; k < nextLettersSize; k++) { - if (nextLetters[k] > 0) { - LOGI("%c = %d,", k, nextLetters[k]); + for (int k = 0; k < NEXT_LETTERS_SIZE; k++) { + if (mNextLettersFrequency[k] > 0) { + LOGI("%c = %d,", k, mNextLettersFrequency[k]); } } } diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index 90c98149b..43aafc69d 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -32,8 +32,7 @@ class UnigramDictionary { public: UnigramDictionary(const unsigned char *dict, int typedLetterMultipler, int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion); - int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies, - int *nextLetters, int nextLettersSize); + int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies); ~UnigramDictionary(); private: @@ -109,6 +108,7 @@ private: int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL]; int mStackDiffs[MAX_WORD_LENGTH_INTERNAL]; int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL]; + int mNextLettersFrequency[NEXT_LETTERS_SIZE]; }; // ---------------------------------------------------------------------------- diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java index a6fb6e1e1..46e5a2454 100644 --- a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java +++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java @@ -61,7 +61,7 @@ public class UserBigramSuggestHelper extends SuggestHelper { mSuggest.getSuggestions(null, firstChar, previous); boolean reloading = mUserBigram.reloadDictionaryIfRequired(); if (reloading) mUserBigram.waitForDictionaryLoading(); - mUserBigram.getBigrams(firstChar, previous, mSuggest, null); + mUserBigram.getBigrams(firstChar, previous, mSuggest); } for (int i = 0; i < mSuggest.mBigramSuggestions.size(); i++) { -- cgit v1.2.3-83-g751a From 16ee377bcd5fd13cd10e88da1b95a0059786306a Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Thu, 17 Feb 2011 12:45:31 +0900 Subject: Fix password variation check code Change-Id: Id994112a029982b4dabd6ebca34edd1d787e5ae2 --- .../com/android/inputmethod/latin/LatinIME.java | 28 ++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index cb1ff41dd..9f65bf788 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -494,10 +494,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return container; } - private static boolean isPasswordVariation(int variation) { - return variation == InputType.TYPE_TEXT_VARIATION_PASSWORD - || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD - || variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD; + // Please refer to TextView.isPasswordInputType + private static boolean isPasswordInputType(int inputType) { + final int variation = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return (variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) + || (variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) + || (variation + == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); + } + + // Please refer to TextView.isVisiblePasswordInputType + private static boolean isVisiblePasswordInputType(int inputType) { + final int variation = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); } private static boolean isEmailVariation(int variation) { @@ -525,8 +539,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to // know now whether this is a password text field, because we need to know now whether we // want to enable the voice button. - mVoiceConnector.resetVoiceStates(isPasswordVariation( - attribute.inputType & InputType.TYPE_MASK_VARIATION)); + mVoiceConnector.resetVoiceStates(isPasswordInputType(attribute.inputType) + || isVisiblePasswordInputType(attribute.inputType)); final int mode = initializeInputAttributesAndGetMode(attribute.inputType); @@ -583,7 +597,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case InputType.TYPE_CLASS_TEXT: mIsSettingsSuggestionStripOn = true; // Make sure that passwords are not displayed in candidate view - if (isPasswordVariation(variation)) { + if (isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType)) { mIsSettingsSuggestionStripOn = false; } if (isEmailVariation(variation) -- cgit v1.2.3-83-g751a From 060efb6d82af1f896d90cb845c8ae07c726f85e1 Mon Sep 17 00:00:00 2001 From: satok Date: Thu, 17 Feb 2011 17:45:46 +0900 Subject: Not to make a thread at changing view from keyboard to voice Change-Id: If3f89b3e9774c566a1bb2119b161c42b715e6709 --- .../com/android/inputmethod/latin/LatinIME.java | 33 +++++++++------------- 1 file changed, 14 insertions(+), 19 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index cb1ff41dd..fa71eea98 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1459,26 +1459,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void switchToKeyboardView() { - mHandler.post(new Runnable() { - @Override - public void run() { - if (DEBUG) { - Log.d(TAG, "Switch to keyboard view."); - } - View v = mKeyboardSwitcher.getInputView(); - if (v != null) { - // Confirms that the keyboard view doesn't have parent view. - ViewParent p = v.getParent(); - if (p != null && p instanceof ViewGroup) { - ((ViewGroup) p).removeView(v); - } - setInputView(v); - } - setCandidatesViewShown(isCandidateStripVisible()); - updateInputViewShown(); - mHandler.postUpdateSuggestions(); + if (DEBUG) { + Log.d(TAG, "Switch to keyboard view."); + } + View v = mKeyboardSwitcher.getInputView(); + if (v != null) { + // Confirms that the keyboard view doesn't have parent view. + ViewParent p = v.getParent(); + if (p != null && p instanceof ViewGroup) { + ((ViewGroup) p).removeView(v); } - }); + setInputView(v); + } + setCandidatesViewShown(isCandidateStripVisible()); + updateInputViewShown(); + mHandler.postUpdateSuggestions(); } public void clearSuggestions() { -- cgit v1.2.3-83-g751a From aa721c4783616e65a42fb5f234f2b3909ccdd931 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Fri, 18 Feb 2011 11:28:17 +0900 Subject: Add "enabled" as an attribute of Key in Keyboard xml If the key is marked as "enabled=false", the key will not respond to be pressed. Bug: 3384942 Change-Id: I1ea82d9f3dfa5a9916ff3bbc736ea7e6c77426ae --- java/res/values/attrs.xml | 2 ++ java/src/com/android/inputmethod/keyboard/Key.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'java/src') diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index 9759e0eb6..b6d5fdc62 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -140,6 +140,8 @@ + + diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 23886ad97..7396f0518 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -95,7 +95,7 @@ public class Key { public boolean mPressed; /** If this is a sticky key, is it on? */ public boolean mOn; - /** Key is enabled or not. */ + /** Key is enabled and responds on press */ public boolean mEnabled = true; private final static int[] KEY_STATE_NORMAL_ON = { @@ -226,6 +226,7 @@ public class Key { mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false); mModifier = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier, false); mSticky = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky, false); + mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true); mEdgeFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyEdgeFlags, 0) | row.mRowEdgeFlags; -- cgit v1.2.3-83-g751a From d9c52ba62317aa7919483ccd157182ae0f0db7f4 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Fri, 18 Feb 2011 14:16:29 +0900 Subject: Rename imeOptions attribute of Keyboard to imeAction. Bug: 3384942 Change-Id: I3ce01b83f0ae37f447de8a7fcdb5d7fe06b108c6 --- java/res/values/attrs.xml | 2 +- java/res/xml-xlarge/kbd_qwerty_row4.xml | 2 +- java/res/xml/kbd_key_styles.xml | 10 +++++----- java/src/com/android/inputmethod/keyboard/KeyboardId.java | 13 +++++++------ .../com/android/inputmethod/keyboard/KeyboardParser.java | 13 +++++++------ 5 files changed, 21 insertions(+), 19 deletions(-) (limited to 'java/src') diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index b6d5fdc62..dea163595 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -171,7 +171,7 @@ - + diff --git a/java/res/xml-xlarge/kbd_qwerty_row4.xml b/java/res/xml-xlarge/kbd_qwerty_row4.xml index 9d0fd81c7..65ca093a4 100644 --- a/java/res/xml-xlarge/kbd_qwerty_row4.xml +++ b/java/res/xml-xlarge/kbd_qwerty_row4.xml @@ -49,7 +49,7 @@ %s", TAG_CASE, textAttr(KeyboardId.modeName( @@ -444,7 +445,7 @@ public class KeyboardParser { booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"), booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), textAttr(KeyboardId.imeOptionsName( - a.getInt(R.styleable.Keyboard_Case_imeOptions, -1)), "imeOptions"), + a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"), Boolean.toString(selected))); return selected; -- cgit v1.2.3-83-g751a From ea6c2e3350f21d1fc9e9866dbe13d74713d548a9 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Fri, 18 Feb 2011 15:33:44 +0900 Subject: Add "languageCode" attribute to "case" tag in Keyboard XML bug: 3451586 Change-Id: I08ca80e48648ba18543e9b8ca7362c06bb61a826 --- java/res/values/attrs.xml | 1 + .../com/android/inputmethod/keyboard/KeyboardParser.java | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'java/src') diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index dea163595..f5babd3cd 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -182,6 +182,7 @@ + diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java index 1aec686e0..70f16c21b 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -433,10 +433,13 @@ public class KeyboardParser { // this attribute with id.mImeOptions as integer value is enough for our purpose. final boolean imeActionMatched = matchInteger(a, R.styleable.Keyboard_Case_imeAction, id.mImeAction); + final boolean languageCodeMatched = matchString(a, + R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched - && voiceKeyMatched && colorSchemeMatched && imeActionMatched; + && voiceKeyMatched && colorSchemeMatched && imeActionMatched + && languageCodeMatched; - if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s> %s", TAG_CASE, + if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s> %s", TAG_CASE, textAttr(KeyboardId.modeName( a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"), textAttr(KeyboardId.colorSchemeName( @@ -446,6 +449,7 @@ public class KeyboardParser { booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), textAttr(KeyboardId.imeOptionsName( a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"), + textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"), Boolean.toString(selected))); return selected; @@ -467,6 +471,12 @@ public class KeyboardParser { return !a.hasValue(index) || a.getBoolean(index, false) == value; } + private static boolean matchString(TypedArray a, int index, String value) { + // If does not have "index" attribute, that means this is wild-card for the + // attribute. + return !a.hasValue(index) || a.getString(index).equals(value); + } + private boolean parseDefault(XmlResourceParser parser, Row row, List keys) throws XmlPullParserException, IOException { if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT)); -- cgit v1.2.3-83-g751a From fdb6a194b1255c4d9cd642586b98e1e55ff58249 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Sun, 20 Feb 2011 15:25:57 +0900 Subject: Read "enabled" attribute of Key from This is follow up change of I1ea82d9f. Bug: 3384942 Change-Id: If96eda69fe81edbfc1b243a6fa9f8eb6bbf4c12d --- java/src/com/android/inputmethod/keyboard/KeyStyles.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/KeyStyles.java index 44ec53181..169f2e6c3 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyStyles.java +++ b/java/src/com/android/inputmethod/keyboard/KeyStyles.java @@ -188,6 +188,7 @@ public class KeyStyles { readBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier); readBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky); readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable); + readBoolean(keyAttr, R.styleable.Keyboard_Key_enabled); } private void readDrawable(TypedArray a, int index) { -- cgit v1.2.3-83-g751a From 690b1360bfda3cbaae896de65dcc3cd347dc8329 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Sun, 20 Feb 2011 16:50:46 +0900 Subject: Don't play key press sound if the key is disabled Change-Id: I1c6d1b16a7420bc7e5f97f50da549e6a89498f18 --- .../inputmethod/keyboard/PointerTracker.java | 55 +++++++++++++--------- 1 file changed, 32 insertions(+), 23 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 4c90e2c3f..ac375473a 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -129,32 +129,42 @@ public class PointerTracker { } // Returns true if keyboard has been changed by this callback. - private boolean callListenerOnPressAndCheckKeyboardLayoutChange(int primaryCode) { + private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { if (DEBUG_LISTENER) - Log.d(TAG, "onPress : " + keyCodePrintable(primaryCode)); - mListener.onPress(primaryCode); - final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; - mKeyboardLayoutHasBeenChanged = false; - return keyboardLayoutHasBeenChanged; + Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode)); + if (key.mEnabled) { + mListener.onPress(key.mCode); + final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; + mKeyboardLayoutHasBeenChanged = false; + return keyboardLayoutHasBeenChanged; + } + return false; } - private void callListenerOnCodeInput(int primaryCode, int[] keyCodes, int x, int y) { + // Note that we need primaryCode argument because the keyboard may in shifted state and the + // primaryCode is different from {@link Key#mCode}. + private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) { if (DEBUG_LISTENER) Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode) + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y); - mListener.onCodeInput(primaryCode, keyCodes, x, y); + if (key.mEnabled) + mListener.onCodeInput(primaryCode, keyCodes, x, y); } - private void callListenerOnTextInput(CharSequence text) { + private void callListenerOnTextInput(Key key) { if (DEBUG_LISTENER) - Log.d(TAG, "onTextInput: text=" + text); - mListener.onTextInput(text); + Log.d(TAG, "onTextInput: text=" + key.mOutputText); + if (key.mEnabled) + mListener.onTextInput(key.mOutputText); } - private void callListenerOnRelease(int primaryCode) { + // Note that we need primaryCode argument because the keyboard may in shifted state and the + // primaryCode is different from {@link Key#mCode}. + private void callListenerOnRelease(Key key, int primaryCode) { if (DEBUG_LISTENER) Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode)); - mListener.onRelease(primaryCode); + if (key.mEnabled) + mListener.onRelease(primaryCode); } private void callListenerOnCancelInput() { @@ -313,7 +323,7 @@ public class PointerTracker { // This onPress call may have changed keyboard layout. Those cases are detected at // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new // keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex].mCode)) + if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex])) keyIndex = mKeyState.onDownKey(x, y, eventTime); } if (isValidKeyIndex(keyIndex)) { @@ -346,7 +356,7 @@ public class PointerTracker { // This onPress call may have changed keyboard layout. Those cases are detected at // {@link #setKeyboard}. In those cases, we should update keyIndex according to the // new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex).mCode)) + if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex))) keyIndex = keyState.onMoveKey(x, y); keyState.onMoveToNewKey(keyIndex, x, y); startLongPressTimer(keyIndex); @@ -355,13 +365,13 @@ public class PointerTracker { // onRelease() first to notify that the previous key has been released, then call // onPress() to notify that the new key is being pressed. mIsInSlidingKeyInput = true; - callListenerOnRelease(oldKey.mCode); + callListenerOnRelease(oldKey, oldKey.mCode); mHandler.cancelLongPressTimers(); if (mIsAllowedSlidingKeyInput) { // This onPress call may have changed keyboard layout. Those cases are detected // at {@link #setKeyboard}. In those cases, we should update keyIndex according // to the new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex).mCode)) + if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex))) keyIndex = keyState.onMoveKey(x, y); keyState.onMoveToNewKey(keyIndex, x, y); startLongPressTimer(keyIndex); @@ -390,7 +400,7 @@ public class PointerTracker { // The pointer has been slid out from the previous key, we must call onRelease() to // notify that the previous key has been released. mIsInSlidingKeyInput = true; - callListenerOnRelease(oldKey.mCode); + callListenerOnRelease(oldKey, oldKey.mCode); mHandler.cancelLongPressTimers(); if (mIsAllowedSlidingKeyInput) { keyState.onMoveToNewKey(keyIndex, x ,y); @@ -539,8 +549,8 @@ public class PointerTracker { return; } if (key.mOutputText != null) { - callListenerOnTextInput(key.mOutputText); - callListenerOnRelease(key.mCode); + callListenerOnTextInput(key); + callListenerOnRelease(key, key.mCode); } else { int code = key.mCode; final int[] codes = mKeyDetector.newCodeArray(); @@ -561,9 +571,8 @@ public class PointerTracker { codes[1] = codes[0]; codes[0] = code; } - if (key.mEnabled) - callListenerOnCodeInput(code, codes, x, y); - callListenerOnRelease(code); + callListenerOnCodeInput(key, code, codes, x, y); + callListenerOnRelease(key, code); } } -- cgit v1.2.3-83-g751a From 4b13b4f94215368c6387b2564bdaf2cbcbe4c130 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Sun, 20 Feb 2011 12:54:14 +0900 Subject: Add "passwordInput" attribute to case tag of Keyboard This change also introduces number password (a.k.a. PIN) layout. The text password layout will follow. Bug: 3384942 Change-Id: I50b5e413e47a28d5285fd0468115e11ce46c7f18 --- java/res/values/attrs.xml | 1 + java/res/xml-xlarge/kbd_number.xml | 299 +++++++++++++-------- .../android/inputmethod/keyboard/KeyboardId.java | 26 +- .../inputmethod/keyboard/KeyboardParser.java | 11 +- .../inputmethod/keyboard/KeyboardSwitcher.java | 44 +-- .../inputmethod/keyboard/LatinKeyboard.java | 2 + .../com/android/inputmethod/latin/LatinIME.java | 52 ++-- java/src/com/android/inputmethod/latin/Utils.java | 28 +- .../inputmethod/latin/SuggestTestsBase.java | 7 +- 9 files changed, 281 insertions(+), 189 deletions(-) (limited to 'java/src') diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index f5babd3cd..65892784d 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -168,6 +168,7 @@ + diff --git a/java/res/xml-xlarge/kbd_number.xml b/java/res/xml-xlarge/kbd_number.xml index 875548ba7..152ec8285 100644 --- a/java/res/xml-xlarge/kbd_number.xml +++ b/java/res/xml-xlarge/kbd_number.xml @@ -31,120 +31,189 @@ > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index 42dc0b0cd..d09f6786e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -17,6 +17,7 @@ package com.android.inputmethod.keyboard; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.Utils; import android.view.inputmethod.EditorInfo; @@ -41,6 +42,7 @@ public class KeyboardId { public final int mMode; public final int mXmlId; public final int mColorScheme; + public final boolean mPasswordInput; public final boolean mHasSettingsKey; public final boolean mVoiceKeyEnabled; public final boolean mHasVoiceKey; @@ -50,21 +52,25 @@ public class KeyboardId { private final int mHashCode; - public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int mode, - int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, boolean hasVoiceKey, - int imeOptions, boolean enableShiftLock) { + public KeyboardId(String xmlName, int xmlId, int colorScheme, Locale locale, int orientation, + int mode, EditorInfo attribute, boolean hasSettingsKey, boolean voiceKeyEnabled, + boolean hasVoiceKey, boolean enableShiftLock) { + final int inputType = (attribute != null) ? attribute.inputType : 0; + final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; this.mLocale = locale; this.mOrientation = orientation; this.mMode = mode; this.mXmlId = xmlId; this.mColorScheme = colorScheme; + this.mPasswordInput = Utils.isPasswordInputType(inputType) + || Utils.isVisiblePasswordInputType(inputType); this.mHasSettingsKey = hasSettingsKey; this.mVoiceKeyEnabled = voiceKeyEnabled; this.mHasVoiceKey = hasVoiceKey; // We are interested only in {@link EditorInfo#IME_MASK_ACTION} enum value and // {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION}. - this.mImeAction = imeOptions - & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION); + this.mImeAction = imeOptions & ( + EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION); this.mEnableShiftLock = enableShiftLock; this.mXmlName = xmlName; @@ -74,6 +80,7 @@ public class KeyboardId { mode, xmlId, colorScheme, + mPasswordInput, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, @@ -113,6 +120,7 @@ public class KeyboardId { && other.mMode == this.mMode && other.mXmlId == this.mXmlId && other.mColorScheme == this.mColorScheme + && other.mPasswordInput == this.mPasswordInput && other.mHasSettingsKey == this.mHasSettingsKey && other.mVoiceKeyEnabled == this.mVoiceKeyEnabled && other.mHasVoiceKey == this.mHasVoiceKey @@ -127,17 +135,19 @@ public class KeyboardId { @Override public String toString() { - return String.format("[%s.xml %s %s %s imeOptions=%s %s%s%s%s%s]", + return String.format("[%s.xml %s %s %s imeAction=%s %s%s%s%s%s%s]", mXmlName, mLocale, (mOrientation == 1 ? "port" : "land"), modeName(mMode), imeOptionsName(mImeAction), - colorSchemeName(mColorScheme), + (mPasswordInput ? " passwordInput" : ""), (mHasSettingsKey ? " hasSettingsKey" : ""), (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""), (mHasVoiceKey ? " hasVoiceKey" : ""), - (mEnableShiftLock ? " enableShiftLock" : "")); + (mEnableShiftLock ? " enableShiftLock" : ""), + colorSchemeName(mColorScheme) + ); } public static String modeName(int mode) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java index 70f16c21b..89208dd5d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -419,6 +419,8 @@ public class KeyboardParser { try { final boolean modeMatched = matchInteger(a, R.styleable.Keyboard_Case_mode, id.mMode); + final boolean passwordInputMatched = matchBoolean(a, + R.styleable.Keyboard_Case_passwordInput, id.mPasswordInput); final boolean settingsKeyMatched = matchBoolean(a, R.styleable.Keyboard_Case_hasSettingsKey, id.mHasSettingsKey); final boolean voiceEnabledMatched = matchBoolean(a, @@ -435,15 +437,16 @@ public class KeyboardParser { R.styleable.Keyboard_Case_imeAction, id.mImeAction); final boolean languageCodeMatched = matchString(a, R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); - final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched - && voiceKeyMatched && colorSchemeMatched && imeActionMatched - && languageCodeMatched; + final boolean selected = modeMatched && passwordInputMatched && settingsKeyMatched + && voiceEnabledMatched && voiceKeyMatched && colorSchemeMatched + && imeActionMatched && languageCodeMatched; - if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s> %s", TAG_CASE, + if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s> %s", TAG_CASE, textAttr(KeyboardId.modeName( a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"), textAttr(KeyboardId.colorSchemeName( a.getInt(R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"), + booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"), booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"), booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"), booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 9b98f2878..93d3811c4 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -28,6 +28,7 @@ import android.content.SharedPreferences; import android.content.res.Resources; import android.util.Log; import android.view.InflateException; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import java.lang.ref.SoftReference; @@ -66,8 +67,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private final HashMap> mKeyboardCache = new HashMap>(); + // TODO: clean mMode up and use mAttribute instead. private int mMode = KeyboardId.MODE_TEXT; /* default value */ - private int mImeOptions; + private EditorInfo mAttribute; private boolean mIsSymbols; /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of * what user actually typed. */ @@ -128,10 +130,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha final int orientation = res.getConfiguration().orientation; final int mode = mMode; final int colorScheme = getColorScheme(); - final boolean hasSettingsKey = mHasSettingsKey; - final boolean voiceKeyEnabled = mVoiceKeyEnabled; - final boolean hasVoiceKey = voiceKeyEnabled && !mVoiceButtonOnPrimary; - final int imeOptions = mImeOptions; + final boolean hasVoiceKey = mVoiceKeyEnabled && !mVoiceButtonOnPrimary; // Note: This comment is only applied for phone number keyboard layout. // On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch // between "phone keyboard" and "phone symbols keyboard". But on xlarge device, @@ -140,26 +139,25 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard" // respectively here for xlarge device's layout switching. int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols; - mSymbolsId = new KeyboardId( - res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme, - hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); + final String xmlName = res.getResourceEntryName(xmlId); + mSymbolsId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode, + mAttribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true); xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift; - mSymbolsShiftedId = new KeyboardId( - res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme, - hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); + mSymbolsShiftedId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode, + mAttribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true); } private boolean hasVoiceKey(boolean isSymbols) { return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary); } - public void loadKeyboard(int mode, int imeOptions, boolean voiceKeyEnabled, + public void loadKeyboard(int mode, EditorInfo attribute, boolean voiceKeyEnabled, boolean voiceButtonOnPrimary) { mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA; try { if (mInputView == null) return; final Keyboard oldKeyboard = mInputView.getKeyboard(); - loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary, false); + loadKeyboardInternal(mode, attribute, voiceKeyEnabled, voiceButtonOnPrimary, false); final Keyboard newKeyboard = mInputView.getKeyboard(); if (newKeyboard.isAlphaKeyboard()) { final boolean localeChanged = (oldKeyboard == null) @@ -167,23 +165,25 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged); } } catch (RuntimeException e) { - Log.w(TAG, e); - LatinImeLogger.logOnException(mode + "," + imeOptions, e); + // Get KeyboardId to record which keyboard has been failed to load. + final KeyboardId id = getKeyboardId(mode, attribute, false); + Log.w(TAG, "loading keyboard failed: " + id, e); + LatinImeLogger.logOnException(id.toString(), e); } } - private void loadKeyboardInternal(int mode, int imeOptions, boolean voiceButtonEnabled, + private void loadKeyboardInternal(int mode, EditorInfo attribute, boolean voiceButtonEnabled, boolean voiceButtonOnPrimary, boolean isSymbols) { if (mInputView == null) return; mMode = mode; - mImeOptions = imeOptions; + mAttribute = attribute; mVoiceKeyEnabled = voiceButtonEnabled; mVoiceButtonOnPrimary = voiceButtonOnPrimary; mIsSymbols = isSymbols; // Update the settings key state because number of enabled IMEs could have been changed mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService); - final KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols); + final KeyboardId id = getKeyboardId(mode, attribute, isSymbols); final Keyboard oldKeyboard = mInputView.getKeyboard(); if (oldKeyboard != null && oldKeyboard.mId.equals(id)) @@ -228,7 +228,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return keyboard; } - private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) { + private KeyboardId getKeyboardId(int mode, EditorInfo attribute, boolean isSymbols) { final boolean hasVoiceKey = hasVoiceKey(isSymbols); final int charColorId = getColorScheme(); final int xmlId; @@ -260,8 +260,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha final int orientation = res.getConfiguration().orientation; final Locale locale = mSubtypeSwitcher.getInputLocale(); return new KeyboardId( - res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, charColorId, - mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock); + res.getResourceEntryName(xmlId), xmlId, charColorId, locale, orientation, mode, + attribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, enableShiftLock); } public int getKeyboardMode() { @@ -566,7 +566,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } private void toggleKeyboardMode() { - loadKeyboardInternal(mMode, mImeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary, + loadKeyboardInternal(mMode, mAttribute, mVoiceKeyEnabled, mVoiceButtonOnPrimary, !mIsSymbols); if (mIsSymbols) { mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN; diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java index b6eab1f3b..5820049bb 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java @@ -155,6 +155,8 @@ public class LatinKeyboard extends Keyboard { } private void updateSpacebarForLocale(boolean isAutoCorrection) { + if (mSpaceKey == null) + return; final Resources res = mContext.getResources(); // If application locales are explicitly selected. if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 0bb39fdd9..2a340ad0f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -494,26 +494,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return container; } - // Please refer to TextView.isPasswordInputType - private static boolean isPasswordInputType(int inputType) { - final int variation = - inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); - return (variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) - || (variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) - || (variation - == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); - } - - // Please refer to TextView.isVisiblePasswordInputType - private static boolean isVisiblePasswordInputType(int inputType) { - final int variation = - inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); - return variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - } - private static boolean isEmailVariation(int variation) { return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; @@ -539,10 +519,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to // know now whether this is a password text field, because we need to know now whether we // want to enable the voice button. - mVoiceConnector.resetVoiceStates(isPasswordInputType(attribute.inputType) - || isVisiblePasswordInputType(attribute.inputType)); + mVoiceConnector.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) + || Utils.isVisiblePasswordInputType(attribute.inputType)); - final int mode = initializeInputAttributesAndGetMode(attribute.inputType); + final int mode = initializeInputAttributesAndGetMode(attribute); inputView.closing(); mEnteredText = null; @@ -553,7 +533,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen loadSettings(attribute); if (mSubtypeSwitcher.isKeyboardMode()) { - switcher.loadKeyboard(mode, attribute.imeOptions, + switcher.loadKeyboard(mode, attribute, mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary()); switcher.updateShiftState(); @@ -577,7 +557,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); } - private int initializeInputAttributesAndGetMode(int inputType) { + // TODO: Separate calculating keyboard mode from initializing attributes, and make it an + // utility method in {@link Utils}. + private int initializeInputAttributesAndGetMode(EditorInfo attribute) { + if (attribute == null) return KeyboardId.MODE_TEXT; + final int inputType = attribute.inputType; final int variation = inputType & InputType.TYPE_MASK_VARIATION; mAutoSpace = false; mInputTypeNoAutoCorrect = false; @@ -597,16 +581,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case InputType.TYPE_CLASS_TEXT: mIsSettingsSuggestionStripOn = true; // Make sure that passwords are not displayed in candidate view - if (isPasswordInputType(inputType) || isVisiblePasswordInputType(inputType)) { + if (Utils.isPasswordInputType(inputType) + || Utils.isVisiblePasswordInputType(inputType)) { mIsSettingsSuggestionStripOn = false; } - if (isEmailVariation(variation) + if (LatinIME.isEmailVariation(variation) || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { mAutoSpace = false; } else { mAutoSpace = true; } - if (isEmailVariation(variation)) { + if (LatinIME.isEmailVariation(variation)) { mIsSettingsSuggestionStripOn = false; mode = KeyboardId.MODE_EMAIL; } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { @@ -1924,15 +1909,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeSwitcher.toggleLanguage(reset, next); } // Reload keyboard because the current language has been changed. - KeyboardSwitcher switcher = mKeyboardSwitcher; final EditorInfo attribute = getCurrentInputEditorInfo(); - final int mode = initializeInputAttributesAndGetMode((attribute != null) - ? attribute.inputType : 0); - final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; - switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(), - mVoiceConnector.isVoiceButtonOnPrimary()); + final int mode = initializeInputAttributesAndGetMode(attribute); + mKeyboardSwitcher.loadKeyboard(mode, attribute, + mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary()); initSuggest(); - switcher.updateShiftState(); + mKeyboardSwitcher.updateShiftState(); } @Override diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index aafafbf2f..7091d9b56 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -16,13 +16,17 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.KeyboardId; + import android.inputmethodservice.InputMethodService; import android.os.AsyncTask; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; +import android.text.InputType; import android.text.format.DateUtils; import android.util.Log; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; @@ -55,7 +59,7 @@ public class Utils { } public static class GCUtils { - private static final String TAG = "GCUtils"; + private static final String GC_TAG = GCUtils.class.getSimpleName(); public static final int GC_TRY_COUNT = 2; // GC_TRY_LOOP_MAX is used for the hard limit of GC wait, // GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT. @@ -84,7 +88,7 @@ public class Utils { Thread.sleep(GC_INTERVAL); return true; } catch (InterruptedException e) { - Log.e(TAG, "Sleep was interrupted."); + Log.e(GC_TAG, "Sleep was interrupted."); LatinImeLogger.logOnException(metaData, t); return false; } @@ -452,4 +456,24 @@ public class Utils { return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */); } } + + // Please refer to TextView.isPasswordInputType + public static boolean isPasswordInputType(int inputType) { + final int variation = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return (variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) + || (variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) + || (variation + == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); + } + + // Please refer to TextView.isVisiblePasswordInputType + public static boolean isVisiblePasswordInputType(int inputType) { + final int variation = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + } } diff --git a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java index 365cea865..4500c2aee 100644 --- a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java +++ b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java @@ -23,6 +23,7 @@ import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.test.AndroidTestCase; import android.text.TextUtils; +import android.view.inputmethod.EditorInfo; import java.io.File; import java.io.InputStream; @@ -30,9 +31,9 @@ import java.util.Locale; public class SuggestTestsBase extends AndroidTestCase { protected static final KeyboardId US_KEYBOARD_ID = new KeyboardId("en_US qwerty keyboard", - com.android.inputmethod.latin.R.xml.kbd_qwerty, Locale.US, - Configuration.ORIENTATION_LANDSCAPE, KeyboardId.MODE_TEXT, - KeyboardView.COLOR_SCHEME_WHITE, false, false, false, 0, false); + com.android.inputmethod.latin.R.xml.kbd_qwerty, KeyboardView.COLOR_SCHEME_WHITE, + Locale.US, Configuration.ORIENTATION_LANDSCAPE, KeyboardId.MODE_TEXT, + new EditorInfo(), false, false, false, false); protected File mTestPackageFile; -- cgit v1.2.3-83-g751a From 4adf5d9ae5d8c449510bf1f93cf678f63109a1cc Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Sun, 20 Feb 2011 14:44:59 +0900 Subject: Disable non-ASCII symbol keys in password input Bug: 3384942 Change-Id: Ifa10e84f22bb6ff85dfc397fac95a0294b2a6bea --- java/res/xml-xlarge/kbd_key_styles.xml | 15 +++++++++++++++ java/res/xml-xlarge/kbd_symbols_shift.xml | 19 +++++++++++++++++++ .../android/inputmethod/keyboard/KeyboardParser.java | 6 +++--- .../android/inputmethod/keyboard/KeyboardView.java | 10 ++++++++-- 4 files changed, 45 insertions(+), 5 deletions(-) (limited to 'java/src') diff --git a/java/res/xml-xlarge/kbd_key_styles.xml b/java/res/xml-xlarge/kbd_key_styles.xml index d211e5e61..fc06d00fc 100644 --- a/java/res/xml-xlarge/kbd_key_styles.xml +++ b/java/res/xml-xlarge/kbd_key_styles.xml @@ -165,4 +165,19 @@ latin:keyOutputText="@string/keylabel_for_popular_domain" latin:keyHintIcon="@drawable/hint_popup_holo" latin:popupCharacters="@string/alternates_for_popular_domain" /> + + + + + + + + + diff --git a/java/res/xml-xlarge/kbd_symbols_shift.xml b/java/res/xml-xlarge/kbd_symbols_shift.xml index f7cf24a3f..1f5513b83 100644 --- a/java/res/xml-xlarge/kbd_symbols_shift.xml +++ b/java/res/xml-xlarge/kbd_symbols_shift.xml @@ -46,21 +46,28 @@ ", - TAG_KEY, key.mLabel, key.mCode, + if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d popupCharacters=%s />", + TAG_KEY, (key.mEnabled ? "" : " disabled"), key.mLabel, key.mCode, Arrays.toString(key.mPopupCharacters))); checkEndTag(TAG_KEY, parser); keys.add(key); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index d4c5e579b..7fee022e0 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.PorterDuff; @@ -705,8 +706,13 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { } else { paint.setColor(mKeyTextColor); } - // Set a drop shadow for the text - paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); + if (key.mEnabled) { + // Set a drop shadow for the text + paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); + } else { + // Make label invisible + paint.setColor(Color.TRANSPARENT); + } canvas.drawText(label, positionX, baseline, paint); // Turn off drop shadow paint.setShadowLayer(0, 0, 0, 0); -- cgit v1.2.3-83-g751a From 050c0462dc2ada5a5afecec5b6745693c5066b85 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Sun, 20 Feb 2011 21:12:51 +0900 Subject: Display language name on spacebar even in symbol keyboard. Bug: 3468634 Change-Id: I1a25eb71ddbd7efae6f40ea357714924a9d56dc4 --- .../inputmethod/keyboard/KeyboardSwitcher.java | 31 ++++++++++++---------- .../inputmethod/keyboard/LatinKeyboardView.java | 5 ++-- .../com/android/inputmethod/latin/LatinIME.java | 2 +- .../android/inputmethod/latin/SubtypeSwitcher.java | 10 +++---- 4 files changed, 25 insertions(+), 23 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 93d3811c4..3297dc325 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -155,15 +155,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha boolean voiceButtonOnPrimary) { mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA; try { - if (mInputView == null) return; - final Keyboard oldKeyboard = mInputView.getKeyboard(); loadKeyboardInternal(mode, attribute, voiceKeyEnabled, voiceButtonOnPrimary, false); - final Keyboard newKeyboard = mInputView.getKeyboard(); - if (newKeyboard.isAlphaKeyboard()) { - final boolean localeChanged = (oldKeyboard == null) - || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); - mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged); - } } catch (RuntimeException e) { // Get KeyboardId to record which keyboard has been failed to load. final KeyboardId id = getKeyboardId(mode, attribute, false); @@ -192,7 +184,15 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha makeSymbolsKeyboardIds(); mCurrentId = id; mInputView.setPreviewEnabled(mInputMethodService.getPopupOn()); - mInputView.setKeyboard(getKeyboard(id)); + setKeyboard(getKeyboard(id)); + } + + private void setKeyboard(final Keyboard newKeyboard) { + final Keyboard oldKeyboard = mInputView.getKeyboard(); + mInputView.setKeyboard(newKeyboard); + final boolean localeChanged = (oldKeyboard == null) + || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); + mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged); } private LatinKeyboard getKeyboard(KeyboardId id) { @@ -278,13 +278,16 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha public boolean isKeyboardAvailable() { if (mInputView != null) - return mInputView.getLatinKeyboard() != null; + return mInputView.getKeyboard() != null; return false; } - private LatinKeyboard getLatinKeyboard() { - if (mInputView != null) - return mInputView.getLatinKeyboard(); + public LatinKeyboard getLatinKeyboard() { + if (mInputView != null) { + final Keyboard keyboard = mInputView.getKeyboard(); + if (keyboard instanceof LatinKeyboard) + return (LatinKeyboard)keyboard; + } return null; } @@ -550,7 +553,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha // indicator, we need to call enableShiftLock() and setShiftLocked(false). keyboard.setShifted(false); } - mInputView.setKeyboard(keyboard); + setKeyboard(keyboard); } public boolean isInMomentaryAutoModeSwitchState() { diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index e7246dd6e..6c3ad5edb 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -66,7 +66,8 @@ public class LatinKeyboardView extends KeyboardView { } } - public void setLatinKeyboard(LatinKeyboard newKeyboard) { + @Override + public void setKeyboard(Keyboard newKeyboard) { final LatinKeyboard oldKeyboard = getLatinKeyboard(); if (oldKeyboard != null) { // Reset old keyboard state before switching to new keyboard. @@ -80,7 +81,7 @@ public class LatinKeyboardView extends KeyboardView { mLastRowY = (newKeyboard.getHeight() * 3) / 4; } - public LatinKeyboard getLatinKeyboard() { + private LatinKeyboard getLatinKeyboard() { Keyboard keyboard = getKeyboard(); if (keyboard instanceof LatinKeyboard) { return (LatinKeyboard)keyboard; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 2a340ad0f..c9e7f8b39 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -321,7 +321,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR); final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) { - final LatinKeyboard keyboard = inputView.getLatinKeyboard(); + final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard(); // The language is never displayed when the delay is zero. if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0) inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index dee3da425..ee9dab3b7 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -371,12 +371,10 @@ public class SubtypeSwitcher { ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); mIsNetworkConnected = !noConnection; - final LatinKeyboardView inputView = KeyboardSwitcher.getInstance().getInputView(); - if (inputView != null) { - final LatinKeyboard keyboard = inputView.getLatinKeyboard(); - if (keyboard != null) { - keyboard.updateShortcutKey(isShortcutAvailable(), inputView); - } + final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); + final LatinKeyboard keyboard = switcher.getLatinKeyboard(); + if (keyboard != null) { + keyboard.updateShortcutKey(isShortcutAvailable(), switcher.getInputView()); } } -- cgit v1.2.3-83-g751a From a5d58497018f465080f08fbbfed35de883bc8be3 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 18 Feb 2011 17:50:58 +0900 Subject: Force autocorrection of matching words with different accents. When entering a word without accents the user expects the system to add accents automatically if there is no other matching word. This patch ensures the accented version is promoted accordingly and autocorrection really takes place. Issue: 3400015 Change-Id: I8cd3db5bf131ec6844b26abecc1ecbd1d6269df4 --- java/src/com/android/inputmethod/latin/Utils.java | 21 +++++--- native/src/debug.h | 58 +++++++++++++++++++++++ native/src/defines.h | 6 +-- native/src/unigram_dictionary.cpp | 48 ++++++++++++++----- 4 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 native/src/debug.h (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 7091d9b56..149c5ca9e 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -285,13 +285,22 @@ public class Utils { // (the number of matched characters between typed word and suggested word)) // * (individual word's score which defined in the unigram dictionary, // and this score is defined in range [0, 255].) - // * (when before.length() == after.length(), - // mFullWordMultiplier (this is defined 2)) - // So, maximum original score is pow(2, before.length()) * 255 * 2 - // So, we can normalize original score by dividing this value. + // Then, the following processing is applied. + // - If the dictionary word is matched up to the point of the user entry + // (full match up to min(before.length(), after.length()) + // => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2) + // - If the word is a true full match except for differences in accents or + // capitalization, then treat it as if the frequency was 255. + // - If before.length() == after.length() + // => multiply by mFullWordMultiplier (this is defined 2)) + // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2 + // For historical reasons we ignore the 1.2 modifier (because the measure for a good + // autocorrection threshold was done at a time when it didn't exist). This doesn't change + // the result. + // So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2. private static final int MAX_INITIAL_SCORE = 255; private static final int TYPED_LETTER_MULTIPLIER = 2; - private static final int FULL_WORD_MULTIPLYER = 2; + private static final int FULL_WORD_MULTIPLIER = 2; public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) { final int beforeLength = before.length(); final int afterLength = after.length(); @@ -301,7 +310,7 @@ public class Utils { // correction. final double maximumScore = MAX_INITIAL_SCORE * Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength)) - * FULL_WORD_MULTIPLYER; + * FULL_WORD_MULTIPLIER; // add a weight based on edit distance. // distance <= max(afterLength, beforeLength) == afterLength, // so, 0 <= distance / afterLength <= 1 diff --git a/native/src/debug.h b/native/src/debug.h new file mode 100644 index 000000000..e5572e1a5 --- /dev/null +++ b/native/src/debug.h @@ -0,0 +1,58 @@ +/* +** +** Copyright 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. +*/ + +#ifndef LATINIME_DEBUG_H +#define LATINIME_DEBUG_H + +#include "defines.h" + +static inline unsigned char* convertToUnibyteString(unsigned short* input, unsigned char* output, + const unsigned int length) { + int i = 0; + for (; i <= length && input[i] != 0; ++i) + output[i] = input[i] & 0xFF; + output[i] = 0; + return output; +} +static inline unsigned char* convertToUnibyteStringAndReplaceLastChar(unsigned short* input, + unsigned char* output, const unsigned int length, unsigned char c) { + int i = 0; + for (; i <= length && input[i] != 0; ++i) + output[i] = input[i] & 0xFF; + output[i-1] = c; + output[i] = 0; + return output; +} +static inline void LOGI_S16(unsigned short* string, const unsigned int length) { + unsigned char tmp_buffer[length]; + convertToUnibyteString(string, tmp_buffer, length); + LOGI(">> %s", tmp_buffer); + // The log facility is throwing out log that comes too fast. The following + // is a dirty way of slowing down processing so that we can see all log. + // TODO : refactor this in a blocking log or something. + // usleep(10); +} +static inline void LOGI_S16_PLUS(unsigned short* string, const unsigned int length, + unsigned char c) { + unsigned char tmp_buffer[length+1]; + convertToUnibyteStringAndReplaceLastChar(string, tmp_buffer, length, c); + LOGI(">> %s", tmp_buffer); + // Likewise + // usleep(10); +} + +#endif // LATINIME_DEBUG_H diff --git a/native/src/defines.h b/native/src/defines.h index 7fa7e35e0..918028afe 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -100,6 +100,9 @@ static void prof_out(void) { #ifndef U_SHORT_MAX #define U_SHORT_MAX 1 << 16 #endif +#ifndef S_INT_MAX +#define S_INT_MAX ((1 << 31) - 1) +#endif // Define this to use mmap() for dictionary loading. Undefine to use malloc() instead of mmap(). // We measured and compared performance of both, and found mmap() is fairly good in terms of @@ -137,9 +140,6 @@ static void prof_out(void) { #define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60 #define FULL_MATCHED_WORDS_PROMOTION_RATE 120 -// This is used as a bare multiplier (not subject to /100) -#define FULL_MATCH_ACCENTS_OR_CAPITALIZATION_DIFFER_MULTIPLIER 2 - // This should be greater than or equal to MAX_WORD_LENGTH defined in BinaryDictionary.java // This is only used for the size of array. Not to be used in c functions. #define MAX_WORD_LENGTH_INTERNAL 48 diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index 3d5683ed9..f36eabb3f 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -347,6 +347,10 @@ void UnigramDictionary::getWordsRec(const int childrenCount, const int pos, cons } } +static const int TWO_31ST_DIV_255 = ((1 << 31) - 1) / 255; +static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) { + return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX); +} inline int UnigramDictionary::calculateFinalFreq(const int inputIndex, const int depth, const int snr, const int skipPos, const int excessivePos, const int transposedPos, const int freq, const bool sameLength) { @@ -369,7 +373,7 @@ inline int UnigramDictionary::calculateFinalFreq(const int inputIndex, const int multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq); } if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) { - finalFreq *= FULL_MATCH_ACCENTS_OR_CAPITALIZATION_DIFFER_MULTIPLIER; + finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq); } } if (sameLength && skipPos < 0) finalFreq *= FULL_WORD_MULTIPLIER; @@ -428,24 +432,46 @@ inline bool UnigramDictionary::existsAdjacentProximityChars(const int inputIndex return false; } + +// In the following function, c is the current character of the dictionary word +// currently examined. +// currentChars is an array containing the keys close to the character the +// user actually typed at the same position. We want to see if c is in it: if so, +// then the word contains at that position a character close to what the user +// typed. +// What the user typed is actually the first character of the array. +// Notice : accented characters do not have a proximity list, so they are alone +// in their list. The non-accented version of the character should be considered +// "close", but not the other keys close to the non-accented version. inline UnigramDictionary::ProximityType UnigramDictionary::getMatchedProximityId( const int *currentChars, const unsigned short c, const int skipPos, const int excessivePos, const int transposedPos) { const unsigned short lowerC = toLowerCase(c); - int j = 0; + + // The first char in the array is what user typed. If it matches right away, + // that means the user typed that same char for this pos. + if (currentChars[0] == lowerC || currentChars[0] == c) + return SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR; + + // If one of those is true, we should not check for close characters at all. + if (skipPos >= 0 || excessivePos >= 0 || transposedPos >= 0) + return UNRELATED_CHAR; + + // If the non-accented, lowercased version of that first character matches c, + // then we have a non-accented version of the accented character the user + // typed. Treat it as a close char. + if (toLowerCase(currentChars[0]) == lowerC) + return NEAR_PROXIMITY_CHAR; + + // Not an exact nor an accent-alike match: search the list of close keys + int j = 1; while (currentChars[j] > 0 && j < MAX_PROXIMITY_CHARS) { const bool matched = (currentChars[j] == lowerC || currentChars[j] == c); - // If skipPos is defined, not to search proximity collections. - // First char is what user typed. - if (matched) { - if (j > 0) return NEAR_PROXIMITY_CHAR; - return SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR; - } else if (skipPos >= 0 || excessivePos >= 0 || transposedPos >= 0) { - // Not to check proximity characters - return UNRELATED_CHAR; - } + if (matched) return NEAR_PROXIMITY_CHAR; ++j; } + + // Was not included, signal this as an unrelated character. return UNRELATED_CHAR; } -- cgit v1.2.3-83-g751a From cb97c2f1407364b24dc1a54226481a55501d1533 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Sun, 20 Feb 2011 22:04:50 +0900 Subject: Refactor keyboard mode holding variables Change-Id: Id8d4e780a98292fda63b1e5bbf739eda78935a3b --- .../inputmethod/keyboard/KeyboardSwitcher.java | 26 ++--- .../com/android/inputmethod/latin/LatinIME.java | 121 ++++++++------------- .../android/inputmethod/latin/SubtypeSwitcher.java | 1 - java/src/com/android/inputmethod/latin/Utils.java | 53 ++++++++- 4 files changed, 103 insertions(+), 98 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 3297dc325..de5e097e3 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -67,8 +67,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private final HashMap> mKeyboardCache = new HashMap>(); - // TODO: clean mMode up and use mAttribute instead. - private int mMode = KeyboardId.MODE_TEXT; /* default value */ private EditorInfo mAttribute; private boolean mIsSymbols; /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of @@ -124,11 +122,10 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha prefs.registerOnSharedPreferenceChangeListener(sInstance); } - private void makeSymbolsKeyboardIds() { + private void makeSymbolsKeyboardIds(final int mode) { final Locale locale = mSubtypeSwitcher.getInputLocale(); final Resources res = mInputMethodService.getResources(); final int orientation = res.getConfiguration().orientation; - final int mode = mMode; final int colorScheme = getColorScheme(); final boolean hasVoiceKey = mVoiceKeyEnabled && !mVoiceButtonOnPrimary; // Note: This comment is only applied for phone number keyboard layout. @@ -151,37 +148,36 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary); } - public void loadKeyboard(int mode, EditorInfo attribute, boolean voiceKeyEnabled, + public void loadKeyboard(EditorInfo attribute, boolean voiceKeyEnabled, boolean voiceButtonOnPrimary) { mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA; try { - loadKeyboardInternal(mode, attribute, voiceKeyEnabled, voiceButtonOnPrimary, false); + loadKeyboardInternal(attribute, voiceKeyEnabled, voiceButtonOnPrimary, false); } catch (RuntimeException e) { // Get KeyboardId to record which keyboard has been failed to load. - final KeyboardId id = getKeyboardId(mode, attribute, false); + final KeyboardId id = getKeyboardId(attribute, false); Log.w(TAG, "loading keyboard failed: " + id, e); LatinImeLogger.logOnException(id.toString(), e); } } - private void loadKeyboardInternal(int mode, EditorInfo attribute, boolean voiceButtonEnabled, + private void loadKeyboardInternal(EditorInfo attribute, boolean voiceButtonEnabled, boolean voiceButtonOnPrimary, boolean isSymbols) { if (mInputView == null) return; - mMode = mode; mAttribute = attribute; mVoiceKeyEnabled = voiceButtonEnabled; mVoiceButtonOnPrimary = voiceButtonOnPrimary; mIsSymbols = isSymbols; // Update the settings key state because number of enabled IMEs could have been changed mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService); - final KeyboardId id = getKeyboardId(mode, attribute, isSymbols); + final KeyboardId id = getKeyboardId(attribute, isSymbols); final Keyboard oldKeyboard = mInputView.getKeyboard(); if (oldKeyboard != null && oldKeyboard.mId.equals(id)) return; - makeSymbolsKeyboardIds(); + makeSymbolsKeyboardIds(id.mMode); mCurrentId = id; mInputView.setPreviewEnabled(mInputMethodService.getPopupOn()); setKeyboard(getKeyboard(id)); @@ -228,7 +224,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return keyboard; } - private KeyboardId getKeyboardId(int mode, EditorInfo attribute, boolean isSymbols) { + private KeyboardId getKeyboardId(EditorInfo attribute, boolean isSymbols) { + final int mode = Utils.getKeyboardMode(attribute); final boolean hasVoiceKey = hasVoiceKey(isSymbols); final int charColorId = getColorScheme(); final int xmlId; @@ -265,7 +262,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } public int getKeyboardMode() { - return mMode; + return mCurrentId != null ? mCurrentId.mMode : KeyboardId.MODE_TEXT; } public boolean isAlphabetMode() { @@ -569,8 +566,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } private void toggleKeyboardMode() { - loadKeyboardInternal(mMode, mAttribute, mVoiceKeyEnabled, mVoiceButtonOnPrimary, - !mIsSymbols); + loadKeyboardInternal(mAttribute, mVoiceKeyEnabled, mVoiceButtonOnPrimary, !mIsSymbols); if (mIsSymbols) { mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN; } else { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index c9e7f8b39..38ee82626 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -18,7 +18,6 @@ package com.android.inputmethod.latin; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.LatinKeyboard; @@ -494,11 +493,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return container; } - private static boolean isEmailVariation(int variation) { - return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; - } - @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { final KeyboardSwitcher switcher = mKeyboardSwitcher; @@ -522,7 +516,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mVoiceConnector.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) || Utils.isVisiblePasswordInputType(attribute.inputType)); - final int mode = initializeInputAttributesAndGetMode(attribute); + initializeInputAttributes(attribute); inputView.closing(); mEnteredText = null; @@ -533,7 +527,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen loadSettings(attribute); if (mSubtypeSwitcher.isKeyboardMode()) { - switcher.loadKeyboard(mode, attribute, + switcher.loadKeyboard(attribute, mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary()); switcher.updateShiftState(); @@ -557,10 +551,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); } - // TODO: Separate calculating keyboard mode from initializing attributes, and make it an - // utility method in {@link Utils}. - private int initializeInputAttributesAndGetMode(EditorInfo attribute) { - if (attribute == null) return KeyboardId.MODE_TEXT; + private void initializeInputAttributes(EditorInfo attribute) { + if (attribute == null) + return; final int inputType = attribute.inputType; final int variation = inputType & InputType.TYPE_MASK_VARIATION; mAutoSpace = false; @@ -569,70 +562,48 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mApplicationSpecifiedCompletionOn = false; mApplicationSpecifiedCompletions = null; - final int mode; - switch (inputType & InputType.TYPE_MASK_CLASS) { - case InputType.TYPE_CLASS_NUMBER: - case InputType.TYPE_CLASS_DATETIME: - mode = KeyboardId.MODE_NUMBER; - break; - case InputType.TYPE_CLASS_PHONE: - mode = KeyboardId.MODE_PHONE; - break; - case InputType.TYPE_CLASS_TEXT: - mIsSettingsSuggestionStripOn = true; - // Make sure that passwords are not displayed in candidate view - if (Utils.isPasswordInputType(inputType) - || Utils.isVisiblePasswordInputType(inputType)) { - mIsSettingsSuggestionStripOn = false; - } - if (LatinIME.isEmailVariation(variation) - || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { - mAutoSpace = false; - } else { - mAutoSpace = true; - } - if (LatinIME.isEmailVariation(variation)) { - mIsSettingsSuggestionStripOn = false; - mode = KeyboardId.MODE_EMAIL; - } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { - mIsSettingsSuggestionStripOn = false; - mode = KeyboardId.MODE_URL; - } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { - mode = KeyboardId.MODE_IM; - } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { - mIsSettingsSuggestionStripOn = false; - mode = KeyboardId.MODE_TEXT; - } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { - mode = KeyboardId.MODE_WEB; - // If it's a browser edit field and auto correct is not ON explicitly, then - // disable auto correction, but keep suggestions on. - if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { - mInputTypeNoAutoCorrect = true; - } - } else { - mode = KeyboardId.MODE_TEXT; - } - - // If NO_SUGGESTIONS is set, don't do prediction. - if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { - mIsSettingsSuggestionStripOn = false; - mInputTypeNoAutoCorrect = true; - } - // If it's not multiline and the autoCorrect flag is not set, then don't correct - if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 && - (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { + if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { + mIsSettingsSuggestionStripOn = true; + // Make sure that passwords are not displayed in candidate view + if (Utils.isPasswordInputType(inputType) + || Utils.isVisiblePasswordInputType(inputType)) { + mIsSettingsSuggestionStripOn = false; + } + if (Utils.isEmailVariation(variation) + || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { + mAutoSpace = false; + } else { + mAutoSpace = true; + } + if (Utils.isEmailVariation(variation)) { + mIsSettingsSuggestionStripOn = false; + } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { + mIsSettingsSuggestionStripOn = false; + } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { + mIsSettingsSuggestionStripOn = false; + } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { + // If it's a browser edit field and auto correct is not ON explicitly, then + // disable auto correction, but keep suggestions on. + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { mInputTypeNoAutoCorrect = true; } - if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { - mIsSettingsSuggestionStripOn = false; - mApplicationSpecifiedCompletionOn = isFullscreenMode(); - } - break; - default: - mode = KeyboardId.MODE_TEXT; - break; + } + + // If NO_SUGGESTIONS is set, don't do prediction. + if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { + mIsSettingsSuggestionStripOn = false; + mInputTypeNoAutoCorrect = true; + } + // If it's not multiline and the autoCorrect flag is not set, then don't correct + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 + && (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { + mInputTypeNoAutoCorrect = true; + } + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { + mIsSettingsSuggestionStripOn = false; + mApplicationSpecifiedCompletionOn = isFullscreenMode(); + } } - return mode; } private void checkReCorrectionOnStart() { @@ -1909,9 +1880,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeSwitcher.toggleLanguage(reset, next); } // Reload keyboard because the current language has been changed. - final EditorInfo attribute = getCurrentInputEditorInfo(); - final int mode = initializeInputAttributesAndGetMode(attribute); - mKeyboardSwitcher.loadKeyboard(mode, attribute, + mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary()); initSuggest(); mKeyboardSwitcher.updateShiftState(); diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index ee9dab3b7..c43f5b532 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -18,7 +18,6 @@ package com.android.inputmethod.latin; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.LatinKeyboard; -import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.voice.SettingsUtil; import com.android.inputmethod.voice.VoiceIMEConnector; import com.android.inputmethod.voice.VoiceInput; diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 149c5ca9e..b4c05134f 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -45,6 +45,10 @@ public class Utils { private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4; private static boolean DBG = LatinImeLogger.sDBG; + private Utils() { + // Intentional empty constructor for utility class. + } + /** * Cancel an {@link AsyncTask}. * @@ -319,7 +323,7 @@ public class Utils { } public static class UsabilityStudyLogUtils { - private static final String TAG = "UsabilityStudyLogUtils"; + private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName(); private static final String FILENAME = "log.txt"; private static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils(); @@ -356,7 +360,7 @@ public class Utils { try { mWriter = getPrintWriter(mDirectory, FILENAME, false); } catch (IOException e) { - Log.e(TAG, "Can't create log file."); + Log.e(USABILITY_TAG, "Can't create log file."); } } } @@ -393,7 +397,7 @@ public class Utils { final String printString = String.format("%s\t%d\t%s\n", mDateFormat.format(mDate), currentTime, log); if (LatinImeLogger.sDBG) { - Log.d(TAG, "Write: " + log); + Log.d(USABILITY_TAG, "Write: " + log); } mWriter.print(printString); } @@ -414,10 +418,10 @@ public class Utils { sb.append(line); } } catch (IOException e) { - Log.e(TAG, "Can't read log file."); + Log.e(USABILITY_TAG, "Can't read log file."); } finally { if (LatinImeLogger.sDBG) { - Log.d(TAG, "output all logs\n" + sb.toString()); + Log.d(USABILITY_TAG, "output all logs\n" + sb.toString()); } mIms.getCurrentInputConnection().commitText(sb.toString(), 0); try { @@ -436,7 +440,7 @@ public class Utils { public void run() { if (mFile != null && mFile.exists()) { if (LatinImeLogger.sDBG) { - Log.d(TAG, "Delete log file."); + Log.d(USABILITY_TAG, "Delete log file."); } mFile.delete(); mWriter.close(); @@ -466,6 +470,43 @@ public class Utils { } } + public static int getKeyboardMode(EditorInfo attribute) { + if (attribute == null) + return KeyboardId.MODE_TEXT; + + final int inputType = attribute.inputType; + final int variation = inputType & InputType.TYPE_MASK_VARIATION; + + switch (inputType & InputType.TYPE_MASK_CLASS) { + case InputType.TYPE_CLASS_NUMBER: + case InputType.TYPE_CLASS_DATETIME: + return KeyboardId.MODE_NUMBER; + case InputType.TYPE_CLASS_PHONE: + return KeyboardId.MODE_PHONE; + case InputType.TYPE_CLASS_TEXT: + if (Utils.isEmailVariation(variation)) { + return KeyboardId.MODE_EMAIL; + } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { + return KeyboardId.MODE_URL; + } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { + return KeyboardId.MODE_IM; + } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { + return KeyboardId.MODE_TEXT; + } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { + return KeyboardId.MODE_WEB; + } else { + return KeyboardId.MODE_TEXT; + } + default: + return KeyboardId.MODE_TEXT; + } + } + + public static boolean isEmailVariation(int variation) { + return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; + } + // Please refer to TextView.isPasswordInputType public static boolean isPasswordInputType(int inputType) { final int variation = -- cgit v1.2.3-83-g751a From 912ade36d21b903353e7f106df49966d8494caa9 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 23 Feb 2011 17:09:24 +0900 Subject: Add countryCode attribute to case tag of Keyboard Change-Id: I0480c3350a335e4cb57bba2d3484622990cd90af --- java/res/values/attrs.xml | 1 + java/src/com/android/inputmethod/keyboard/KeyboardParser.java | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'java/src') diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index 65892784d..f0da2744b 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -184,6 +184,7 @@ + diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java index df64ad5af..2632796dd 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -437,11 +437,13 @@ public class KeyboardParser { R.styleable.Keyboard_Case_imeAction, id.mImeAction); final boolean languageCodeMatched = matchString(a, R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); + final boolean countryCodeMatched = matchString(a, + R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); final boolean selected = modeMatched && passwordInputMatched && settingsKeyMatched && voiceEnabledMatched && voiceKeyMatched && colorSchemeMatched - && imeActionMatched && languageCodeMatched; + && imeActionMatched && languageCodeMatched && countryCodeMatched; - if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s> %s", TAG_CASE, + if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE, textAttr(KeyboardId.modeName( a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"), textAttr(KeyboardId.colorSchemeName( @@ -453,6 +455,7 @@ public class KeyboardParser { textAttr(KeyboardId.imeOptionsName( a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"), textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"), + textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), "countryCode"), Boolean.toString(selected))); return selected; -- cgit v1.2.3-83-g751a From 8efc0addce3cd1a32618bf18bafabaaca2b498b0 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 23 Feb 2011 17:23:29 +0900 Subject: Fix privateImeOptions parsing This change deprecates "nm" private IME options and introduces new private option, .noMicrophone, instead. Change-Id: I9d5e3daaead2cdd42e017cd29f5f786ef3ba3649 --- .../inputmethod/latin/InputLanguageSelection.java | 2 +- .../com/android/inputmethod/latin/LatinIME.java | 32 +++++++++++++--------- java/src/com/android/inputmethod/latin/Utils.java | 27 ++++++++++++++++++ .../inputmethod/voice/VoiceIMEConnector.java | 13 ++++----- 4 files changed, 53 insertions(+), 21 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java index a9f2c2c22..5587c685f 100644 --- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java +++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java @@ -106,7 +106,7 @@ public class InputLanguageSelection extends PreferenceActivity { conf.locale = locale; res.updateConfiguration(conf, res.getDisplayMetrics()); - int mainDicResId = LatinIME.getMainDictionaryResourceId(res); + int mainDicResId = Utils.getMainDictionaryResourceId(res); BinaryDictionary bd = BinaryDictionary.initDictionary(this, mainDicResId, Suggest.DIC_MAIN); // Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 38ee82626..40cebb395 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -81,11 +81,27 @@ import java.util.Locale; * Input method implementation for Qwerty'ish keyboard. */ public class LatinIME extends InputMethodService implements KeyboardActionListener { - private static final String TAG = "LatinIME"; + private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean PERF_DEBUG = false; private static final boolean TRACE = false; private static boolean DEBUG = LatinImeLogger.sDBG; + /** + * The private IME option used to indicate that no microphone should be + * shown for a given text field. For instance, this is specified by the + * search dialog when the dialog is already showing a voice search button. + * + * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed. + */ + public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm"; + + /** + * The private IME option used to indicate that no microphone should be + * shown for a given text field. For instance, this is specified by the + * search dialog when the dialog is already showing a voice search button. + */ + public static final String IME_OPTION_NO_MICROPHONE = "noMicrophone"; + private static final int DELAY_UPDATE_SUGGESTIONS = 180; private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300; private static final int DELAY_UPDATE_SHIFT_STATE = 300; @@ -345,7 +361,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen super.onCreate(); mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)); - mInputMethodId = Utils.getInputMethodId(mImm, getApplicationInfo().packageName); + mInputMethodId = Utils.getInputMethodId(mImm, getPackageName()); mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mKeyboardSwitcher = KeyboardSwitcher.getInstance(); @@ -394,16 +410,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler); } - /** - * Returns a main dictionary resource id - * @return main dictionary resource id - */ - public static int getMainDictionaryResourceId(Resources res) { - final String MAIN_DIC_NAME = "main"; - String packageName = LatinIME.class.getPackage().getName(); - return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName); - } - private void initSuggest() { updateAutoTextEnabled(); String locale = mSubtypeSwitcher.getInputLocaleStr(); @@ -416,7 +422,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mQuickFixes = isQuickFixesEnabled(prefs); final Resources res = mResources; - int mainDicResId = getMainDictionaryResourceId(res); + int mainDicResId = Utils.getMainDictionaryResourceId(res); mSuggest = new Suggest(this, mainDicResId); loadAndSetAutoCorrectionThreshold(prefs); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index b4c05134f..f26766768 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.keyboard.KeyboardId; +import android.content.res.Resources; import android.inputmethodservice.InputMethodService; import android.os.AsyncTask; import android.os.Handler; @@ -526,4 +527,30 @@ public class Utils { return variation == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); } + + public static boolean containsInCsv(String key, String csv) { + if (csv == null) + return false; + for (String option : csv.split(",")) { + if (option.equals(key)) + return true; + } + return false; + } + + public static boolean inPrivateImeOptions(String packageName, String key, + EditorInfo attribute) { + if (attribute == null) + return false; + return containsInCsv(packageName != null ? packageName + "." + key : key, + attribute.privateImeOptions); + } + + /** + * Returns a main dictionary resource id + * @return main dictionary resource id + */ + public static int getMainDictionaryResourceId(Resources res) { + return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName()); + } } diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java index 277ef7e65..105656fe0 100644 --- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java +++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java @@ -25,6 +25,7 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SharedPreferencesCompat; import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.Utils; import android.app.AlertDialog; import android.content.Context; @@ -74,10 +75,6 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { // For example, the user has a Chinese UI but activates voice input. private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = "has_used_voice_input_unsupported_locale"; - // The private IME option used to indicate that no microphone should be shown for a - // given text field. For instance this is specified by the search dialog when the - // dialog is already showing a voice search button. - private static final String IME_OPTION_NO_MICROPHONE = "nm"; private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6; private static final String TAG = VoiceIMEConnector.class.getSimpleName(); @@ -627,9 +624,11 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { } private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { - return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) - && !(attribute != null - && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions)) + final boolean noMic = Utils.inPrivateImeOptions(null, + LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute) + || Utils.inPrivateImeOptions(mService.getPackageName(), + LatinIME.IME_OPTION_NO_MICROPHONE, attribute); + return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic && SpeechRecognizer.isRecognitionAvailable(mService); } -- cgit v1.2.3-83-g751a From 8fbd55229243cb66c03d5ea1f79dfb39f596590d Mon Sep 17 00:00:00 2001 From: satok Date: Tue, 22 Feb 2011 17:28:55 +0900 Subject: Add proximity info to native Bug: 3311719 Change-Id: Ie596304070e321ad23fb67a13bf05e2b6af1b54b --- .../com/android/inputmethod/keyboard/Keyboard.java | 12 ++- .../android/inputmethod/keyboard/KeyboardView.java | 4 +- .../inputmethod/keyboard/ProximityInfo.java | 77 ++++++++++++++++++ .../inputmethod/latin/BinaryDictionary.java | 38 ++++----- .../com/android/inputmethod/latin/LatinIME.java | 16 +++- .../android/inputmethod/latin/WordComposer.java | 54 ++++++++++--- native/Android.mk | 3 + ..._android_inputmethod_keyboard_ProximityInfo.cpp | 90 ++++++++++++++++++++++ ...om_android_inputmethod_keyboard_ProximityInfo.h | 27 +++++++ ..._android_inputmethod_latin_BinaryDictionary.cpp | 55 +++++-------- ...om_android_inputmethod_latin_BinaryDictionary.h | 27 +++++++ native/jni/onload.cpp | 62 +++++++++++++++ native/src/defines.h | 2 + native/src/dictionary.cpp | 4 +- native/src/dictionary.h | 8 +- native/src/proximity_info.cpp | 34 ++++++++ native/src/proximity_info.h | 40 ++++++++++ native/src/unigram_dictionary.cpp | 16 ++-- native/src/unigram_dictionary.h | 8 +- .../android/inputmethod/latin/SuggestHelper.java | 8 +- 20 files changed, 499 insertions(+), 86 deletions(-) create mode 100644 java/src/com/android/inputmethod/keyboard/ProximityInfo.java create mode 100644 native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp create mode 100644 native/jni/com_android_inputmethod_keyboard_ProximityInfo.h create mode 100644 native/jni/com_android_inputmethod_latin_BinaryDictionary.h create mode 100644 native/jni/onload.cpp create mode 100644 native/src/proximity_info.cpp create mode 100644 native/src/proximity_info.h (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 3a0bf53ab..3005920b1 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -132,6 +132,7 @@ public class Keyboard { // Variables for pre-computing nearest keys. + // TODO: Change GRID_WIDTH and GRID_HEIGHT to private. public final int GRID_WIDTH; public final int GRID_HEIGHT; private final int GRID_SIZE; @@ -143,6 +144,8 @@ public class Keyboard { /** Number of key widths from current touch point to search for nearest keys. */ private static float SEARCH_DISTANCE = 1.2f; + private final ProximityInfo mProximityInfo; + /** * Creates a keyboard from the given xml key layout file. * @param context the application or service context @@ -171,6 +174,11 @@ public class Keyboard { mDefaultHeight = mDefaultWidth; mId = id; loadKeyboard(context, xmlLayoutResId); + mProximityInfo = new ProximityInfo(mDisplayWidth, mDisplayHeight, GRID_WIDTH, GRID_HEIGHT); + } + + public int getProximityInfo() { + return mProximityInfo.getNativeProximityInfo(this); } public List getKeys() { @@ -345,7 +353,8 @@ public class Keyboard { return mId != null && mId.isNumberKeyboard(); } - private void computeNearestNeighbors() { + // TODO: Move this function to ProximityInfo and make this private. + public void computeNearestNeighbors() { // Round-up so we don't have any pixels outside the grid mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH; mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT; @@ -369,6 +378,7 @@ public class Keyboard { mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; } } + mProximityInfo.setProximityInfo(mGridNeighbors); } public boolean isInside(Key key, int x, int y) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 7fee022e0..25bf0e0b6 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -762,6 +762,8 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { canvas.translate(-key.mX - kbdPaddingLeft, -key.mY - kbdPaddingTop); } + // TODO: Move this function to ProximityInfo for getting rid of public declarations for + // GRID_WIDTH and GRID_HEIGHT if (DEBUG_KEYBOARD_GRID) { Paint p = new Paint(); p.setStyle(Paint.Style.STROKE); @@ -1028,7 +1030,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0); } - private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) { + private void onDoubleTapShiftKey(PointerTracker tracker) { // When shift key is double tapped, the first tap is correctly processed as usual tap. And // the second tap is treated as this double tap event, so that we need not mark tracker // calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue. diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java new file mode 100644 index 000000000..fe836c1e2 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * 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.keyboard; + +public class ProximityInfo { + public static final int MAX_PROXIMITY_CHARS_SIZE = 16; + + private final int mDisplayWidth; + private final int mDisplayHeight; + private final int mGridWidth; + private final int mGridHeight; + private final int mGridSize; + + ProximityInfo(int displayWidth, int displayHeight, int gridWidth, int gridHeight) { + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + mGridWidth = gridWidth; + mGridHeight = gridHeight; + mGridSize = mGridWidth * mGridHeight; + } + + private int mNativeProximityInfo; + private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth, + int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray); + private native void releaseProximityInfoNative(int nativeProximityInfo); + + public final void setProximityInfo(int[][] gridNeighbors) { + int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; + for (int i = 0; i < mGridSize; ++i) { + final int proximityCharsLength = gridNeighbors[i].length; + for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; ++j) { + int charCode = KeyDetector.NOT_A_KEY; + if (j < proximityCharsLength) { + charCode = gridNeighbors[i][j]; + } + proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = charCode; + } + } + mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE, + mDisplayWidth, mDisplayHeight, mGridWidth, mGridHeight, proximityCharsArray); + } + + // TODO: Get rid of this function's input (keyboard). + public int getNativeProximityInfo(Keyboard keyboard) { + if (mNativeProximityInfo == 0) { + // TODO: Move this function to ProximityInfo and make this private. + keyboard.computeNearestNeighbors(); + } + return mNativeProximityInfo; + } + + @Override + protected void finalize() throws Throwable { + try { + if (mNativeProximityInfo != 0) { + releaseProximityInfoNative(mNativeProximityInfo); + mNativeProximityInfo = 0; + } + } finally { + super.finalize(); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index ff7e2b88a..33b8c7af6 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -16,6 +16,9 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.ProximityInfo; + import android.content.Context; import android.content.res.AssetFileDescriptor; import android.util.Log; @@ -34,10 +37,10 @@ public class BinaryDictionary extends Dictionary { * It is necessary to keep it at this value because some languages e.g. German have * really long words. */ - protected static final int MAX_WORD_LENGTH = 48; + public static final int MAX_WORD_LENGTH = 48; private static final String TAG = "BinaryDictionary"; - private static final int MAX_ALTERNATIVES = 16; + private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; private static final int MAX_WORDS = 18; private static final int MAX_BIGRAMS = 60; @@ -47,19 +50,13 @@ public class BinaryDictionary extends Dictionary { private int mDicTypeId; private int mNativeDict; private long mDictLength; - private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES]; + private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE]; private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; private final int[] mFrequencies = new int[MAX_WORDS]; private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS]; - static { - try { - System.loadLibrary("jni_latinime"); - } catch (UnsatisfiedLinkError ule) { - Log.e(TAG, "Could not load native library jni_latinime"); - } - } + private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance(); private BinaryDictionary() { } @@ -117,8 +114,9 @@ public class BinaryDictionary extends Dictionary { int maxWords, int maxAlternatives); private native void closeNative(int dict); private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); - private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, - char[] outputChars, int[] frequencies); + private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates, + int[] yCoordinates, int[] inputCodes, int codesSize, char[] outputChars, + int[] frequencies); private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies, int maxWordLength, int maxBigrams, int maxAlternatives); @@ -126,7 +124,7 @@ public class BinaryDictionary extends Dictionary { private final void loadDictionary(String path, long startOffset, long length) { mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER, - MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES); + MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE); mDictLength = length; } @@ -143,11 +141,11 @@ public class BinaryDictionary extends Dictionary { Arrays.fill(mInputCodes, -1); int[] alternatives = codes.getCodesAt(0); System.arraycopy(alternatives, 0, mInputCodes, 0, - Math.min(alternatives.length, MAX_ALTERNATIVES)); + Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize, mOutputChars_bigrams, mFrequencies_bigrams, MAX_WORD_LENGTH, MAX_BIGRAMS, - MAX_ALTERNATIVES); + MAX_PROXIMITY_CHARS_SIZE); for (int j = 0; j < count; ++j) { if (mFrequencies_bigrams[j] < 1) break; @@ -174,14 +172,16 @@ public class BinaryDictionary extends Dictionary { Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE); for (int i = 0; i < codesSize; i++) { int[] alternatives = codes.getCodesAt(i); - System.arraycopy(alternatives, 0, mInputCodes, i * MAX_ALTERNATIVES, - Math.min(alternatives.length, MAX_ALTERNATIVES)); + System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE, + Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); } Arrays.fill(mOutputChars, (char) 0); Arrays.fill(mFrequencies, 0); - int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars, - mFrequencies); + int count = getSuggestionsNative( + mNativeDict, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo(), + codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, + mOutputChars, mFrequencies); for (int j = 0; j < count; ++j) { if (mFrequencies[j] < 1) break; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 38ee82626..638578a1a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -187,6 +187,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private final ArrayList mWordHistory = new ArrayList(); + static { + try { + System.loadLibrary("jni_latinime"); + } catch (UnsatisfiedLinkError ule) { + Log.e(TAG, "Could not load native library jni_latinime"); + } + } + public abstract static class WordAlternatives { protected CharSequence mChosenWord; @@ -1119,7 +1127,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (isWordSeparator(primaryCode)) { handleSeparator(primaryCode); } else { - handleCharacter(primaryCode, keyCodes); + handleCharacter(primaryCode, keyCodes, x, y); } // Cancel the just reverted state mJustReverted = false; @@ -1244,7 +1252,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void handleCharacter(int primaryCode, int[] keyCodes) { + private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) { mVoiceConnector.handleCharacter(); if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) { @@ -1285,7 +1293,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mWord.setFirstCharCapitalized(true); } mComposing.append((char) code); - mWord.add(code, keyCodes); + mWord.add(code, keyCodes, x, y); InputConnection ic = getCurrentInputConnection(); if (ic != null) { // If it's the first letter, make note of auto-caps state @@ -1692,7 +1700,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen for (int i = 0; i < touching.mWord.length(); i++) { foundWord.add(touching.mWord.charAt(i), new int[] { touching.mWord.charAt(i) - }); + }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); } foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.mWord.charAt(0))); } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index e003dcd5b..02583895b 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -24,18 +24,24 @@ import java.util.ArrayList; * A place to store the currently composing word with information such as adjacent key codes as well */ public class WordComposer { + public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE; + public static final int NOT_A_COORDINATE = -1; /** * The list of unicode values for each keystroke (including surrounding keys) */ private final ArrayList mCodes; - + + private int mTypedLength; + private final int[] mXCoordinates; + private final int[] mYCoordinates; + /** * The word chosen from the candidate list, until it is committed. */ private String mPreferredWord; - + private final StringBuilder mTypedWord; private int mCapsCount; @@ -48,17 +54,24 @@ public class WordComposer { private boolean mIsFirstCharCapitalized; public WordComposer() { - mCodes = new ArrayList(12); - mTypedWord = new StringBuilder(20); + final int N = BinaryDictionary.MAX_WORD_LENGTH; + mCodes = new ArrayList(N); + mTypedWord = new StringBuilder(N); + mTypedLength = 0; + mXCoordinates = new int[N]; + mYCoordinates = new int[N]; } - WordComposer(WordComposer copy) { - mCodes = new ArrayList(copy.mCodes); - mPreferredWord = copy.mPreferredWord; - mTypedWord = new StringBuilder(copy.mTypedWord); - mCapsCount = copy.mCapsCount; - mAutoCapitalized = copy.mAutoCapitalized; - mIsFirstCharCapitalized = copy.mIsFirstCharCapitalized; + WordComposer(WordComposer source) { + mCodes = new ArrayList(source.mCodes); + mPreferredWord = source.mPreferredWord; + mTypedWord = new StringBuilder(source.mTypedWord); + mCapsCount = source.mCapsCount; + mAutoCapitalized = source.mAutoCapitalized; + mIsFirstCharCapitalized = source.mIsFirstCharCapitalized; + mTypedLength = source.mTypedLength; + mXCoordinates = source.mXCoordinates; + mYCoordinates = source.mYCoordinates; } /** @@ -66,6 +79,7 @@ public class WordComposer { */ public void reset() { mCodes.clear(); + mTypedLength = 0; mIsFirstCharCapitalized = false; mPreferredWord = null; mTypedWord.setLength(0); @@ -89,15 +103,28 @@ public class WordComposer { return mCodes.get(index); } + public int[] getXCoordinates() { + return mXCoordinates; + } + + public int[] getYCoordinates() { + return mYCoordinates; + } + /** * Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of * the array containing unicode for adjacent keys, sorted by reducing probability/proximity. * @param codes the array of unicode values */ - public void add(int primaryCode, int[] codes) { + public void add(int primaryCode, int[] codes, int x, int y) { mTypedWord.append((char) primaryCode); correctPrimaryJuxtapos(primaryCode, codes); mCodes.add(codes); + if (mTypedLength < BinaryDictionary.MAX_WORD_LENGTH) { + mXCoordinates[mTypedLength] = x; + mYCoordinates[mTypedLength] = y; + } + ++mTypedLength; if (Character.isUpperCase((char) primaryCode)) mCapsCount++; } @@ -128,6 +155,9 @@ public class WordComposer { mTypedWord.deleteCharAt(lastPos); if (Character.isUpperCase(last)) mCapsCount--; } + if (mTypedLength > 0) { + --mTypedLength; + } } /** diff --git a/native/Android.mk b/native/Android.mk index a8fe06d50..c8342e31f 100644 --- a/native/Android.mk +++ b/native/Android.mk @@ -4,10 +4,13 @@ include $(CLEAR_VARS) LOCAL_C_INCLUDES += $(LOCAL_PATH)/src LOCAL_SRC_FILES := \ + jni/com_android_inputmethod_keyboard_ProximityInfo.cpp \ jni/com_android_inputmethod_latin_BinaryDictionary.cpp \ + jni/onload.cpp \ src/bigram_dictionary.cpp \ src/char_utils.cpp \ src/dictionary.cpp \ + src/proximity_info.cpp \ src/unigram_dictionary.cpp #FLAG_DBG := true diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp new file mode 100644 index 000000000..3db89edf1 --- /dev/null +++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp @@ -0,0 +1,90 @@ +/* +** +** Copyright 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. +*/ + +#define LOG_TAG "LatinIME: jni: ProximityInfo" + +#include "com_android_inputmethod_keyboard_ProximityInfo.h" +#include "jni.h" +#include "proximity_info.h" + +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace latinime { + +// +// helper function to throw an exception +// +static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) { + if (jclass cls = env->FindClass(ex)) { + char msg[1000]; + snprintf(msg, sizeof(msg), fmt, data); + env->ThrowNew(cls, msg); + env->DeleteLocalRef(cls); + } +} + +static jint latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object, + jint maxProximityCharsSize, jint displayWidth, jint displayHeight, jint gridWidth, + jint gridHeight, jintArray proximityCharsArray) { + jint* proximityChars = env->GetIntArrayElements(proximityCharsArray, NULL); + ProximityInfo *proximityInfo = new ProximityInfo(maxProximityCharsSize, displayWidth, + displayHeight, gridWidth, gridHeight, (const uint32_t *)proximityChars); + env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0); + return (jint)proximityInfo; +} + +static void latinime_Keyboard_release(JNIEnv *env, jobject object, jint proximityInfo) { + ProximityInfo *pi = (ProximityInfo*)proximityInfo; + if (!pi) return; + delete pi; +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod sKeyboardMethods[] = { + {"setProximityInfoNative", "(IIIII[I)I", (void*)latinime_Keyboard_setProximityInfo}, + {"releaseProximityInfoNative", "(I)V", (void*)latinime_Keyboard_release} +}; + +static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, + int numMethods) { + jclass clazz; + + clazz = env->FindClass(className); + if (clazz == NULL) { + LOGE("Native registration unable to find class '%s'", className); + return JNI_FALSE; + } + if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { + LOGE("RegisterNatives failed for '%s'", className); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +int register_ProximityInfo(JNIEnv *env) { + const char* const kClassPathName = "com/android/inputmethod/keyboard/ProximityInfo"; + return registerNativeMethods(env, kClassPathName, sKeyboardMethods, + sizeof(sKeyboardMethods) / sizeof(sKeyboardMethods[0])); +} + +}; // namespace latinime diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.h b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.h new file mode 100644 index 000000000..bdeeb8f37 --- /dev/null +++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.h @@ -0,0 +1,27 @@ +/* +** +** Copyright 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. +*/ + +#ifndef _COM_ANDROID_INPUTMETHOD_KEYBOARD_PROXIMITYINFO_H +#define _COM_ANDROID_INPUTMETHOD_KEYBOARD_PROXIMITYINFO_H + +#include "jni.h" + +namespace latinime { +int register_ProximityInfo(JNIEnv *env); +} + +#endif // _COM_ANDROID_INPUTMETHOD_KEYBOARD_PROXIMITYINFO_H diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index 4660103b1..b10dd6d7b 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -15,10 +15,12 @@ ** limitations under the License. */ -#define LOG_TAG "LatinIME: jni" +#define LOG_TAG "LatinIME: jni: BinaryDictionary" +#include "com_android_inputmethod_latin_BinaryDictionary.h" #include "dictionary.h" #include "jni.h" +#include "proximity_info.h" #include #include @@ -35,7 +37,7 @@ // ---------------------------------------------------------------------------- -using namespace latinime; +namespace latinime { // // helper function to throw an exception @@ -43,7 +45,7 @@ using namespace latinime; static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) { if (jclass cls = env->FindClass(ex)) { char msg[1000]; - sprintf(msg, fmt, data); + snprintf(msg, sizeof(msg), fmt, data); env->ThrowNew(cls, msg); env->DeleteLocalRef(cls); } @@ -123,19 +125,27 @@ static jint latinime_BinaryDictionary_open(JNIEnv *env, jobject object, } static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jint dict, + jint proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray, jintArray inputArray, jint arraySize, jcharArray outputArray, jintArray frequencyArray) { Dictionary *dictionary = (Dictionary*)dict; if (!dictionary) return 0; + ProximityInfo *pInfo = (ProximityInfo*)proximityInfo; + if (!pInfo) return 0; + + int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, NULL); + int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, NULL); int *frequencies = env->GetIntArrayElements(frequencyArray, NULL); int *inputCodes = env->GetIntArrayElements(inputArray, NULL); jchar *outputChars = env->GetCharArrayElements(outputArray, NULL); - int count = dictionary->getSuggestions(inputCodes, arraySize, (unsigned short*) outputChars, - frequencies); + int count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, inputCodes, + arraySize, (unsigned short*) outputChars, frequencies); env->ReleaseIntArrayElements(frequencyArray, frequencies, 0); env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT); + env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0); + env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0); env->ReleaseCharArrayElements(outputArray, outputChars, 0); return count; @@ -200,10 +210,10 @@ static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jint di // ---------------------------------------------------------------------------- -static JNINativeMethod gMethods[] = { +static JNINativeMethod sMethods[] = { {"openNative", "(Ljava/lang/String;JJIIIII)I", (void*)latinime_BinaryDictionary_open}, {"closeNative", "(I)V", (void*)latinime_BinaryDictionary_close}, - {"getSuggestionsNative", "(I[II[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions}, + {"getSuggestionsNative", "(II[I[I[II[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions}, {"isValidWordNative", "(I[CI)Z", (void*)latinime_BinaryDictionary_isValidWord}, {"getBigramsNative", "(I[CI[II[C[IIII)I", (void*)latinime_BinaryDictionary_getBigrams} }; @@ -225,33 +235,10 @@ static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMe return JNI_TRUE; } -static int registerNatives(JNIEnv *env) { +int register_BinaryDictionary(JNIEnv *env) { const char* const kClassPathName = "com/android/inputmethod/latin/BinaryDictionary"; - return registerNativeMethods(env, kClassPathName, gMethods, - sizeof(gMethods) / sizeof(gMethods[0])); + return registerNativeMethods(env, kClassPathName, sMethods, + sizeof(sMethods) / sizeof(sMethods[0])); } -/* - * Returns the JNI version on success, -1 on failure. - */ -jint JNI_OnLoad(JavaVM* vm, void* reserved) { - JNIEnv* env = NULL; - jint result = -1; - - if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { - LOGE("ERROR: GetEnv failed"); - goto bail; - } - assert(env != NULL); - - if (!registerNatives(env)) { - LOGE("ERROR: BinaryDictionary native registration failed"); - goto bail; - } - - /* success -- return valid version number */ - result = JNI_VERSION_1_4; - -bail: - return result; -} +}; // namespace latinime diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.h b/native/jni/com_android_inputmethod_latin_BinaryDictionary.h new file mode 100644 index 000000000..f7cd81fa7 --- /dev/null +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.h @@ -0,0 +1,27 @@ +/* +** +** Copyright 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. +*/ + +#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARY_H +#define _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARY_H + +#include "jni.h" + +namespace latinime { +int register_BinaryDictionary(JNIEnv *env); +} + +#endif // _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARY_H diff --git a/native/jni/onload.cpp b/native/jni/onload.cpp new file mode 100644 index 000000000..f02c9a052 --- /dev/null +++ b/native/jni/onload.cpp @@ -0,0 +1,62 @@ +/* +** +** Copyright 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. +*/ + +#define LOG_TAG "LatinIME: jni" + +#include "com_android_inputmethod_keyboard_ProximityInfo.h" +#include "com_android_inputmethod_latin_BinaryDictionary.h" +#include "jni.h" +#include "proximity_info.h" + +#include +#include +#include + +// ---------------------------------------------------------------------------- + +using namespace latinime; + + +/* + * Returns the JNI version on success, -1 on failure. + */ +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("ERROR: GetEnv failed"); + goto bail; + } + assert(env != NULL); + + if (!register_BinaryDictionary(env)) { + LOGE("ERROR: BinaryDictionary native registration failed"); + goto bail; + } + + if (!register_ProximityInfo(env)) { + LOGE("ERROR: ProximityInfo native registration failed"); + goto bail; + } + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + +bail: + return result; +} diff --git a/native/src/defines.h b/native/src/defines.h index 918028afe..ddd65c9fa 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -28,6 +28,7 @@ #define DEBUG_SHOW_FOUND_WORD DEBUG_DICT_FULL #define DEBUG_NODE DEBUG_DICT_FULL #define DEBUG_TRACE DEBUG_DICT_FULL +#define DEBUG_PROXIMITY_INFO true // Profiler #include @@ -83,6 +84,7 @@ static void prof_out(void) { #define DEBUG_SHOW_FOUND_WORD false #define DEBUG_NODE false #define DEBUG_TRACE false +#define DEBUG_PROXIMITY_INFO false #define PROF_BUF_SIZE 0 #define PROF_RESET diff --git a/native/src/dictionary.cpp b/native/src/dictionary.cpp index fe3375706..d69cb2a53 100644 --- a/native/src/dictionary.cpp +++ b/native/src/dictionary.cpp @@ -23,6 +23,7 @@ namespace latinime { +// TODO: Change the type of all keyCodes to uint32_t Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives) @@ -53,8 +54,7 @@ bool Dictionary::hasBigram() { } // TODO: use uint16_t instead of unsigned short -bool Dictionary::isValidWord(unsigned short *word, int length) -{ +bool Dictionary::isValidWord(unsigned short *word, int length) { if (IS_LATEST_DICT_VERSION) { return (isValidWordRec(DICTIONARY_HEADER_SIZE, word, 0, length) != NOT_VALID_WORD); } else { diff --git a/native/src/dictionary.h b/native/src/dictionary.h index 941bd191a..fbbb8312b 100644 --- a/native/src/dictionary.h +++ b/native/src/dictionary.h @@ -19,6 +19,7 @@ #include "bigram_dictionary.h" #include "defines.h" +#include "proximity_info.h" #include "unigram_dictionary.h" namespace latinime { @@ -27,8 +28,10 @@ class Dictionary { public: Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler, int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives); - int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies) { - return mUnigramDictionary->getSuggestions(codes, codesSize, outWords, frequencies); + int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates, + int *codes, int codesSize, unsigned short *outWords, int *frequencies) { + return mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, + codesSize, outWords, frequencies); } // TODO: Call mBigramDictionary instead of mUnigramDictionary @@ -38,6 +41,7 @@ public: return mBigramDictionary->getBigrams(word, length, codes, codesSize, outWords, frequencies, maxWordLength, maxBigrams, maxAlternatives); } + bool isValidWord(unsigned short *word, int length); int isValidWordRec(int pos, unsigned short *word, int offset, int length); void *getDict() { return (void *)mDict; } diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp new file mode 100644 index 000000000..d0cba3eb6 --- /dev/null +++ b/native/src/proximity_info.cpp @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#include +#include + +#include "proximity_info.h" + +namespace latinime { +ProximityInfo::ProximityInfo(int maxProximityCharsSize, int displayWidth, int displayHeight, + int gridWidth, int gridHeight, uint32_t const *proximityCharsArray) + : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), DISPLAY_WIDTH(displayWidth), + DISPLAY_HEIGHT(displayHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight) { + mProximityCharsArray = new uint32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE]; + memcpy(mProximityCharsArray, proximityCharsArray, sizeof(mProximityCharsArray)); +} + +ProximityInfo::~ProximityInfo() { + delete[] mProximityCharsArray; +} +} diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h new file mode 100644 index 000000000..54e995059 --- /dev/null +++ b/native/src/proximity_info.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef LATINIME_PROXIMITY_INFO_H +#define LATINIME_PROXIMITY_INFO_H + +#include + +#include "defines.h" + +namespace latinime { + +class ProximityInfo { +public: + ProximityInfo(int maxProximityCharsSize, int displayWidth, int displayHeight, int gridWidth, + int gridHeight, uint32_t const *proximityCharsArray); + ~ProximityInfo(); +private: + const int MAX_PROXIMITY_CHARS_SIZE; + const int DISPLAY_WIDTH; + const int DISPLAY_HEIGHT; + const int GRID_WIDTH; + const int GRID_HEIGHT; + uint32_t *mProximityCharsArray; +}; +}; // namespace latinime +#endif // LATINIME_PROXIMITY_INFO_H diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index 0ea650629..72b0f361a 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -41,13 +41,19 @@ UnigramDictionary::UnigramDictionary(const unsigned char *dict, int typedLetterM UnigramDictionary::~UnigramDictionary() {} -int UnigramDictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWords, - int *frequencies) { +int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, + int *ycoordinates, int *codes, int codesSize, unsigned short *outWords, int *frequencies) { PROF_OPEN; PROF_START(0); initSuggestions(codes, codesSize, outWords, frequencies); if (DEBUG_DICT) assert(codesSize == mInputLength); + if (DEBUG_PROXIMITY_INFO) { + for (int i = 0; i < codesSize; ++i) { + LOGI("Input[%d] x = %d, y = %d", i, xcoordinates[i], ycoordinates[i]); + } + } + const int MAX_DEPTH = min(mInputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH); PROF_END(0); @@ -406,7 +412,7 @@ inline void UnigramDictionary::onTerminalWhenUserTypedLengthIsSameAsInputLength( inline bool UnigramDictionary::needsToSkipCurrentNode(const unsigned short c, const int inputIndex, const int skipPos, const int depth) { - const unsigned short userTypedChar = (mInputCodes + (inputIndex * MAX_PROXIMITY_CHARS))[0]; + const unsigned short userTypedChar = getInputCharsAt(inputIndex)[0]; // Skip the ' or other letter and continue deeper return (c == QUOTE && userTypedChar != QUOTE) || skipPos == depth; } @@ -517,7 +523,7 @@ inline bool UnigramDictionary::processCurrentNode(const int pos, const int depth *newDiffs = diffs; *newInputIndex = inputIndex; } else { - int *currentChars = mInputCodes + (inputIndex * MAX_PROXIMITY_CHARS); + int *currentChars = getInputCharsAt(inputIndex); if (transposedPos >= 0) { if (inputIndex == transposedPos) currentChars += MAX_PROXIMITY_CHARS; @@ -620,7 +626,7 @@ inline bool UnigramDictionary::processCurrentNodeForExactMatch(const int firstCh const int startInputIndex, const int depth, unsigned short *word, int *newChildPosition, int *newCount, bool *newTerminal, int *newFreq, int *siblingPos) { const int inputIndex = startInputIndex + depth; - const int *currentChars = mInputCodes + (inputIndex * MAX_PROXIMITY_CHARS); + const int *currentChars = getInputCharsAt(inputIndex); unsigned short c; *siblingPos = Dictionary::setDictionaryValues(DICT, IS_LATEST_DICT_VERSION, firstChildPos, &c, newChildPosition, newTerminal, newFreq); diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index db40646e1..e84875b59 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -18,6 +18,7 @@ #define LATINIME_UNIGRAM_DICTIONARY_H #include "defines.h" +#include "proximity_info.h" namespace latinime { @@ -32,7 +33,8 @@ class UnigramDictionary { public: UnigramDictionary(const unsigned char *dict, int typedLetterMultipler, int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion); - int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies); + int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates, + int *codes, int codesSize, unsigned short *outWords, int *frequencies); ~UnigramDictionary(); private: @@ -84,7 +86,9 @@ private: const int startInputIndex, const int depth, unsigned short *word, int *newChildPosition, int *newCount, bool *newTerminal, int *newFreq, int *siblingPos); bool existsAdjacentProximityChars(const int inputIndex, const int inputLength); - int* getInputCharsAt(const int index) {return mInputCodes + (index * MAX_PROXIMITY_CHARS);} + inline int* getInputCharsAt(const int index) { + return mInputCodes + (index * MAX_PROXIMITY_CHARS); + } const unsigned char *DICT; const int MAX_WORD_LENGTH; const int MAX_WORDS; diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java index 88f89d9ae..a845acb9a 100644 --- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java +++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java @@ -64,7 +64,7 @@ public class SuggestHelper { return mSuggest.hasMainDictionary(); } - private int[] getProximityCodes(char c) { + private void addKeyInfo(WordComposer word, char c) { final List keys = mKeyboard.getKeys(); for (final Key key : keys) { if (key.mCode == c) { @@ -72,17 +72,17 @@ public class SuggestHelper { final int y = key.mY + key.mHeight / 2; final int[] codes = mKeyDetector.newCodeArray(); mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); - return codes; + word.add(c, codes, x, y); } } - return new int[] { c }; + word.add(c, new int[] { c }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); } protected WordComposer createWordComposer(CharSequence s) { WordComposer word = new WordComposer(); for (int i = 0; i < s.length(); i++) { final char c = s.charAt(i); - word.add(c, getProximityCodes(c)); + addKeyInfo(word, c); } return word; } -- cgit v1.2.3-83-g751a From dcade18113286a132a2f5fb508fdba836470c3fb Mon Sep 17 00:00:00 2001 From: Alan Viverette Date: Fri, 14 Jan 2011 10:49:21 -0800 Subject: Added support for speaking keys through accessibility service. Issue: 3366636 Change-Id: I70a2ebb4420782ba4d7d874ffc2259bb1b80446f --- java/res/values/keycodes.xml | 20 ++ java/res/values/strings.xml | 25 +++ .../inputmethod/keyboard/KeyboardSwitcher.java | 22 ++- .../android/inputmethod/keyboard/KeyboardView.java | 39 +++- .../inputmethod/keyboard/LatinKeyboardView.java | 4 + .../inputmethod/keyboard/PointerTracker.java | 28 ++- .../inputmethod/latin/AccessibilityUtils.java | 211 +++++++++++++++++++++ .../com/android/inputmethod/latin/LatinIME.java | 14 +- 8 files changed, 350 insertions(+), 13 deletions(-) create mode 100644 java/src/com/android/inputmethod/latin/AccessibilityUtils.java (limited to 'java/src') diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml index 6c18cb42a..d6f9bfc28 100644 --- a/java/res/values/keycodes.xml +++ b/java/res/values/keycodes.xml @@ -28,4 +28,24 @@ -5 -100 -102 + + + + @integer/key_tab + @string/description_tab_key + @integer/key_return + @string/description_return_key + @integer/key_space + @string/description_space_key + @integer/key_shift + @string/description_shift_key + @integer/key_switch_alpha_symbol + @string/description_switch_alpha_symbol_key + @integer/key_delete + @string/description_delete_key + @integer/key_settings + @string/description_settings_key + @integer/key_voice + @string/description_voice_key + diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml index f63d6816c..3c0a9c1a2 100644 --- a/java/res/values/strings.xml +++ b/java/res/values/strings.xml @@ -102,6 +102,31 @@ Wait + + Delete + + Return + + Settings + + Shift + + Space + + Symbols + + Tab + + Voice Input + + Symbols on + + Symbols off + + Shift on + + Shift off + - + + + + + + + + diff --git a/java/res/xml-xlarge/kbd_phone.xml b/java/res/xml-xlarge/kbd_phone.xml index b9444ad50..9122176a9 100644 --- a/java/res/xml-xlarge/kbd_phone.xml +++ b/java/res/xml-xlarge/kbd_phone.xml @@ -129,9 +129,17 @@ the touch event on the area, "space" is intentionally not marked as a left edge key. --> - + + + + + + + + diff --git a/java/res/xml-xlarge/kbd_phone_symbols.xml b/java/res/xml-xlarge/kbd_phone_symbols.xml index 690bcde0c..055c14867 100644 --- a/java/res/xml-xlarge/kbd_phone_symbols.xml +++ b/java/res/xml-xlarge/kbd_phone_symbols.xml @@ -141,9 +141,17 @@ the touch event on the area, "space" is intentionally not marked as a left edge key. --> - + + + + + + + + diff --git a/java/res/xml-xlarge/kbd_qwerty_row4.xml b/java/res/xml-xlarge/kbd_qwerty_row4.xml index 8011064a4..f36b61fc7 100644 --- a/java/res/xml-xlarge/kbd_qwerty_row4.xml +++ b/java/res/xml-xlarge/kbd_qwerty_row4.xml @@ -27,8 +27,16 @@ > - + + + + + + + + - + + + + + + + + - + + + + + + + + diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index de5e097e3..86b4405bd 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -83,8 +83,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private static final int AUTO_MODE_SWITCH_STATE_CHORDING = 4; private int mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA; - // Indicates whether or not we have the settings key - private boolean mHasSettingsKey; + // Indicates whether or not we have the settings key in option of settings + private boolean mSettingsKeyEnabledInSettings; private static final int SETTINGS_KEY_MODE_AUTO = R.string.settings_key_mode_auto; private static final int SETTINGS_KEY_MODE_ALWAYS_SHOW = R.string.settings_key_mode_always_show; @@ -122,32 +122,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha prefs.registerOnSharedPreferenceChangeListener(sInstance); } - private void makeSymbolsKeyboardIds(final int mode) { - final Locale locale = mSubtypeSwitcher.getInputLocale(); - final Resources res = mInputMethodService.getResources(); - final int orientation = res.getConfiguration().orientation; - final int colorScheme = getColorScheme(); - final boolean hasVoiceKey = mVoiceKeyEnabled && !mVoiceButtonOnPrimary; - // Note: This comment is only applied for phone number keyboard layout. - // On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch - // between "phone keyboard" and "phone symbols keyboard". But on xlarge device, - // "@integer/key_shift" key code is used for that purpose in order to properly display - // "more" and "locked more" key labels. To achieve these behavior, we should initialize - // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard" - // respectively here for xlarge device's layout switching. - int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols; - final String xmlName = res.getResourceEntryName(xmlId); - mSymbolsId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode, - mAttribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true); - xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift; - mSymbolsShiftedId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode, - mAttribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true); - } - - private boolean hasVoiceKey(boolean isSymbols) { - return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary); - } - public void loadKeyboard(EditorInfo attribute, boolean voiceKeyEnabled, boolean voiceButtonOnPrimary) { mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA; @@ -170,14 +144,14 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha mVoiceButtonOnPrimary = voiceButtonOnPrimary; mIsSymbols = isSymbols; // Update the settings key state because number of enabled IMEs could have been changed - mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService); + mSettingsKeyEnabledInSettings = getSettingsKeyMode(mPrefs, mInputMethodService); final KeyboardId id = getKeyboardId(attribute, isSymbols); final Keyboard oldKeyboard = mInputView.getKeyboard(); if (oldKeyboard != null && oldKeyboard.mId.equals(id)) return; - makeSymbolsKeyboardIds(id.mMode); + makeSymbolsKeyboardIds(id.mMode, attribute); mCurrentId = id; mInputView.setPreviewEnabled(mInputMethodService.getPopupOn()); setKeyboard(getKeyboard(id)); @@ -224,6 +198,16 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return keyboard; } + private boolean hasVoiceKey(boolean isSymbols) { + return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary); + } + + private boolean hasSettingsKey(EditorInfo attribute) { + return mSettingsKeyEnabledInSettings + && !Utils.inPrivateImeOptions(mInputMethodService.getPackageName(), + LatinIME.IME_OPTION_NO_SETTINGS_KEY, attribute); + } + private KeyboardId getKeyboardId(EditorInfo attribute, boolean isSymbols) { final int mode = Utils.getKeyboardMode(attribute); final boolean hasVoiceKey = hasVoiceKey(isSymbols); @@ -253,12 +237,36 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha enableShiftLock = true; } } + final boolean hasSettingsKey = hasSettingsKey(attribute); final Resources res = mInputMethodService.getResources(); final int orientation = res.getConfiguration().orientation; final Locale locale = mSubtypeSwitcher.getInputLocale(); return new KeyboardId( res.getResourceEntryName(xmlId), xmlId, charColorId, locale, orientation, mode, - attribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, enableShiftLock); + attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, enableShiftLock); + } + + private void makeSymbolsKeyboardIds(final int mode, EditorInfo attribute) { + final Locale locale = mSubtypeSwitcher.getInputLocale(); + final Resources res = mInputMethodService.getResources(); + final int orientation = res.getConfiguration().orientation; + final int colorScheme = getColorScheme(); + final boolean hasVoiceKey = mVoiceKeyEnabled && !mVoiceButtonOnPrimary; + final boolean hasSettingsKey = hasSettingsKey(attribute); + // Note: This comment is only applied for phone number keyboard layout. + // On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch + // between "phone keyboard" and "phone symbols keyboard". But on xlarge device, + // "@integer/key_shift" key code is used for that purpose in order to properly display + // "more" and "locked more" key labels. To achieve these behavior, we should initialize + // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard" + // respectively here for xlarge device's layout switching. + int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols; + final String xmlName = res.getResourceEntryName(xmlId); + mSymbolsId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode, + attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true); + xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift; + mSymbolsShiftedId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode, + attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true); } public int getKeyboardMode() { @@ -689,7 +697,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha createInputViewInternal(layoutId, false); postSetInputView(); } else if (Settings.PREF_SETTINGS_KEY.equals(key)) { - mHasSettingsKey = getSettingsKeyMode(sharedPreferences, mInputMethodService); + mSettingsKeyEnabledInSettings = getSettingsKeyMode(sharedPreferences, + mInputMethodService); createInputViewInternal(mLayoutId, true); postSetInputView(); } @@ -725,7 +734,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha Context.INPUT_METHOD_SERVICE))))) { return true; } + return false; } - return false; + // If the show settings key option is disabled, we always try showing the settings key. + return true; } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 40cebb395..786e766d6 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -100,7 +100,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * shown for a given text field. For instance, this is specified by the * search dialog when the dialog is already showing a voice search button. */ - public static final String IME_OPTION_NO_MICROPHONE = "noMicrophone"; + public static final String IME_OPTION_NO_MICROPHONE = "noMicrophoneKey"; + + /** + * The private IME option used to indicate that no settings key should be + * shown for a given text field. + */ + public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey"; private static final int DELAY_UPDATE_SUGGESTIONS = 180; private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300; -- cgit v1.2.3-83-g751a From eaef1c500703b4ee378821884c7b108815ed2983 Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Fri, 25 Feb 2011 18:21:02 +0900 Subject: Change how to load native lib to be on the safe side Change-Id: If170d4821b8dbc187f29137ea99d10e30c20955f --- java/src/com/android/inputmethod/keyboard/ProximityInfo.java | 5 +++++ java/src/com/android/inputmethod/latin/BinaryDictionary.java | 3 +++ java/src/com/android/inputmethod/latin/LatinIME.java | 8 -------- java/src/com/android/inputmethod/latin/Utils.java | 8 ++++++++ 4 files changed, 16 insertions(+), 8 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index fe836c1e2..fcada3762 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -16,6 +16,8 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.latin.Utils; + public class ProximityInfo { public static final int MAX_PROXIMITY_CHARS_SIZE = 16; @@ -34,6 +36,9 @@ public class ProximityInfo { } private int mNativeProximityInfo; + static { + Utils.loadNativeLibrary(); + } private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth, int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray); private native void releaseProximityInfoNative(int nativeProximityInfo); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 33b8c7af6..55cd0848a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -109,6 +109,9 @@ public class BinaryDictionary extends Dictionary { return sInstance; } + static { + Utils.loadNativeLibrary(); + } private native int openNative(String sourceDir, long dictOffset, long dictSize, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 97f6b8d83..91fd5b01e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -211,14 +211,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private final ArrayList mWordHistory = new ArrayList(); - static { - try { - System.loadLibrary("jni_latinime"); - } catch (UnsatisfiedLinkError ule) { - Log.e(TAG, "Could not load native library jni_latinime"); - } - } - public abstract static class WordAlternatives { protected CharSequence mChosenWord; diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index f26766768..727e3f16d 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -553,4 +553,12 @@ public class Utils { public static int getMainDictionaryResourceId(Resources res) { return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName()); } + + public static void loadNativeLibrary() { + try { + System.loadLibrary("jni_latinime"); + } catch (UnsatisfiedLinkError ule) { + Log.e(TAG, "Could not load native library jni_latinime"); + } + } } -- cgit v1.2.3-83-g751a From 6f4eba814a7f8426617db61f928a965209ebf359 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Mon, 28 Feb 2011 14:54:04 -0800 Subject: Add test entry for BinaryDictionary class Bug: 3414081 Change-Id: I1a3d60698795bf28c477086838e726d498fb6de0 --- .../inputmethod/latin/BinaryDictionary.java | 46 +++++++++++++--------- 1 file changed, 27 insertions(+), 19 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 55cd0848a..e207c33bd 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.ProximityInfo; @@ -93,8 +94,7 @@ public class BinaryDictionary extends Dictionary { return sInstance; } - // For unit test - /* package */ static BinaryDictionary initDictionary(File dictionary, long startOffset, + /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset, long length, int dicTypeId) { synchronized (sInstance) { sInstance.closeInternal(); @@ -166,11 +166,32 @@ public class BinaryDictionary extends Dictionary { @Override public void getWords(final WordComposer codes, final WordCallback callback) { - if (mNativeDict == 0) return; + final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard()); + + for (int j = 0; j < count; ++j) { + if (mFrequencies[j] < 1) break; + final int start = j * MAX_WORD_LENGTH; + int len = 0; + while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { + ++len; + } + if (len > 0) { + callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId, + DataType.UNIGRAM); + } + } + } + + /* package for test */ boolean isValidDictionary() { + return mNativeDict != 0; + } + + /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard) { + if (!isValidDictionary()) return -1; final int codesSize = codes.size(); // Won't deal with really long words. - if (codesSize > MAX_WORD_LENGTH - 1) return; + if (codesSize > MAX_WORD_LENGTH - 1) return -1; Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE); for (int i = 0; i < codesSize; i++) { @@ -181,23 +202,10 @@ public class BinaryDictionary extends Dictionary { Arrays.fill(mOutputChars, (char) 0); Arrays.fill(mFrequencies, 0); - int count = getSuggestionsNative( - mNativeDict, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo(), + return getSuggestionsNative( + mNativeDict, keyboard.getProximityInfo(), codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, mOutputChars, mFrequencies); - - for (int j = 0; j < count; ++j) { - if (mFrequencies[j] < 1) break; - final int start = j * MAX_WORD_LENGTH; - int len = 0; - while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { - ++len; - } - if (len > 0) { - callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId, - DataType.UNIGRAM); - } - } } @Override -- cgit v1.2.3-83-g751a From 2e04770adfc16344f69d316efd3ed0a617ede330 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Mon, 28 Feb 2011 16:24:41 -0800 Subject: Add output variables to BinaryDictionary's test entry Bug: 3414081 Change-Id: I1d11710476ffadcef67b0dbfbde309e7f8344d8a --- .../com/android/inputmethod/latin/BinaryDictionary.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index e207c33bd..d866ec148 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -39,10 +39,10 @@ public class BinaryDictionary extends Dictionary { * really long words. */ public static final int MAX_WORD_LENGTH = 48; + public static final int MAX_WORDS = 18; private static final String TAG = "BinaryDictionary"; private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; - private static final int MAX_WORDS = 18; private static final int MAX_BIGRAMS = 60; private static final int TYPED_LETTER_MULTIPLIER = 2; @@ -166,7 +166,8 @@ public class BinaryDictionary extends Dictionary { @Override public void getWords(final WordComposer codes, final WordCallback callback) { - final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard()); + final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(), + mOutputChars, mFrequencies); for (int j = 0; j < count; ++j) { if (mFrequencies[j] < 1) break; @@ -186,7 +187,8 @@ public class BinaryDictionary extends Dictionary { return mNativeDict != 0; } - /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard) { + /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard, + char[] outputChars, int[] frequencies) { if (!isValidDictionary()) return -1; final int codesSize = codes.size(); @@ -199,13 +201,13 @@ public class BinaryDictionary extends Dictionary { System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE, Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); } - Arrays.fill(mOutputChars, (char) 0); - Arrays.fill(mFrequencies, 0); + Arrays.fill(outputChars, (char) 0); + Arrays.fill(frequencies, 0); return getSuggestionsNative( mNativeDict, keyboard.getProximityInfo(), codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, - mOutputChars, mFrequencies); + outputChars, frequencies); } @Override -- cgit v1.2.3-83-g751a From 952cb7e973bbc4c09728cb5967505f84c80c8bd6 Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Tue, 1 Mar 2011 15:59:06 +0900 Subject: Refactor recorrection variable and method names Change-Id: I3cb76e622e33985a187afe0a8928ed7b261615fd --- .../com/android/inputmethod/latin/LatinIME.java | 54 +++++++++++----------- .../android/inputmethod/latin/TextEntryState.java | 24 +++++----- 2 files changed, 39 insertions(+), 39 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 91fd5b01e..3ff072ef2 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -167,7 +167,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private boolean mHasDictionary; private boolean mJustAddedAutoSpace; private boolean mAutoCorrectEnabled; - private boolean mReCorrectionEnabled; + private boolean mRecorrectionEnabled; private boolean mBigramSuggestionEnabled; private boolean mAutoCorrectOn; private boolean mVibrateOn; @@ -381,10 +381,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // If the option should not be shown, do not read the recorrection preference // but always use the default setting defined in the resources. if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) { - mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED, + mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED, res.getBoolean(R.bool.config_default_recorrection_enabled)); } else { - mReCorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled); + mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled); } mConfigEnableShowSubtypeSettings = res.getBoolean( @@ -562,7 +562,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen inputView.setProximityCorrectionEnabled(true); inputView.setAccessibilityEnabled(accessibilityEnabled); // If we just entered a text field, maybe it has some old text that requires correction - checkReCorrectionOnStart(); + checkRecorrectionOnStart(); inputView.setForeground(true); mVoiceConnector.onStartInputView(inputView.getWindowToken()); @@ -625,8 +625,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void checkReCorrectionOnStart() { - if (!mReCorrectionEnabled) return; + private void checkRecorrectionOnStart() { + if (!mRecorrectionEnabled) return; final InputConnection ic = getCurrentInputConnection(); if (ic == null) return; @@ -735,18 +735,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mLastSelectionStart = newSelStart; mLastSelectionEnd = newSelEnd; - if (mReCorrectionEnabled && isShowingSuggestionsStrip()) { + if (mRecorrectionEnabled && isShowingSuggestionsStrip()) { // Don't look for corrections if the keyboard is not visible if (mKeyboardSwitcher.isInputViewShown()) { // Check if we should go in or out of correction mode. if (isSuggestionsRequested() && !mJustReverted && (candidatesStart == candidatesEnd || newSelStart != oldSelStart - || TextEntryState.isCorrecting()) + || TextEntryState.isRecorrecting()) && (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) { if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) { mHandler.postUpdateOldSuggestions(); } else { - abortCorrection(false); + abortRecorrection(false); // Show the punctuation suggestions list if the current one is not // and if not showing "Touch again to save". if (mCandidateView != null && !isShowingPunctuationList() @@ -769,7 +769,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen */ @Override public void onExtractedTextClicked() { - if (mReCorrectionEnabled && isSuggestionsRequested()) return; + if (mRecorrectionEnabled && isSuggestionsRequested()) return; super.onExtractedTextClicked(); } @@ -785,7 +785,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen */ @Override public void onExtractedCursorMovement(int dx, int dy) { - if (mReCorrectionEnabled && isSuggestionsRequested()) return; + if (mRecorrectionEnabled && isSuggestionsRequested()) return; super.onExtractedCursorMovement(dx, dy); } @@ -1154,7 +1154,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mVoiceConnector.commitVoiceInput(); InputConnection ic = getCurrentInputConnection(); if (ic == null) return; - abortCorrection(false); + abortRecorrection(false); ic.beginBatchEdit(); commitTyped(ic); maybeRemovePreviousPeriod(text); @@ -1255,9 +1255,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void abortCorrection(boolean force) { - if (force || TextEntryState.isCorrecting()) { - TextEntryState.onAbortCorrection(); + private void abortRecorrection(boolean force) { + if (force || TextEntryState.isRecorrecting()) { + TextEntryState.onAbortRecorrection(); setCandidatesViewShown(isCandidateStripVisible()); getCurrentInputConnection().finishComposingText(); clearSuggestions(); @@ -1267,8 +1267,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) { mVoiceConnector.handleCharacter(); - if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) { - abortCorrection(false); + if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) { + abortRecorrection(false); } int code = primaryCode; @@ -1336,7 +1336,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final InputConnection ic = getCurrentInputConnection(); if (ic != null) { ic.beginBatchEdit(); - abortCorrection(false); + abortRecorrection(false); } if (mHasValidSuggestions) { // In certain languages where single quote is a separator, it's better @@ -1439,7 +1439,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private boolean isCandidateStripVisible() { if (mCandidateView == null) return false; - if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isCorrecting()) + if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting()) return true; if (!isShowingSuggestionsStrip()) return false; @@ -1528,7 +1528,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // Don't auto-correct words with multiple capital letter correctionAvailable &= !word.isMostlyCaps(); - correctionAvailable &= !TextEntryState.isCorrecting(); + correctionAvailable &= !TextEntryState.isRecorrecting(); // Basically, we update the suggestion strip only when suggestion count > 1. However, // there is an exception: We update the suggestion strip whenever typed word's length @@ -1586,7 +1586,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen SuggestedWords suggestions = mCandidateView.getSuggestions(); mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators); - final boolean correcting = TextEntryState.isCorrecting(); + final boolean recorrecting = TextEntryState.isRecorrecting(); InputConnection ic = getCurrentInputConnection(); if (ic != null) { ic.beginBatchEdit(); @@ -1636,7 +1636,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen index, suggestions.mWords); TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion); // Follow it with a space - if (mAutoSpace && !correcting) { + if (mAutoSpace && !recorrecting) { sendSpace(); mJustAddedAutoSpace = true; } @@ -1657,7 +1657,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen && !mSuggest.isValidWord(suggestion) && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))); - if (!correcting) { + if (!recorrecting) { // Fool the state watcher so that a subsequent backspace will not do a revert, unless // we just did a correction, in which case we need to stay in // TextEntryState.State.PICKED_SUGGESTION state. @@ -1761,19 +1761,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!mVoiceConnector.applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) { - abortCorrection(true); + abortRecorrection(true); } else { - TextEntryState.selectedForCorrection(); + TextEntryState.selectedForRecorrection(); EditingUtils.underlineWord(ic, touching); } ic.endBatchEdit(); } else { - abortCorrection(true); + abortRecorrection(true); setPunctuationSuggestions(); // Show the punctuation suggestions list } } else { - abortCorrection(true); + abortRecorrection(true); } } diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java index f571f26d5..c8b8c334d 100644 --- a/java/src/com/android/inputmethod/latin/TextEntryState.java +++ b/java/src/com/android/inputmethod/latin/TextEntryState.java @@ -61,8 +61,8 @@ public class TextEntryState { SPACE_AFTER_ACCEPTED, SPACE_AFTER_PICKED, UNDO_COMMIT, - CORRECTING, - PICKED_CORRECTION, + RECORRECTING, + PICKED_RECORRECTION, } private static State sState = State.UNKNOWN; @@ -158,21 +158,21 @@ public class TextEntryState { if (typedWord.equals(actualWord)) { acceptedTyped(typedWord); } - if (oldState == State.CORRECTING || oldState == State.PICKED_CORRECTION) { - sState = State.PICKED_CORRECTION; + if (oldState == State.RECORRECTING || oldState == State.PICKED_RECORRECTION) { + sState = State.PICKED_RECORRECTION; } else { sState = State.PICKED_SUGGESTION; } displayState(); } - public static void selectedForCorrection() { - sState = State.CORRECTING; + public static void selectedForRecorrection() { + sState = State.RECORRECTING; displayState(); } - public static void onAbortCorrection() { - if (isCorrecting()) { + public static void onAbortRecorrection() { + if (isRecorrecting()) { sState = State.START; } displayState(); @@ -199,7 +199,7 @@ public class TextEntryState { } break; case PICKED_SUGGESTION: - case PICKED_CORRECTION: + case PICKED_RECORRECTION: if (isSpace) { sState = State.SPACE_AFTER_PICKED; } else if (isSeparator) { @@ -227,7 +227,7 @@ public class TextEntryState { sState = State.IN_WORD; } break; - case CORRECTING: + case RECORRECTING: sState = State.START; break; } @@ -258,8 +258,8 @@ public class TextEntryState { return sState; } - public static boolean isCorrecting() { - return sState == State.CORRECTING || sState == State.PICKED_CORRECTION; + public static boolean isRecorrecting() { + return sState == State.RECORRECTING || sState == State.PICKED_RECORRECTION; } public static void keyPressedAt(Key key, int x, int y) { -- cgit v1.2.3-83-g751a From 025a5e356ec74dbed7f805a2e43adbb2a35320f8 Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Fri, 25 Feb 2011 18:11:18 +0900 Subject: Reset the suggestion status when composing span gets cleared bug: 3353818 Change-Id: I87b80470015dbeb2eaaec9aa4d9d802597c18e19 --- java/src/com/android/inputmethod/latin/LatinIME.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 3ff072ef2..3a97846fa 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -694,6 +694,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (DEBUG) { Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd + + ", lss=" + mLastSelectionStart + + ", lse=" + mLastSelectionEnd + ", nss=" + newSelStart + ", nse=" + newSelEnd + ", cs=" + candidatesStart @@ -704,9 +706,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // If the current selection in the text view changes, we should // clear whatever candidate text we have. - if ((((mComposing.length() > 0 && mHasValidSuggestions) - || mVoiceConnector.isVoiceInputHighlighted()) && (newSelStart != candidatesEnd - || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) { + final boolean selectionChanged = (newSelStart != candidatesEnd + || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart; + final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1; + if (((mComposing.length() > 0 && mHasValidSuggestions) + || mVoiceConnector.isVoiceInputHighlighted()) + && (selectionChanged || candidatesCleared)) { + if (candidatesCleared) { + // If the composing span has been cleared, save the typed word in the history for + // recorrection before we reset the candidate strip. Then, we'll be able to show + // suggestions for recorrection right away. + saveWordInHistory(mComposing); + } mComposing.setLength(0); mHasValidSuggestions = false; mHandler.postUpdateSuggestions(); -- cgit v1.2.3-83-g751a From 18e15a1e5e30cf200e935e43a73edef25682de27 Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Wed, 2 Mar 2011 07:20:27 +0900 Subject: Fix null token error bug: 3499746 Change-Id: I76bd745b0623f4736c6b0aaf0ab42fd4b7c4f349 --- java/src/com/android/inputmethod/latin/LatinIME.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 3a97846fa..ec491b7de 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -39,6 +39,7 @@ import android.media.AudioManager; import android.net.ConnectivityManager; import android.os.Debug; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.SystemClock; import android.os.Vibrator; @@ -2263,6 +2264,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void showOptionsMenuInternal(CharSequence title, CharSequence[] items, DialogInterface.OnClickListener listener) { + final IBinder windowToken = mKeyboardSwitcher.getInputView().getWindowToken(); + if (windowToken == null) return; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(true); builder.setIcon(R.drawable.ic_dialog_keyboard); @@ -2273,7 +2276,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mOptionsDialog.setCanceledOnTouchOutside(true); Window window = mOptionsDialog.getWindow(); WindowManager.LayoutParams lp = window.getAttributes(); - lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); + lp.token = windowToken; lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; window.setAttributes(lp); window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); -- cgit v1.2.3-83-g751a From 7e99a28dfcc14f5b19220442db972ca8d786b4fe Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Wed, 2 Mar 2011 15:56:17 +0900 Subject: Clear suggestion strip when the suggestion starts bug: 3488704 bug: 3480132 Change-Id: Ie895d2ad1b58dc65fe16d65baa6fccafd4a142bb --- java/src/com/android/inputmethod/latin/LatinIME.java | 1 + java/src/com/android/inputmethod/latin/Suggest.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index ec491b7de..c4e5ce39f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1290,6 +1290,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mComposing.setLength(0); saveWordInHistory(mBestWord); mWord.reset(); + clearSuggestions(); } } KeyboardSwitcher switcher = mKeyboardSwitcher; diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 6466f7980..ee6930d93 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -297,7 +297,7 @@ public class Suggest implements Dictionary.WordCallback { } if (normalizedScore >= mAutoCorrectionThreshold) { if (DBG) { - Log.d(TAG, "Auto corrected by S-threthhold."); + Log.d(TAG, "Auto corrected by S-threshold."); } mHasAutoCorrection = true; } -- cgit v1.2.3-83-g751a From fc20f2198d6703d65eab22f40f9b8aa2c8e3a0ea Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Tue, 1 Mar 2011 23:40:10 -0800 Subject: Introduce timeout for "." shortcut Bug: 3294239 Change-Id: I1832be8b54cd4d55cd504a3e1e0434aa284f9592 --- java/res/values/config.xml | 1 + .../com/android/inputmethod/latin/LatinIME.java | 25 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) (limited to 'java/src') diff --git a/java/res/values/config.xml b/java/res/values/config.xml index bf42589ea..bdb4409f0 100644 --- a/java/res/values/config.xml +++ b/java/res/values/config.xml @@ -60,6 +60,7 @@ 400 1200 40 + 1100 2.0mm 4 diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index ec491b7de..e90883f4e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -181,6 +181,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private int mConfigDelayBeforeFadeoutLanguageOnSpacebar; private int mConfigDurationOfFadeoutLanguageOnSpacebar; private float mConfigFinalFadeoutFactorOfLanguageOnSpacebar; + private long mConfigDoubleSpacesTurnIntoPeriodTimeout; private int mCorrectionMode; private int mCommittedLength; @@ -269,6 +270,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final int MSG_VOICE_RESULTS = 3; private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4; private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5; + private static final int MSG_SPACE_TYPED = 6; @Override public void handleMessage(Message msg) { @@ -357,6 +359,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } } + + public void startDoubleSpacesTimer() { + removeMessages(MSG_SPACE_TYPED); + sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED), + mConfigDoubleSpacesTurnIntoPeriodTimeout); + } + + public void cancelDoubleSpacesTimer() { + removeMessages(MSG_SPACE_TYPED); + } + + public boolean isAcceptingDoubleSpaces() { + return hasMessages(MSG_SPACE_TYPED); + } } @Override @@ -398,6 +414,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen R.integer.config_duration_of_fadeout_language_on_spacebar); mConfigFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger( R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f; + mConfigDoubleSpacesTurnIntoPeriodTimeout = res.getInteger( + R.integer.config_double_spaces_turn_into_period_timeout); Utils.GCUtils.getInstance().reset(); boolean tryGC = true; @@ -1004,7 +1022,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void doubleSpace() { - //if (!mAutoPunctuate) return; if (mCorrectionMode == Suggest.CORRECTION_NONE) return; final InputConnection ic = getCurrentInputConnection(); if (ic == null) return; @@ -1012,13 +1029,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (lastThree != null && lastThree.length() == 3 && Character.isLetterOrDigit(lastThree.charAt(0)) && lastThree.charAt(1) == Keyboard.CODE_SPACE - && lastThree.charAt(2) == Keyboard.CODE_SPACE) { + && lastThree.charAt(2) == Keyboard.CODE_SPACE + && mHandler.isAcceptingDoubleSpaces()) { + mHandler.cancelDoubleSpacesTimer(); ic.beginBatchEdit(); ic.deleteSurroundingText(2, 0); ic.commitText(". ", 1); ic.endBatchEdit(); mKeyboardSwitcher.updateShiftState(); mJustAddedAutoSpace = true; + } else { + mHandler.startDoubleSpacesTimer(); } } -- cgit v1.2.3-83-g751a From 6f99d12b0752ecdbceda7e5fddead214f939f533 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 2 Mar 2011 10:48:45 -0800 Subject: Enable re-correction even after revert auto-correction This change removes the boolean flag mJustReverted. Because we preserve separator after canceling auto-correction according to bug#3230708, there is no need for this flag now. Bug: 3193810 Change-Id: Icea06ece569774b494ffa4ca4a899c898661cc19 --- java/src/com/android/inputmethod/latin/LatinIME.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index e2888e8e4..f1b96ad5d 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -193,7 +193,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Indicates whether the suggestion strip is to be on in landscape private boolean mJustAccepted; - private boolean mJustReverted; private int mDeleteCount; private long mLastKeyTime; @@ -769,7 +768,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Don't look for corrections if the keyboard is not visible if (mKeyboardSwitcher.isInputViewShown()) { // Check if we should go in or out of correction mode. - if (isSuggestionsRequested() && !mJustReverted + if (isSuggestionsRequested() && (candidatesStart == candidatesEnd || newSelStart != oldSelStart || TextEntryState.isRecorrecting()) && (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) { @@ -1174,8 +1173,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { handleCharacter(primaryCode, keyCodes, x, y); } - // Cancel the just reverted state - mJustReverted = false; } switcher.onKey(primaryCode); // Reset after any single keystroke @@ -1195,7 +1192,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen ic.endBatchEdit(); mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY); - mJustReverted = false; mJustAddedAutoSpace = false; mEnteredText = text; } @@ -1260,7 +1256,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } } - mJustReverted = false; ic.endBatchEdit(); } @@ -1377,7 +1372,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // not to auto correct, but accept the typed word. For instance, // in Italian dov' should not be expanded to dove' because the elision // requires the last vowel to be removed. - if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) { + if (mAutoCorrectOn && primaryCode != '\'') { pickedDefault = pickDefaultSuggestion(); // Picked the suggestion by the space key. We consider this // as "added an auto space". @@ -1549,8 +1544,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( mKeyboardSwitcher.getInputView(), word, prevWord); - boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted - && mSuggest.hasAutoCorrection(); + boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection(); final CharSequence typedWord = word.getTypedWord(); // If we're in basic correct final boolean typedWordValid = mSuggest.isValidWord(typedWord) || @@ -1880,7 +1874,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int length = mComposing.length(); if (!mHasValidSuggestions && length > 0) { final InputConnection ic = getCurrentInputConnection(); - mJustReverted = true; final CharSequence punctuation = ic.getTextBeforeCursor(1, 0); if (deleteChar) ic.deleteSurroundingText(1, 0); int toDelete = mCommittedLength; @@ -1908,7 +1901,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.postUpdateSuggestions(); } else { sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); - mJustReverted = false; } } -- cgit v1.2.3-83-g751a From e59491460b0411bed430a5ca6eca0c56c5bf18d9 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 2 Mar 2011 11:43:13 -0800 Subject: Remain in previous state while sliding off from shift key Bug: 3502290 Change-Id: I69b60051863c78727363ae63576c28d87fb71a3a --- .../keyboard/KeyboardActionListener.java | 8 ++++-- .../inputmethod/keyboard/KeyboardSwitcher.java | 16 +++++++----- .../android/inputmethod/keyboard/KeyboardView.java | 8 +++--- .../inputmethod/keyboard/PointerTracker.java | 30 +++++++++++----------- .../com/android/inputmethod/latin/LatinIME.java | 8 +++--- 5 files changed, 38 insertions(+), 32 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java index 734a55a79..098af214e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -24,16 +24,20 @@ public interface KeyboardActionListener { * * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key, * the value will be zero. + * @param withSliding true if pressing has occurred because the user slid finger from other key + * to this key without releasing the finger. */ - public void onPress(int primaryCode); + public void onPress(int primaryCode, boolean withSliding); /** * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called. * For keys that repeat, this is only called once. * * @param primaryCode the code of the key that was released + * @param withSliding true if releasing has occurred because the user slid finger from the key + * to other key without releasing the finger. */ - public void onRelease(int primaryCode); + public void onRelease(int primaryCode, boolean withSliding); /** * Send a key code to the listener. diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 6651f9ffa..3d34e71a0 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -440,7 +440,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha updateShiftState(); } - public void onPressShift() { + public void onPressShift(boolean withSliding) { if (!isKeyboardAvailable()) return; // If accessibility is enabled, disable momentary shift lock. @@ -450,7 +450,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha if (DEBUG_STATE) Log.d(TAG, "onPressShift:" + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() - + " shiftKeyState=" + shiftKeyState); + + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding); if (isAlphabetMode()) { if (isShiftLocked()) { // Shift key is pressed while caps lock state, we will treat this state as shifted @@ -478,7 +478,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } } - public void onReleaseShift() { + public void onReleaseShift(boolean withSliding) { if (!isKeyboardAvailable()) return; // If accessibility is enabled, disable momentary shift lock. @@ -488,18 +488,20 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha if (DEBUG_STATE) Log.d(TAG, "onReleaseShift:" + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() - + " shiftKeyState=" + shiftKeyState); + + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding); if (isAlphabetMode()) { if (shiftKeyState.isMomentary()) { // After chording input while normal state. toggleShift(); - } else if (isShiftLocked() && !shiftKeyState.isIgnoring()) { + } else if (isShiftLocked() && !shiftKeyState.isIgnoring() && !withSliding) { // Shift has been pressed without chording while caps lock state. toggleCapsLock(); - } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()) { + } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted() + && !withSliding) { // Shift has been pressed without chording while shifted state. toggleShift(); - } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing()) { + } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing() + && !withSliding) { // Shift has been pressed without chording while manual temporary upper case // transited from automatic temporary upper case. toggleShift(); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 851430fb8..61af15b1d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -1094,12 +1094,12 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { // Nothing to do. } @Override - public void onPress(int primaryCode) { - mKeyboardActionListener.onPress(primaryCode); + public void onPress(int primaryCode, boolean withSliding) { + mKeyboardActionListener.onPress(primaryCode, withSliding); } @Override - public void onRelease(int primaryCode) { - mKeyboardActionListener.onRelease(primaryCode); + public void onRelease(int primaryCode, boolean withSliding) { + mKeyboardActionListener.onRelease(primaryCode, withSliding); } }); // Override default ProximityKeyDetector. diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 8e9d02247..746857819 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -93,9 +93,9 @@ public class PointerTracker { // Empty {@link KeyboardActionListener} private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() { @Override - public void onPress(int primaryCode) {} + public void onPress(int primaryCode, boolean withSliding) {} @Override - public void onRelease(int primaryCode) {} + public void onRelease(int primaryCode, boolean withSliding) {} @Override public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {} @Override @@ -138,11 +138,11 @@ public class PointerTracker { } // Returns true if keyboard has been changed by this callback. - private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { + private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) { if (DEBUG_LISTENER) - Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode)); + Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding); if (key.mEnabled) { - mListener.onPress(key.mCode); + mListener.onPress(key.mCode, withSliding); final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; mKeyboardLayoutHasBeenChanged = false; return keyboardLayoutHasBeenChanged; @@ -169,11 +169,11 @@ public class PointerTracker { // Note that we need primaryCode argument because the keyboard may in shifted state and the // primaryCode is different from {@link Key#mCode}. - private void callListenerOnRelease(Key key, int primaryCode) { + private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) { if (DEBUG_LISTENER) - Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode)); + Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode) + " sliding=" + withSliding); if (key.mEnabled) - mListener.onRelease(primaryCode); + mListener.onRelease(primaryCode, withSliding); } private void callListenerOnCancelInput() { @@ -333,7 +333,7 @@ public class PointerTracker { // This onPress call may have changed keyboard layout. Those cases are detected at // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new // keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex])) + if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex], false)) keyIndex = mKeyState.onDownKey(x, y, eventTime); } if (isValidKeyIndex(keyIndex)) { @@ -368,7 +368,7 @@ public class PointerTracker { // This onPress call may have changed keyboard layout. Those cases are detected at // {@link #setKeyboard}. In those cases, we should update keyIndex according to the // new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex))) + if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true)) keyIndex = keyState.onMoveKey(x, y); keyState.onMoveToNewKey(keyIndex, x, y); startLongPressTimer(keyIndex); @@ -377,13 +377,13 @@ public class PointerTracker { // onRelease() first to notify that the previous key has been released, then call // onPress() to notify that the new key is being pressed. mIsInSlidingKeyInput = true; - callListenerOnRelease(oldKey, oldKey.mCode); + callListenerOnRelease(oldKey, oldKey.mCode, true); mHandler.cancelLongPressTimers(); if (mIsAllowedSlidingKeyInput) { // This onPress call may have changed keyboard layout. Those cases are detected // at {@link #setKeyboard}. In those cases, we should update keyIndex according // to the new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex))) + if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true)) keyIndex = keyState.onMoveKey(x, y); keyState.onMoveToNewKey(keyIndex, x, y); startLongPressTimer(keyIndex); @@ -412,7 +412,7 @@ public class PointerTracker { // The pointer has been slid out from the previous key, we must call onRelease() to // notify that the previous key has been released. mIsInSlidingKeyInput = true; - callListenerOnRelease(oldKey, oldKey.mCode); + callListenerOnRelease(oldKey, oldKey.mCode, true); mHandler.cancelLongPressTimers(); if (mIsAllowedSlidingKeyInput) { keyState.onMoveToNewKey(keyIndex, x ,y); @@ -568,7 +568,7 @@ public class PointerTracker { } if (key.mOutputText != null) { callListenerOnTextInput(key); - callListenerOnRelease(key, key.mCode); + callListenerOnRelease(key, key.mCode, false); } else { int code = key.mCode; final int[] codes = mKeyDetector.newCodeArray(); @@ -590,7 +590,7 @@ public class PointerTracker { codes[0] = code; } callListenerOnCodeInput(key, code, codes, x, y); - callListenerOnRelease(key, code); + callListenerOnRelease(key, code, false); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index f1b96ad5d..838249a39 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1951,7 +1951,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } @Override - public void onPress(int primaryCode) { + public void onPress(int primaryCode, boolean withSliding) { if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) { vibrate(); playKeyClick(primaryCode); @@ -1959,7 +1959,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen KeyboardSwitcher switcher = mKeyboardSwitcher; final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { - switcher.onPressShift(); + switcher.onPressShift(withSliding); } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { switcher.onPressSymbol(); } else { @@ -1969,13 +1969,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } @Override - public void onRelease(int primaryCode) { + public void onRelease(int primaryCode, boolean withSliding) { KeyboardSwitcher switcher = mKeyboardSwitcher; // Reset any drag flags in the keyboard switcher.keyReleased(); final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) { - switcher.onReleaseShift(); + switcher.onReleaseShift(withSliding); } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { switcher.onReleaseSymbol(); } -- cgit v1.2.3-83-g751a From c2bbc6a4499a6da979381fa0e8e6e855a5ac6aa4 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 25 Feb 2011 17:56:53 +0900 Subject: Use translation of fallback umlauts digraphs for German. For German : handle "ae", "oe" and "ue" to be alternate forms for umlaut-bearing versions of "a", "o" and "u". Issue: 3275926 Change-Id: I056c707cdacc464ceab63be56c016c7f8439196c --- java/res/xml/method.xml | 1 + .../inputmethod/latin/BinaryDictionary.java | 34 ++++- .../android/inputmethod/latin/SubtypeSwitcher.java | 54 ++++++-- ..._android_inputmethod_latin_BinaryDictionary.cpp | 7 +- native/src/debug.h | 11 ++ native/src/dictionary.h | 4 +- native/src/unigram_dictionary.cpp | 154 +++++++++++++++++---- native/src/unigram_dictionary.h | 30 +++- 8 files changed, 239 insertions(+), 56 deletions(-) (limited to 'java/src') diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml index b1f737903..8dec7abec 100644 --- a/java/res/xml/method.xml +++ b/java/res/xml/method.xml @@ -65,6 +65,7 @@ android:label="@string/subtype_mode_de_keyboard" android:imeSubtypeLocale="de" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="requiresGermanUmlautProcessing" /> mAllEnabledSubtypesOfCurrentInputMethod; + private InputMethodSubtype mCurrentSubtype; private Locale mSystemLocale; private Locale mInputLocale; private String mInputLocaleStr; - private String mMode; private VoiceInput mVoiceInput; /*-----------------------------------------------------------*/ @@ -110,8 +110,7 @@ public class SubtypeSwitcher { mSystemLocale = null; mInputLocale = null; mInputLocaleStr = null; - // Mode is initialized to KEYBOARD_MODE, in case that LatinIME can't obtain currentSubtype - mMode = KEYBOARD_MODE; + mCurrentSubtype = null; mAllEnabledSubtypesOfCurrentInputMethod = null; // TODO: Voice input should be created here mVoiceInput = null; @@ -145,6 +144,7 @@ public class SubtypeSwitcher { // Reload enabledSubtypes from the framework. private void updateEnabledSubtypes() { + final String currentMode = getCurrentSubtypeMode(); boolean foundCurrentSubtypeBecameDisabled = true; mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList( null, true); @@ -157,7 +157,7 @@ public class SubtypeSwitcher { if (mLocaleSplitter.hasNext()) { mEnabledLanguagesOfCurrentInputMethod.add(mLocaleSplitter.next()); } - if (locale.equals(mInputLocaleStr) && mode.equals(mMode)) { + if (locale.equals(mInputLocaleStr) && mode.equals(currentMode)) { foundCurrentSubtypeBecameDisabled = false; } if (KEYBOARD_MODE.equals(ims.getMode())) { @@ -168,7 +168,7 @@ public class SubtypeSwitcher { && mIsSystemLanguageSameAsInputLanguage); if (foundCurrentSubtypeBecameDisabled) { if (DBG) { - Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + mMode); + Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + currentMode); Log.w(TAG, "Last subtype was disabled. Update to the current one."); } updateSubtype(mImm.getCurrentInputMethodSubtype()); @@ -209,9 +209,10 @@ public class SubtypeSwitcher { public void updateSubtype(InputMethodSubtype newSubtype) { final String newLocale; final String newMode; + final String oldMode = getCurrentSubtypeMode(); if (newSubtype == null) { // Normally, newSubtype shouldn't be null. But just in case newSubtype was null, - // fallback to the default locale and mode. + // fallback to the default locale. Log.w(TAG, "Couldn't get the current subtype."); newLocale = "en_US"; newMode = KEYBOARD_MODE; @@ -220,8 +221,8 @@ public class SubtypeSwitcher { newMode = newSubtype.getMode(); } if (DBG) { - Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode - + ", from: " + mInputLocaleStr + ", " + mMode); + Log.w(TAG, "Update subtype to:" + newLocale + "," + newSubtype.getMode() + + ", from: " + mInputLocaleStr + ", " + oldMode); } boolean languageChanged = false; if (!newLocale.equals(mInputLocaleStr)) { @@ -231,13 +232,12 @@ public class SubtypeSwitcher { updateInputLocale(newLocale); } boolean modeChanged = false; - String oldMode = mMode; - if (!newMode.equals(mMode)) { - if (mMode != null) { + if (!newMode.equals(oldMode)) { + if (oldMode != null) { modeChanged = true; } - mMode = newMode; } + mCurrentSubtype = newSubtype; // If the old mode is voice input, we need to reset or cancel its status. // We cancel its status when we change mode, while we reset otherwise. @@ -262,7 +262,7 @@ public class SubtypeSwitcher { triggerVoiceIME(); } } else { - Log.w(TAG, "Unknown subtype mode: " + mMode); + Log.w(TAG, "Unknown subtype mode: " + newMode); if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { // We need to reset the voice input to release the resources and to reset its status // as it is not the current input mode. @@ -483,7 +483,7 @@ public class SubtypeSwitcher { } public boolean isKeyboardMode() { - return KEYBOARD_MODE.equals(mMode); + return KEYBOARD_MODE.equals(getCurrentSubtypeMode()); } @@ -506,7 +506,7 @@ public class SubtypeSwitcher { } public boolean isVoiceMode() { - return VOICE_MODE.equals(mMode); + return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode()); } private void triggerVoiceIME() { @@ -572,6 +572,30 @@ public class SubtypeSwitcher { } } + ///////////////////////////// + // Other utility functions // + ///////////////////////////// + + public String getCurrentSubtypeExtraValue() { + // If null, return what an empty ExtraValue would return : the empty string. + return null != mCurrentSubtype ? mCurrentSubtype.getExtraValue() : ""; + } + + public boolean currentSubtypeContainsExtraValueKey(String key) { + // If null, return what an empty ExtraValue would return : false. + return null != mCurrentSubtype ? mCurrentSubtype.containsExtraValueKey(key) : false; + } + + public String getCurrentSubtypeExtraValueOf(String key) { + // If null, return what an empty ExtraValue would return : null. + return null != mCurrentSubtype ? mCurrentSubtype.getExtraValueOf(key) : null; + } + + public String getCurrentSubtypeMode() { + return null != mCurrentSubtype ? mCurrentSubtype.getMode() : KEYBOARD_MODE; + } + + // A list of locales which are supported by default for voice input, unless we get a // different list from Gservices. private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index b10dd6d7b..555a522eb 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -126,7 +126,8 @@ static jint latinime_BinaryDictionary_open(JNIEnv *env, jobject object, static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jint dict, jint proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray, - jintArray inputArray, jint arraySize, jcharArray outputArray, jintArray frequencyArray) { + jintArray inputArray, jint arraySize, jint flags, + jcharArray outputArray, jintArray frequencyArray) { Dictionary *dictionary = (Dictionary*)dict; if (!dictionary) return 0; ProximityInfo *pInfo = (ProximityInfo*)proximityInfo; @@ -140,7 +141,7 @@ static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jchar *outputChars = env->GetCharArrayElements(outputArray, NULL); int count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, inputCodes, - arraySize, (unsigned short*) outputChars, frequencies); + arraySize, flags, (unsigned short*) outputChars, frequencies); env->ReleaseIntArrayElements(frequencyArray, frequencies, 0); env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT); @@ -213,7 +214,7 @@ static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jint di static JNINativeMethod sMethods[] = { {"openNative", "(Ljava/lang/String;JJIIIII)I", (void*)latinime_BinaryDictionary_open}, {"closeNative", "(I)V", (void*)latinime_BinaryDictionary_close}, - {"getSuggestionsNative", "(II[I[I[II[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions}, + {"getSuggestionsNative", "(II[I[I[III[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions}, {"isValidWordNative", "(I[CI)Z", (void*)latinime_BinaryDictionary_isValidWord}, {"getBigramsNative", "(I[CI[II[C[IIII)I", (void*)latinime_BinaryDictionary_getBigrams} }; diff --git a/native/src/debug.h b/native/src/debug.h index e5572e1a5..ae629b222 100644 --- a/native/src/debug.h +++ b/native/src/debug.h @@ -55,4 +55,15 @@ static inline void LOGI_S16_PLUS(unsigned short* string, const unsigned int leng // usleep(10); } +static inline void printDebug(const char* tag, int* codes, int codesSize, int MAX_PROXIMITY_CHARS) { + unsigned char *buf = (unsigned char*)malloc((1 + codesSize) * sizeof(*buf)); + + buf[codesSize] = 0; + while (--codesSize >= 0) + buf[codesSize] = (unsigned char)codes[codesSize * MAX_PROXIMITY_CHARS]; + LOGI("%s, WORD = %s", tag, buf); + + free(buf); +} + #endif // LATINIME_DEBUG_H diff --git a/native/src/dictionary.h b/native/src/dictionary.h index fbbb8312b..13b2a2816 100644 --- a/native/src/dictionary.h +++ b/native/src/dictionary.h @@ -29,9 +29,9 @@ public: Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler, int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives); int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates, - int *codes, int codesSize, unsigned short *outWords, int *frequencies) { + int *codes, int codesSize, int flags, unsigned short *outWords, int *frequencies) { return mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, - codesSize, outWords, frequencies); + codesSize, flags, outWords, frequencies); } // TODO: Call mBigramDictionary instead of mUnigramDictionary diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index 72b0f361a..9aa36b064 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -29,20 +29,136 @@ namespace latinime { +const UnigramDictionary::digraph_t UnigramDictionary::GERMAN_UMLAUT_DIGRAPHS[] = + { { 'a', 'e' }, + { 'o', 'e' }, + { 'u', 'e' } }; + UnigramDictionary::UnigramDictionary(const unsigned char *dict, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion) : DICT(dict), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords), MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion), TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier), - ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0) { + ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0), + BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(*mInputCodes)) { if (DEBUG_DICT) LOGI("UnigramDictionary - constructor"); } UnigramDictionary::~UnigramDictionary() {} -int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, - int *ycoordinates, int *codes, int codesSize, unsigned short *outWords, int *frequencies) { +static inline unsigned int getCodesBufferSize(const int* codes, const int codesSize, + const int MAX_PROXIMITY_CHARS) { + return sizeof(*codes) * MAX_PROXIMITY_CHARS * codesSize; +} + +bool UnigramDictionary::isDigraph(const int* codes, const int i, const int codesSize) const { + + // There can't be a digraph if we don't have at least 2 characters to examine + if (i + 2 > codesSize) return false; + + // Search for the first char of some digraph + int lastDigraphIndex = -1; + const int thisChar = codes[i * MAX_PROXIMITY_CHARS]; + for (lastDigraphIndex = sizeof(GERMAN_UMLAUT_DIGRAPHS) / sizeof(GERMAN_UMLAUT_DIGRAPHS[0]) - 1; + lastDigraphIndex >= 0; --lastDigraphIndex) { + if (thisChar == GERMAN_UMLAUT_DIGRAPHS[lastDigraphIndex].first) break; + } + // No match: return early + if (lastDigraphIndex < 0) return false; + + // It's an interesting digraph if the second char matches too. + return GERMAN_UMLAUT_DIGRAPHS[lastDigraphIndex].second == codes[(i + 1) * MAX_PROXIMITY_CHARS]; +} + +// Mostly the same arguments as the non-recursive version, except: +// codes is the original value. It points to the start of the work buffer, and gets passed as is. +// codesSize is the size of the user input (thus, it is the size of codesSrc). +// codesDest is the current point in the work buffer. +// codesSrc is the current point in the user-input, original, content-unmodified buffer. +// codesRemain is the remaining size in codesSrc. +void UnigramDictionary::getWordWithDigraphSuggestionsRec(const ProximityInfo *proximityInfo, + const int *xcoordinates, const int* ycoordinates, const int *codesBuffer, + const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain, + int* codesDest, unsigned short* outWords, int* frequencies) { + + for (int i = 0; i < codesRemain; ++i) { + if (isDigraph(codesSrc, i, codesRemain)) { + // Found a digraph. We will try both spellings. eg. the word is "pruefen" + + // Copy the word up to the first char of the digraph, then continue processing + // on the remaining part of the word, skipping the second char of the digraph. + // In our example, copy "pru" and continue running on "fen" + memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR); + getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer, + codesBufferSize, flags, codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, + codesRemain - i - 1, codesDest + i * MAX_PROXIMITY_CHARS, + outWords, frequencies); + + // Copy the second char of the digraph in place, then continue processing on + // the remaining part of the word. + // In our example, after "pru" in the buffer copy the "e", and continue running on "fen" + memcpy(codesDest + i * MAX_PROXIMITY_CHARS, codesSrc + i * MAX_PROXIMITY_CHARS, + BYTES_IN_ONE_CHAR); + getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer, + codesBufferSize, flags, codesSrc + i * MAX_PROXIMITY_CHARS, codesRemain - i, + codesDest + i * MAX_PROXIMITY_CHARS, outWords, frequencies); + return; + } + } + + // If we come here, we hit the end of the word: let's check it against the dictionary. + // In our example, we'll come here once for "prufen" and then once for "pruefen". + // If the word contains several digraphs, we'll come it for the product of them. + // eg. if the word is "ueberpruefen" we'll test, in order, against + // "uberprufen", "uberpruefen", "ueberprufen", "ueberpruefen". + const unsigned int remainingBytes = BYTES_IN_ONE_CHAR * codesRemain; + if (0 != remainingBytes) + memcpy(codesDest, codesSrc, remainingBytes); + + getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codesBuffer, + (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + codesRemain, outWords, frequencies); +} + +int UnigramDictionary::getSuggestions(const ProximityInfo *proximityInfo, const int *xcoordinates, + const int *ycoordinates, const int *codes, const int codesSize, const int flags, + unsigned short *outWords, int *frequencies) { + + if (REQUIRES_GERMAN_UMLAUT_PROCESSING & flags) + { // Incrementally tune the word and try all possibilities + int codesBuffer[getCodesBufferSize(codes, codesSize, MAX_PROXIMITY_CHARS)]; + getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer, + codesSize, flags, codes, codesSize, codesBuffer, outWords, frequencies); + } else { // Normal processing + getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize, + outWords, frequencies); + } + + PROF_START(6); + // Get the word count + int suggestedWordsCount = 0; + while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) { + suggestedWordsCount++; + } + + if (DEBUG_DICT) { + LOGI("Returning %d words", suggestedWordsCount); + LOGI("Next letters: "); + for (int k = 0; k < NEXT_LETTERS_SIZE; k++) { + if (mNextLettersFrequency[k] > 0) { + LOGI("%c = %d,", k, mNextLettersFrequency[k]); + } + } + } + PROF_END(6); + PROF_CLOSE; + return suggestedWordsCount; +} + +void UnigramDictionary::getWordSuggestions(const ProximityInfo *proximityInfo, + const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize, + unsigned short *outWords, int *frequencies) { + PROF_OPEN; PROF_START(0); initSuggestions(codes, codesSize, outWords, frequencies); @@ -103,30 +219,10 @@ int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, int *xcoordi } } PROF_END(5); - - PROF_START(6); - // Get the word count - int suggestedWordsCount = 0; - while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) { - suggestedWordsCount++; - } - - if (DEBUG_DICT) { - LOGI("Returning %d words", suggestedWordsCount); - LOGI("Next letters: "); - for (int k = 0; k < NEXT_LETTERS_SIZE; k++) { - if (mNextLettersFrequency[k] > 0) { - LOGI("%c = %d,", k, mNextLettersFrequency[k]); - } - } - } - PROF_END(6); - PROF_CLOSE; - return suggestedWordsCount; } -void UnigramDictionary::initSuggestions(int *codes, int codesSize, unsigned short *outWords, - int *frequencies) { +void UnigramDictionary::initSuggestions(const int *codes, const int codesSize, + unsigned short *outWords, int *frequencies) { if (DEBUG_DICT) LOGI("initSuggest"); mFrequencies = frequencies; mOutputChars = outWords; @@ -204,7 +300,7 @@ bool UnigramDictionary::sameAsTyped(unsigned short *word, int length) { if (length != mInputLength) { return false; } - int *inputCodes = mInputCodes; + const int *inputCodes = mInputCodes; while (length--) { if ((unsigned int) *inputCodes != (unsigned int) *word) { return false; @@ -423,7 +519,7 @@ inline bool UnigramDictionary::existsAdjacentProximityChars(const int inputIndex const int currentChar = *getInputCharsAt(inputIndex); const int leftIndex = inputIndex - 1; if (leftIndex >= 0) { - int *leftChars = getInputCharsAt(leftIndex); + const int *leftChars = getInputCharsAt(leftIndex); int i = 0; while (leftChars[i] > 0 && i < MAX_PROXIMITY_CHARS) { if (leftChars[i++] == currentChar) return true; @@ -431,7 +527,7 @@ inline bool UnigramDictionary::existsAdjacentProximityChars(const int inputIndex } const int rightIndex = inputIndex + 1; if (rightIndex < inputLength) { - int *rightChars = getInputCharsAt(rightIndex); + const int *rightChars = getInputCharsAt(rightIndex); int i = 0; while (rightChars[i] > 0 && i < MAX_PROXIMITY_CHARS) { if (rightChars[i++] == currentChar) return true; @@ -523,7 +619,7 @@ inline bool UnigramDictionary::processCurrentNode(const int pos, const int depth *newDiffs = diffs; *newInputIndex = inputIndex; } else { - int *currentChars = getInputCharsAt(inputIndex); + const int *currentChars = getInputCharsAt(inputIndex); if (transposedPos >= 0) { if (inputIndex == transposedPos) currentChars += MAX_PROXIMITY_CHARS; diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index e84875b59..a95984520 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -33,12 +33,22 @@ class UnigramDictionary { public: UnigramDictionary(const unsigned char *dict, int typedLetterMultipler, int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion); - int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates, - int *codes, int codesSize, unsigned short *outWords, int *frequencies); + int getSuggestions(const ProximityInfo *proximityInfo, const int *xcoordinates, + const int *ycoordinates, const int *codes, const int codesSize, const int flags, + unsigned short *outWords, int *frequencies); ~UnigramDictionary(); private: - void initSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies); + void getWordSuggestions(const ProximityInfo *proximityInfo, const int *xcoordinates, + const int *ycoordinates, const int *codes, const int codesSize, + unsigned short *outWords, int *frequencies); + bool isDigraph(const int* codes, const int i, const int codesSize) const; + void getWordWithDigraphSuggestionsRec(const ProximityInfo *proximityInfo, + const int *xcoordinates, const int* ycoordinates, const int *codesBuffer, + const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain, + int* codesDest, unsigned short* outWords, int* frequencies); + void initSuggestions(const int *codes, const int codesSize, unsigned short *outWords, + int *frequencies); void getSuggestionCandidates(const int skipPos, const int excessivePos, const int transposedPos, int *nextLetters, const int nextLettersSize, const int maxDepth); @@ -86,7 +96,7 @@ private: const int startInputIndex, const int depth, unsigned short *word, int *newChildPosition, int *newCount, bool *newTerminal, int *newFreq, int *siblingPos); bool existsAdjacentProximityChars(const int inputIndex, const int inputLength); - inline int* getInputCharsAt(const int index) { + inline const int* getInputCharsAt(const int index) { return mInputCodes + (index * MAX_PROXIMITY_CHARS); } const unsigned char *DICT; @@ -97,10 +107,20 @@ private: const int TYPED_LETTER_MULTIPLIER; const int FULL_WORD_MULTIPLIER; const int ROOT_POS; + const unsigned int BYTES_IN_ONE_CHAR; + + // Flags for special processing + // Those *must* match the flags in BinaryDictionary.Flags.ALL_FLAGS in BinaryDictionary.java + // or something very bad (like, the apocalypse) will happen. + // Please update both at the same time. + enum { + REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1 + }; + static const struct digraph_t { int first; int second; } GERMAN_UMLAUT_DIGRAPHS[]; int *mFrequencies; unsigned short *mOutputChars; - int *mInputCodes; + const int *mInputCodes; int mInputLength; // MAX_WORD_LENGTH_INTERNAL must be bigger than MAX_WORD_LENGTH unsigned short mWord[MAX_WORD_LENGTH_INTERNAL]; -- cgit v1.2.3-83-g751a From 1eff7d77c8acaedf043bf00a0b8e750804f64444 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 2 Mar 2011 17:17:15 -0800 Subject: Remove mIsApplicationSpecifiedCompletions flags from SuggestedWords Bug: 3504990 Change-Id: Ib7588ab05fa8b8ab58bf3bea6ff1f644c53ebb94 --- .../src/com/android/inputmethod/latin/CandidateView.java | 3 --- java/src/com/android/inputmethod/latin/LatinIME.java | 1 + .../com/android/inputmethod/latin/SuggestedWords.java | 16 +++++----------- 3 files changed, 6 insertions(+), 14 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index 9699ad136..5719b9012 100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -347,9 +347,6 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo if (mShowingAddToDictionary && index == 0) { addToDictionary(word); } else { - if (!mSuggestions.mIsApplicationSpecifiedCompletions) { - TextEntryState.acceptedSuggestion(mSuggestions.getWord(0), word); - } mService.pickSuggestionManually(index, word); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 838249a39..79181c6c9 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1636,6 +1636,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } + TextEntryState.acceptedSuggestion(suggestions.getWord(0), suggestion); // If this is a punctuation, apply it through the normal key press if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0)) || isSuggestedPunctuation(suggestion.charAt(0)))) { diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index f774ce3a5..fe7aac7c2 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -24,23 +24,20 @@ import java.util.HashSet; import java.util.List; public class SuggestedWords { - public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, false, null); + public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, null); public final List mWords; - public final boolean mIsApplicationSpecifiedCompletions; public final boolean mTypedWordValid; public final boolean mHasMinimalSuggestion; public final List mSuggestedWordInfoList; - private SuggestedWords(List words, boolean isApplicationSpecifiedCompletions, - boolean typedWordValid, boolean hasMinamlSuggestion, - List suggestedWordInfoList) { + private SuggestedWords(List words, boolean typedWordValid, + boolean hasMinamlSuggestion, List suggestedWordInfoList) { if (words != null) { mWords = words; } else { mWords = Collections.emptyList(); } - mIsApplicationSpecifiedCompletions = isApplicationSpecifiedCompletions; mTypedWordValid = typedWordValid; mHasMinimalSuggestion = hasMinamlSuggestion; mSuggestedWordInfoList = suggestedWordInfoList; @@ -64,7 +61,6 @@ public class SuggestedWords { public static class Builder { private List mWords = new ArrayList(); - private boolean mIsCompletions; private boolean mTypedWordValid; private boolean mHasMinimalSuggestion; private List mSuggestedWordInfoList = @@ -109,7 +105,6 @@ public class SuggestedWords { public Builder setApplicationSpecifiedCompletions(CompletionInfo[] infos) { for (CompletionInfo info : infos) addWord(info.getText()); - mIsCompletions = true; return this; } @@ -141,15 +136,14 @@ public class SuggestedWords { alreadySeen.add(prevWord); } } - mIsCompletions = false; mTypedWordValid = false; mHasMinimalSuggestion = false; return this; } public SuggestedWords build() { - return new SuggestedWords(mWords, mIsCompletions, mTypedWordValid, - mHasMinimalSuggestion, mSuggestedWordInfoList); + return new SuggestedWords(mWords, mTypedWordValid, mHasMinimalSuggestion, + mSuggestedWordInfoList); } public int size() { -- cgit v1.2.3-83-g751a From 662bb7c26cadd5026cb3d4e5d081aae163bd5a3a Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 2 Mar 2011 17:02:06 -0800 Subject: Refactor TextEntryState not to use enum Bug: 3504990 Change-Id: Id1ae9715d9f9ba99702e4cd5c243026a84d6861d --- .../com/android/inputmethod/latin/LatinIME.java | 23 +- .../android/inputmethod/latin/TextEntryState.java | 354 +++++++++------------ 2 files changed, 153 insertions(+), 224 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 79181c6c9..5909b3b53 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -542,7 +542,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeSwitcher.updateParametersOnStartInputView(); - TextEntryState.newSession(this); + TextEntryState.reset(); // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to // know now whether this is a password text field, because we need to know now whether we @@ -746,15 +746,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } mVoiceConnector.setVoiceInputHighlighted(false); } else if (!mHasValidSuggestions && !mJustAccepted) { - switch (TextEntryState.getState()) { - case ACCEPTED_DEFAULT: - TextEntryState.reset(); - // $FALL-THROUGH$ - case SPACE_AFTER_PICKED: + if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) { + if (TextEntryState.isAcceptedDefault()) + TextEntryState.reset(); mJustAddedAutoSpace = false; // The user moved the cursor. - break; - default: - break; } } mJustAccepted = false; @@ -832,7 +827,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mVoiceConnector.hideVoiceWindow(mConfigurationChanging); mWordHistory.clear(); super.hideWindow(); - TextEntryState.endSession(); } @Override @@ -1231,7 +1225,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.postUpdateShiftKeyState(); TextEntryState.backspace(); - if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) { + if (TextEntryState.isUndoCommit()) { revertLastWord(deleteChar); ic.endBatchEdit(); return; @@ -1391,14 +1385,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Handle the case of ". ." -> " .." with auto-space if necessary // before changing the TextEntryState. - if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED - && primaryCode == Keyboard.CODE_PERIOD) { + if (TextEntryState.isPunctuationAfterAccepted() && primaryCode == Keyboard.CODE_PERIOD) { reswapPeriodAndSpace(); } TextEntryState.typedCharacter((char) primaryCode, true); - if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED - && primaryCode != Keyboard.CODE_ENTER) { + if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER) { swapPunctuationAndSpace(); } else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) { doubleSpace(); @@ -1430,7 +1422,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) inputView.closing(); - TextEntryState.endSession(); } private void saveWordInHistory(CharSequence result) { diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java index c8b8c334d..7b629e3e7 100644 --- a/java/src/com/android/inputmethod/latin/TextEntryState.java +++ b/java/src/com/android/inputmethod/latin/TextEntryState.java @@ -16,117 +16,39 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.keyboard.Key; - -import android.content.Context; -import android.text.format.DateFormat; import android.util.Log; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Calendar; - public class TextEntryState { - - private static final boolean DBG = false; - - private static final String TAG = "TextEntryState"; - - private static boolean LOGGING = false; - - private static int sBackspaceCount = 0; - - private static int sAutoSuggestCount = 0; - - private static int sAutoSuggestUndoneCount = 0; - - private static int sManualSuggestCount = 0; - - private static int sWordNotInDictionaryCount = 0; - - private static int sSessionCount = 0; - - private static int sTypedChars; - - private static int sActualChars; - - public enum State { - UNKNOWN, - START, - IN_WORD, - ACCEPTED_DEFAULT, - PICKED_SUGGESTION, - PUNCTUATION_AFTER_WORD, - PUNCTUATION_AFTER_ACCEPTED, - SPACE_AFTER_ACCEPTED, - SPACE_AFTER_PICKED, - UNDO_COMMIT, - RECORRECTING, - PICKED_RECORRECTION, + private static final String TAG = TextEntryState.class.getSimpleName(); + private static final boolean DEBUG = true; + + private static final int UNKNOWN = 0; + private static final int START = 1; + private static final int IN_WORD = 2; + private static final int ACCEPTED_DEFAULT = 3; + private static final int PICKED_SUGGESTION = 4; + private static final int PUNCTUATION_AFTER_WORD = 5; + private static final int PUNCTUATION_AFTER_ACCEPTED = 6; + private static final int SPACE_AFTER_ACCEPTED = 7; + private static final int SPACE_AFTER_PICKED = 8; + private static final int UNDO_COMMIT = 9; + private static final int RECORRECTING = 10; + private static final int PICKED_RECORRECTION = 11; + + private static int sState = UNKNOWN; + private static int sPreviousState = UNKNOWN; + + private static void setState(final int newState) { + sPreviousState = sState; + sState = newState; } - private static State sState = State.UNKNOWN; - - private static FileOutputStream sKeyLocationFile; - private static FileOutputStream sUserActionFile; - - public static void newSession(Context context) { - sSessionCount++; - sAutoSuggestCount = 0; - sBackspaceCount = 0; - sAutoSuggestUndoneCount = 0; - sManualSuggestCount = 0; - sWordNotInDictionaryCount = 0; - sTypedChars = 0; - sActualChars = 0; - sState = State.START; - - if (LOGGING) { - try { - sKeyLocationFile = context.openFileOutput("key.txt", Context.MODE_APPEND); - sUserActionFile = context.openFileOutput("action.txt", Context.MODE_APPEND); - } catch (IOException ioe) { - Log.e("TextEntryState", "Couldn't open file for output: " + ioe); - } - } - } - - public static void endSession() { - if (sKeyLocationFile == null) { - return; - } - try { - sKeyLocationFile.close(); - // Write to log file - // Write timestamp, settings, - String out = DateFormat.format("MM:dd hh:mm:ss", Calendar.getInstance().getTime()) - .toString() - + " BS: " + sBackspaceCount - + " auto: " + sAutoSuggestCount - + " manual: " + sManualSuggestCount - + " typed: " + sWordNotInDictionaryCount - + " undone: " + sAutoSuggestUndoneCount - + " saved: " + ((float) (sActualChars - sTypedChars) / sActualChars) - + "\n"; - sUserActionFile.write(out.getBytes()); - sUserActionFile.close(); - sKeyLocationFile = null; - sUserActionFile = null; - } catch (IOException ioe) { - // ignore - } - } - public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) { if (typedWord == null) return; - if (!typedWord.equals(actualWord)) { - sAutoSuggestCount++; - } - sTypedChars += typedWord.length(); - sActualChars += actualWord.length(); - sState = State.ACCEPTED_DEFAULT; + setState(ACCEPTED_DEFAULT); LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString()); - displayState(); + if (DEBUG) + displayState("acceptedDefault", "typedWord", typedWord, "actualWord", actualWord); } // State.ACCEPTED_DEFAULT will be changed to other sub-states @@ -138,151 +60,167 @@ public class TextEntryState { case SPACE_AFTER_ACCEPTED: case PUNCTUATION_AFTER_ACCEPTED: case IN_WORD: - sState = State.ACCEPTED_DEFAULT; + setState(ACCEPTED_DEFAULT); break; default: break; } - displayState(); + if (DEBUG) displayState("backToAcceptedDefault", "typedWord", typedWord); } - public static void acceptedTyped(@SuppressWarnings("unused") CharSequence typedWord) { - sWordNotInDictionaryCount++; - sState = State.PICKED_SUGGESTION; - displayState(); + public static void acceptedTyped(CharSequence typedWord) { + setState(PICKED_SUGGESTION); + if (DEBUG) displayState("acceptedTyped", "typedWord", typedWord); } public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) { - sManualSuggestCount++; - State oldState = sState; - if (typedWord.equals(actualWord)) { - acceptedTyped(typedWord); - } - if (oldState == State.RECORRECTING || oldState == State.PICKED_RECORRECTION) { - sState = State.PICKED_RECORRECTION; + if (sState == RECORRECTING || sState == PICKED_RECORRECTION) { + setState(PICKED_RECORRECTION); } else { - sState = State.PICKED_SUGGESTION; + setState(PICKED_SUGGESTION); } - displayState(); + if (DEBUG) + displayState("acceptedSuggestion", "typedWord", typedWord, "actualWord", actualWord); } public static void selectedForRecorrection() { - sState = State.RECORRECTING; - displayState(); + setState(RECORRECTING); + if (DEBUG) displayState("selectedForRecorrection"); } public static void onAbortRecorrection() { - if (isRecorrecting()) { - sState = State.START; + if (sState == RECORRECTING || sState == PICKED_RECORRECTION) { + setState(START); } - displayState(); + if (DEBUG) displayState("onAbortRecorrection"); } public static void typedCharacter(char c, boolean isSeparator) { - boolean isSpace = c == ' '; + final boolean isSpace = (c == ' '); switch (sState) { - case IN_WORD: - if (isSpace || isSeparator) { - sState = State.START; - } else { - // State hasn't changed. - } - break; - case ACCEPTED_DEFAULT: - case SPACE_AFTER_PICKED: - if (isSpace) { - sState = State.SPACE_AFTER_ACCEPTED; - } else if (isSeparator) { - sState = State.PUNCTUATION_AFTER_ACCEPTED; - } else { - sState = State.IN_WORD; - } - break; - case PICKED_SUGGESTION: - case PICKED_RECORRECTION: - if (isSpace) { - sState = State.SPACE_AFTER_PICKED; - } else if (isSeparator) { - // Swap - sState = State.PUNCTUATION_AFTER_ACCEPTED; - } else { - sState = State.IN_WORD; - } - break; - case START: - case UNKNOWN: - case SPACE_AFTER_ACCEPTED: - case PUNCTUATION_AFTER_ACCEPTED: - case PUNCTUATION_AFTER_WORD: - if (!isSpace && !isSeparator) { - sState = State.IN_WORD; - } else { - sState = State.START; - } - break; - case UNDO_COMMIT: - if (isSpace || isSeparator) { - sState = State.ACCEPTED_DEFAULT; - } else { - sState = State.IN_WORD; - } - break; - case RECORRECTING: - sState = State.START; - break; + case IN_WORD: + if (isSpace || isSeparator) { + setState(START); + } else { + // State hasn't changed. + } + break; + case ACCEPTED_DEFAULT: + case SPACE_AFTER_PICKED: + if (isSpace) { + setState(SPACE_AFTER_ACCEPTED); + } else if (isSeparator) { + // Swap + setState(PUNCTUATION_AFTER_ACCEPTED); + } else { + setState(IN_WORD); + } + break; + case PICKED_SUGGESTION: + case PICKED_RECORRECTION: + if (isSpace) { + setState(SPACE_AFTER_PICKED); + } else if (isSeparator) { + // Swap + setState(PUNCTUATION_AFTER_ACCEPTED); + } else { + setState(IN_WORD); + } + break; + case START: + case UNKNOWN: + case SPACE_AFTER_ACCEPTED: + case PUNCTUATION_AFTER_ACCEPTED: + case PUNCTUATION_AFTER_WORD: + if (!isSpace && !isSeparator) { + setState(IN_WORD); + } else { + setState(START); + } + break; + case UNDO_COMMIT: + if (isSpace || isSeparator) { + setState(ACCEPTED_DEFAULT); + } else { + setState(IN_WORD); + } + break; + case RECORRECTING: + setState(START); + break; } - displayState(); + if (DEBUG) displayState("typedCharacter", "char", c, "isSeparator", isSeparator); } public static void backspace() { - if (sState == State.ACCEPTED_DEFAULT) { - sState = State.UNDO_COMMIT; - sAutoSuggestUndoneCount++; + if (sState == ACCEPTED_DEFAULT) { + setState(UNDO_COMMIT); LatinImeLogger.logOnAutoSuggestionCanceled(); - } else if (sState == State.UNDO_COMMIT) { - sState = State.IN_WORD; + } else if (sState == UNDO_COMMIT) { + setState(IN_WORD); } - sBackspaceCount++; - displayState(); + if (DEBUG) displayState("backspace"); } public static void reset() { - sState = State.START; - displayState(); + setState(START); + if (DEBUG) displayState("reset"); } - public static State getState() { - if (DBG) { - Log.d(TAG, "Returning state = " + sState); - } - return sState; + public static boolean isAcceptedDefault() { + return sState == ACCEPTED_DEFAULT; } - public static boolean isRecorrecting() { - return sState == State.RECORRECTING || sState == State.PICKED_RECORRECTION; + public static boolean isSpaceAfterPicked() { + return sState == SPACE_AFTER_PICKED; } - public static void keyPressedAt(Key key, int x, int y) { - if (LOGGING && sKeyLocationFile != null && key.mCode >= 32) { - String out = - "KEY: " + (char) key.mCode - + " X: " + x - + " Y: " + y - + " MX: " + (key.mX + key.mWidth / 2) - + " MY: " + (key.mY + key.mHeight / 2) - + "\n"; - try { - sKeyLocationFile.write(out.getBytes()); - } catch (IOException ioe) { - // TODO: May run out of space - } + public static boolean isUndoCommit() { + return sState == UNDO_COMMIT; + } + + public static boolean isPunctuationAfterAccepted() { + return sState == PUNCTUATION_AFTER_ACCEPTED; + } + + public static boolean isRecorrecting() { + return sState == RECORRECTING || sState == PICKED_RECORRECTION; + } + + public static String getState() { + return stateName(sState); + } + + private static String stateName(int state) { + switch (state) { + case START: return "START"; + case IN_WORD: return "IN_WORD"; + case ACCEPTED_DEFAULT: return "ACCEPTED_DEFAULT"; + case PICKED_SUGGESTION: return "PICKED_SUGGESTION"; + case PUNCTUATION_AFTER_WORD: return "PUNCTUATION_AFTER_WORD"; + case PUNCTUATION_AFTER_ACCEPTED: return "PUNCTUATION_AFTER_ACCEPTED"; + case SPACE_AFTER_ACCEPTED: return "SPACE_AFTER_ACCEPTED"; + case SPACE_AFTER_PICKED: return "SPACE_AFTER_PICKED"; + case UNDO_COMMIT: return "UNDO_COMMIT"; + case RECORRECTING: return "RECORRECTING"; + case PICKED_RECORRECTION: return "PICKED_RECORRECTION"; + default: return "UNKNOWN"; } } - private static void displayState() { - if (DBG) { - Log.d(TAG, "State = " + sState); + private static void displayState(String title, Object ... args) { + final StringBuilder sb = new StringBuilder(title); + sb.append(':'); + for (int i = 0; i < args.length; i += 2) { + sb.append(' '); + sb.append(args[i]); + sb.append('='); + sb.append(args[i+1].toString()); } + sb.append(" state="); + sb.append(stateName(sState)); + sb.append(" previous="); + sb.append(stateName(sPreviousState)); + Log.d(TAG, sb.toString()); } } - -- cgit v1.2.3-83-g751a From 424f6ec2bcdfad53f571aa72f2979a0ba30d3ca2 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 2 Mar 2011 21:14:53 -0800 Subject: Continue swapping sentence separator and space Bug: 3504990 Change-Id: I3de44e9d9adb0bcafa2e59685c4cdc257716b1d6 --- java/src/com/android/inputmethod/latin/LatinIME.java | 1 - java/src/com/android/inputmethod/latin/TextEntryState.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 5909b3b53..d0d0b723f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1627,7 +1627,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } - TextEntryState.acceptedSuggestion(suggestions.getWord(0), suggestion); // If this is a punctuation, apply it through the normal key press if (suggestion.length() == 1 && (isWordSeparator(suggestion.charAt(0)) || isSuggestedPunctuation(suggestion.charAt(0)))) { diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java index 7b629e3e7..a4e0b359b 100644 --- a/java/src/com/android/inputmethod/latin/TextEntryState.java +++ b/java/src/com/android/inputmethod/latin/TextEntryState.java @@ -107,6 +107,7 @@ public class TextEntryState { break; case ACCEPTED_DEFAULT: case SPACE_AFTER_PICKED: + case PUNCTUATION_AFTER_ACCEPTED: if (isSpace) { setState(SPACE_AFTER_ACCEPTED); } else if (isSeparator) { @@ -130,7 +131,6 @@ public class TextEntryState { case START: case UNKNOWN: case SPACE_AFTER_ACCEPTED: - case PUNCTUATION_AFTER_ACCEPTED: case PUNCTUATION_AFTER_WORD: if (!isSpace && !isSeparator) { setState(IN_WORD); -- cgit v1.2.3-83-g751a From 4503e2ea9853c1573f60903d8639d82e39e07c56 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 2 Mar 2011 21:34:11 -0800 Subject: Hide shortcut key when the shortcut IME is not enabled Bug: 3398726 Change-Id: I5e1bfc1fec9047c1c4e4f29fbf79f0fdf9356ffb --- .../inputmethod/keyboard/KeyboardParser.java | 3 ++- .../inputmethod/keyboard/KeyboardSwitcher.java | 2 +- .../com/android/inputmethod/latin/LatinIME.java | 12 ++++++---- .../android/inputmethod/latin/SubtypeSwitcher.java | 28 +++++++++++++++++----- 4 files changed, 32 insertions(+), 13 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java index 2632796dd..feb56ab3a 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -447,7 +447,8 @@ public class KeyboardParser { textAttr(KeyboardId.modeName( a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"), textAttr(KeyboardId.colorSchemeName( - a.getInt(R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"), + viewAttr.getInt( + R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"), booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"), booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"), booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"), diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 3d34e71a0..64a23ab92 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -194,7 +194,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha // displayed on its spacebar, it might have had arbitrary text fade factor. In such case, // we should reset the text fade factor. It is also applicable to shortcut key. keyboard.setSpacebarTextFadeFactor(0.0f, null); - keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutAvailable(), null); + keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady(), null); return keyboard; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index d0d0b723f..feaebb931 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -547,7 +547,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to // know now whether this is a password text field, because we need to know now whether we // want to enable the voice button. - mVoiceConnector.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) + final VoiceIMEConnector voiceIme = mVoiceConnector; + voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) || Utils.isVisiblePasswordInputType(attribute.inputType)); initializeInputAttributes(attribute); @@ -562,8 +563,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen loadSettings(attribute); if (mSubtypeSwitcher.isKeyboardMode()) { switcher.loadKeyboard(attribute, - mVoiceConnector.isVoiceButtonEnabled(), - mVoiceConnector.isVoiceButtonOnPrimary()); + mSubtypeSwitcher.isShortcutImeEnabled() && voiceIme.isVoiceButtonEnabled(), + voiceIme.isVoiceButtonOnPrimary()); switcher.updateShiftState(); } @@ -583,7 +584,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen checkRecorrectionOnStart(); inputView.setForeground(true); - mVoiceConnector.onStartInputView(inputView.getWindowToken()); + voiceIme.onStartInputView(inputView.getWindowToken()); if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); } @@ -1930,7 +1931,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // Reload keyboard because the current language has been changed. mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), - mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary()); + mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceConnector.isVoiceButtonEnabled(), + mVoiceConnector.isVoiceButtonOnPrimary()); initSuggest(); mKeyboardSwitcher.updateShiftState(); } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 50827c638..dc14d770a 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -46,7 +46,7 @@ import java.util.Map; public class SubtypeSwitcher { private static boolean DBG = LatinImeLogger.sDBG; - private static final String TAG = "SubtypeSwitcher"; + private static final String TAG = SubtypeSwitcher.class.getSimpleName(); private static final char LOCALE_SEPARATER = '_'; private static final String KEYBOARD_MODE = "keyboard"; @@ -221,7 +221,7 @@ public class SubtypeSwitcher { newMode = newSubtype.getMode(); } if (DBG) { - Log.w(TAG, "Update subtype to:" + newLocale + "," + newSubtype.getMode() + Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode + ", from: " + mInputLocaleStr + ", " + oldMode); } boolean languageChanged = false; @@ -355,11 +355,27 @@ public class SubtypeSwitcher { return false; } - public boolean isShortcutAvailable() { + public boolean isShortcutImeEnabled() { if (mShortcutInputMethodInfo == null) return false; - if (mShortcutSubtype != null && contains(mShortcutSubtype.getExtraValue().split(","), - SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) { + if (mShortcutSubtype == null) + return true; + final boolean allowsImplicitlySelectedSubtypes = true; + for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList( + mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) { + if (enabledSubtype.equals(mShortcutSubtype)) + return true; + } + return false; + } + + public boolean isShortcutImeReady() { + if (mShortcutInputMethodInfo == null) + return false; + if (mShortcutSubtype == null) + return true; + if (contains(mShortcutSubtype.getExtraValue().split(","), + SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) { return mIsNetworkConnected; } return true; @@ -373,7 +389,7 @@ public class SubtypeSwitcher { final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); final LatinKeyboard keyboard = switcher.getLatinKeyboard(); if (keyboard != null) { - keyboard.updateShortcutKey(isShortcutAvailable(), switcher.getInputView()); + keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getInputView()); } } -- cgit v1.2.3-83-g751a From 9f67e12a0e3f77985fb8bafe0db4c00e32317b9a Mon Sep 17 00:00:00 2001 From: satok Date: Wed, 2 Mar 2011 15:40:08 +0900 Subject: Move the auto correction functionalities to AutoCorrection.java - Also removed a quick fix for 2nd or more suggestions. Change-Id: I7bf46a9cd6a727473e68836954fca160c9d853e6 --- .../android/inputmethod/latin/AutoCorrection.java | 103 +++++++++++++++++++ .../com/android/inputmethod/latin/LatinIME.java | 3 +- .../src/com/android/inputmethod/latin/Suggest.java | 114 +++++++++------------ 3 files changed, 154 insertions(+), 66 deletions(-) create mode 100644 java/src/com/android/inputmethod/latin/AutoCorrection.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java new file mode 100644 index 000000000..d1154d9ed --- /dev/null +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -0,0 +1,103 @@ +/* + * 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; + +import android.util.Log; + +import java.util.ArrayList; + +public class AutoCorrection { + private static final boolean DBG = LatinImeLogger.sDBG; + private static final String TAG = AutoCorrection.class.getSimpleName(); + private boolean mHasAutoCorrection; + private CharSequence mAutoCorrectionWord; + private double mNormalizedScore; + + public void init() { + mHasAutoCorrection = false; + mAutoCorrectionWord = null; + mNormalizedScore = Integer.MIN_VALUE; + } + + public boolean hasAutoCorrection() { + return mHasAutoCorrection; + } + + public CharSequence getAutoCorrectionWord() { + return mAutoCorrectionWord; + } + + public double getNormalizedScore() { + return mNormalizedScore; + } + + public void updateAutoCorrectionStatus(Suggest suggest, + WordComposer wordComposer, ArrayList suggestions, int[] priorities, + CharSequence typedWord, double autoCorrectionThreshold, int correctionMode, + CharSequence quickFixedWord) { + if (hasAutoCorrectionForTypedWord( + suggest, wordComposer, suggestions, typedWord, correctionMode)) { + mHasAutoCorrection = true; + mAutoCorrectionWord = typedWord; + } else if (hasAutoCorrectForBinaryDictionary(wordComposer, suggestions, correctionMode, + priorities, typedWord, autoCorrectionThreshold)) { + mHasAutoCorrection = true; + mAutoCorrectionWord = suggestions.get(0); + } else if (hasAutoCorrectionForQuickFix(quickFixedWord)) { + mHasAutoCorrection = true; + mAutoCorrectionWord = quickFixedWord; + } + } + + private boolean hasAutoCorrectionForTypedWord(Suggest suggest, WordComposer wordComposer, + ArrayList suggestions, CharSequence typedWord, int correctionMode) { + return wordComposer.size() > 1 && suggestions.size() > 0 && suggest.isValidWord(typedWord) + && (correctionMode == Suggest.CORRECTION_FULL + || correctionMode == Suggest.CORRECTION_FULL_BIGRAM); + } + + private boolean hasAutoCorrectForBinaryDictionary(WordComposer wordComposer, + ArrayList suggestions, int correctionMode, int[] priorities, + CharSequence typedWord, double autoCorrectionThreshold) { + if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL + || correctionMode == Suggest.CORRECTION_FULL_BIGRAM) + && typedWord != null && suggestions.size() > 0 && priorities.length > 0) { + final CharSequence autoCorrectionCandidate = suggestions.get(0); + final int autoCorrectionCandidateScore = priorities[0]; + // TODO: when the normalized score of the first suggestion is nearly equals to + // the normalized score of the second suggestion, behave less aggressive. + mNormalizedScore = Utils.calcNormalizedScore( + typedWord,autoCorrectionCandidate, autoCorrectionCandidateScore); + if (DBG) { + Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionCandidate + "," + + autoCorrectionCandidateScore + ", " + mNormalizedScore + + "(" + autoCorrectionThreshold + ")"); + } + if (mNormalizedScore >= autoCorrectionThreshold) { + if (DBG) { + Log.d(TAG, "Auto corrected by S-threshold."); + } + return true; + } + } + return false; + } + + private boolean hasAutoCorrectionForQuickFix(CharSequence quickFixedWord) { + return quickFixedWord != null; + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 838249a39..611d36216 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1538,7 +1538,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void showSuggestions(WordComposer word) { - // TODO Maybe need better way of retrieving previous word + // TODO: May need a better way of retrieving previous word CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(), mWordSeparators); SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( @@ -2059,6 +2059,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void updateCorrectionMode() { + // TODO: cleanup messy flags mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false; mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) && !mInputTypeNoAutoCorrect && mHasDictionary; diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index ee6930d93..95a2d631b 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -67,6 +67,8 @@ public class Suggest implements Dictionary.WordCallback { private static final boolean DBG = LatinImeLogger.sDBG; + private AutoCorrection mAutoCorrection; + private BinaryDictionary mMainDict; private Dictionary mUserDictionary; @@ -90,7 +92,6 @@ public class Suggest implements Dictionary.WordCallback { private ArrayList mSuggestions = new ArrayList(); ArrayList mBigramSuggestions = new ArrayList(); private ArrayList mStringPool = new ArrayList(); - private boolean mHasAutoCorrection; private String mLowerOriginalWord; // TODO: Remove these member variables by passing more context to addWord() callback method @@ -101,12 +102,16 @@ public class Suggest implements Dictionary.WordCallback { public Suggest(Context context, int dictionaryResId) { mMainDict = BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN); - initPool(); + init(); } - // For unit test - /* package */ Suggest(File dictionary, long startOffset, long length) { + /* package for test */ Suggest(File dictionary, long startOffset, long length) { mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN); + init(); + } + + private void init() { + mAutoCorrection = new AutoCorrection(); initPool(); } @@ -205,7 +210,7 @@ public class Suggest implements Dictionary.WordCallback { public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer, CharSequence prevWordForBigram) { LatinImeLogger.onStartSuggestion(prevWordForBigram); - mHasAutoCorrection = false; + mAutoCorrection.init(); mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsAllUpperCase = wordComposer.isAllUpperCase(); collectGarbage(mSuggestions, mPrefMaxSuggestions); @@ -224,7 +229,6 @@ public class Suggest implements Dictionary.WordCallback { mLowerOriginalWord = ""; } - double normalizedScore = Integer.MIN_VALUE; if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM || mCorrectionMode == CORRECTION_BASIC)) { // At first character typed, search only the bigrams @@ -273,86 +277,67 @@ public class Suggest implements Dictionary.WordCallback { if (mContactsDictionary != null) { mContactsDictionary.getWords(wordComposer, this); } - - if (mSuggestions.size() > 0 && isValidWord(typedWord) - && (mCorrectionMode == CORRECTION_FULL - || mCorrectionMode == CORRECTION_FULL_BIGRAM)) { - if (DBG) { - Log.d(TAG, "Auto corrected by CORRECTION_FULL."); - } - mHasAutoCorrection = true; - } } if (mMainDict != null) mMainDict.getWords(wordComposer, this); - if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM) - && mSuggestions.size() > 0 && mPriorities.length > 0) { - // TODO: when the normalized score of the first suggestion is nearly equals to - // the normalized score of the second suggestion, behave less aggressive. - normalizedScore = Utils.calcNormalizedScore( - typedWord, mSuggestions.get(0), mPriorities[0]); - if (DBG) { - Log.d(TAG, "Normalized " + typedWord + "," + mSuggestions.get(0) + "," - + mPriorities[0] + ", " + normalizedScore - + "(" + mAutoCorrectionThreshold + ")"); - } - if (normalizedScore >= mAutoCorrectionThreshold) { - if (DBG) { - Log.d(TAG, "Auto corrected by S-threshold."); - } - mHasAutoCorrection = true; - } - } } + CharSequence autoText = null; + final String typedWordString = typedWord == null ? null : typedWord.toString(); if (typedWord != null) { - mSuggestions.add(0, typedWord.toString()); - } - if (mQuickFixesEnabled) { - int i = 0; - int max = 6; - // Don't autotext the suggestions from the dictionaries - if (mCorrectionMode == CORRECTION_BASIC) max = 1; - while (i < mSuggestions.size() && i < max) { - String suggestedWord = mSuggestions.get(i).toString().toLowerCase(); - CharSequence autoText = - AutoText.get(suggestedWord, 0, suggestedWord.length(), view); + // Apply quick fix only for the typed word. + if (mQuickFixesEnabled) { + final String lowerCaseTypedWord = typedWordString.toLowerCase(); + CharSequence tempAutoText = + AutoText.get(lowerCaseTypedWord, 0, lowerCaseTypedWord.length(), view); // Is there an AutoText (also known as Quick Fixes) correction? - boolean canAdd = autoText != null; // Capitalize as needed - final int autoTextLength = autoText != null ? autoText.length() : 0; - if (autoTextLength > 0 && (mIsAllUpperCase || mIsFirstCharCapitalized)) { - int poolSize = mStringPool.size(); - StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove( - poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); + if (!TextUtils.isEmpty(tempAutoText) + && (mIsAllUpperCase || mIsFirstCharCapitalized)) { + final int tempAutoTextLength = tempAutoText.length(); + final int poolSize = mStringPool.size(); + final StringBuilder sb = + poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) + : new StringBuilder(getApproxMaxWordLength()); sb.setLength(0); if (mIsAllUpperCase) { - sb.append(autoText.toString().toUpperCase()); + sb.append(tempAutoText.toString().toUpperCase()); } else if (mIsFirstCharCapitalized) { - sb.append(Character.toUpperCase(autoText.charAt(0))); - if (autoTextLength > 1) { - sb.append(autoText.subSequence(1, autoTextLength)); + sb.append(Character.toUpperCase(tempAutoText.charAt(0))); + if (tempAutoTextLength > 1) { + sb.append(tempAutoText.subSequence(1, tempAutoTextLength)); } } - autoText = sb.toString(); + tempAutoText = sb.toString(); } + boolean canAdd = tempAutoText != null; // Is that correction already the current prediction (or original word)? - canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i)); + canAdd &= !TextUtils.equals(tempAutoText, typedWord); // Is that correction already the next predicted word? - if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) { - canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1)); + if (canAdd && mSuggestions.size() > 0 && mCorrectionMode != CORRECTION_BASIC) { + canAdd &= !TextUtils.equals(tempAutoText, mSuggestions.get(0)); } if (canAdd) { if (DBG) { Log.d(TAG, "Auto corrected by AUTOTEXT."); } - mHasAutoCorrection = true; - mSuggestions.add(i + 1, autoText); - i++; + autoText = tempAutoText; } - i++; } } + + mAutoCorrection.updateAutoCorrectionStatus(this, wordComposer, mSuggestions, mPriorities, + typedWord, mAutoCorrectionThreshold, mCorrectionMode, autoText); + + if (autoText != null) { + mSuggestions.add(0, autoText); + } + + if (typedWord != null) { + mSuggestions.add(0, typedWordString); + } removeDupes(); + if (DBG) { + double normalizedScore = mAutoCorrection.getNormalizedScore(); ArrayList frequencyInfoList = new ArrayList(); frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); @@ -373,9 +358,8 @@ public class Suggest implements Dictionary.WordCallback { frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); } return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList); - } else { - return new SuggestedWords.Builder().addWords(mSuggestions, null); } + return new SuggestedWords.Builder().addWords(mSuggestions, null); } private void removeDupes() { @@ -406,7 +390,7 @@ public class Suggest implements Dictionary.WordCallback { } public boolean hasAutoCorrection() { - return mHasAutoCorrection; + return mAutoCorrection.hasAutoCorrection(); } private static boolean compareCaseInsensitive(final String lowerOriginalWord, -- cgit v1.2.3-83-g751a From c2c44f94e705e74598ec944ab51f3bd13eb50dbf Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Thu, 3 Mar 2011 16:02:28 -0800 Subject: Cleanup Suggest to be able to be unit test ready Bug: 3414081 Change-Id: Ia76afac4b1a61b8953a215b7cbdb7557736f7b9c --- .../android/inputmethod/latin/AutoCorrection.java | 19 +++- .../src/com/android/inputmethod/latin/Suggest.java | 111 ++++++++++----------- 2 files changed, 66 insertions(+), 64 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index d1154d9ed..f177f6a71 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin; import android.util.Log; import java.util.ArrayList; +import java.util.Collection; public class AutoCorrection { private static final boolean DBG = LatinImeLogger.sDBG; @@ -45,12 +46,12 @@ public class AutoCorrection { return mNormalizedScore; } - public void updateAutoCorrectionStatus(Suggest suggest, + public void updateAutoCorrectionStatus(Collection dictionaries, WordComposer wordComposer, ArrayList suggestions, int[] priorities, CharSequence typedWord, double autoCorrectionThreshold, int correctionMode, CharSequence quickFixedWord) { if (hasAutoCorrectionForTypedWord( - suggest, wordComposer, suggestions, typedWord, correctionMode)) { + dictionaries, wordComposer, suggestions, typedWord, correctionMode)) { mHasAutoCorrection = true; mAutoCorrectionWord = typedWord; } else if (hasAutoCorrectForBinaryDictionary(wordComposer, suggestions, correctionMode, @@ -63,9 +64,17 @@ public class AutoCorrection { } } - private boolean hasAutoCorrectionForTypedWord(Suggest suggest, WordComposer wordComposer, - ArrayList suggestions, CharSequence typedWord, int correctionMode) { - return wordComposer.size() > 1 && suggestions.size() > 0 && suggest.isValidWord(typedWord) + private boolean hasAutoCorrectionForTypedWord(Collection dictionaries, + WordComposer wordComposer, ArrayList suggestions, CharSequence typedWord, + int correctionMode) { + boolean isValidWord = false; + for (final Dictionary dictionary : dictionaries) { + if (dictionary.isValidWord(typedWord)) { + isValidWord = true; + break; + } + } + return wordComposer.size() > 1 && suggestions.size() > 0 && isValidWord && (correctionMode == Suggest.CORRECTION_FULL || correctionMode == Suggest.CORRECTION_FULL_BIGRAM); } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 95a2d631b..ec86a39bb 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -25,6 +25,10 @@ import android.view.View; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; /** * This class loads a dictionary and provides a list of suggestions for a given sequence of @@ -70,14 +74,13 @@ public class Suggest implements Dictionary.WordCallback { private AutoCorrection mAutoCorrection; private BinaryDictionary mMainDict; - - private Dictionary mUserDictionary; - - private Dictionary mAutoDictionary; - - private Dictionary mContactsDictionary; - - private Dictionary mUserBigramDictionary; + private static final String DICT_KEY_MAIN = "main"; + private static final String DICT_KEY_CONTACTS = "contacts"; + private static final String DICT_KEY_AUTO = "auto"; + private static final String DICT_KEY_USER = "user"; + private static final String DICT_KEY_USER_BIGRAM = "user_bigram"; + private final Map mUnigramDictionaries = new HashMap(); + private final Map mBigramDictionaries = new HashMap(); private int mPrefMaxSuggestions = 12; @@ -101,16 +104,19 @@ public class Suggest implements Dictionary.WordCallback { private int mCorrectionMode = CORRECTION_BASIC; public Suggest(Context context, int dictionaryResId) { - mMainDict = BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN); - init(); + init(BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN)); } /* package for test */ Suggest(File dictionary, long startOffset, long length) { - mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN); - init(); + init(BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN)); } - private void init() { + private void init(BinaryDictionary mainDict) { + if (mainDict != null) { + mMainDict = mainDict; + mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict); + mBigramDictionaries.put(DICT_KEY_MAIN, mainDict); + } mAutoCorrection = new AutoCorrection(); initPool(); } @@ -147,22 +153,28 @@ public class Suggest implements Dictionary.WordCallback { * before the main dictionary, if set. */ public void setUserDictionary(Dictionary userDictionary) { - mUserDictionary = userDictionary; + if (userDictionary != null) + mUnigramDictionaries.put(DICT_KEY_USER, userDictionary); } /** * Sets an optional contacts dictionary resource to be loaded. */ - public void setContactsDictionary(Dictionary userDictionary) { - mContactsDictionary = userDictionary; + public void setContactsDictionary(Dictionary contactsDictionary) { + if (contactsDictionary != null) { + mUnigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary); + mBigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary); + } } public void setAutoDictionary(Dictionary autoDictionary) { - mAutoDictionary = autoDictionary; + if (autoDictionary != null) + mUnigramDictionaries.put(DICT_KEY_AUTO, autoDictionary); } public void setUserBigramDictionary(Dictionary userBigramDictionary) { - mUserBigramDictionary = userBigramDictionary; + if (userBigramDictionary != null) + mBigramDictionaries.put(DICT_KEY_USER_BIGRAM, userBigramDictionary); } public void setAutoCorrectionThreshold(double threshold) { @@ -240,14 +252,8 @@ public class Suggest implements Dictionary.WordCallback { if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) { prevWordForBigram = lowerPrevWord; } - if (mUserBigramDictionary != null) { - mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this); - } - if (mContactsDictionary != null) { - mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this); - } - if (mMainDict != null) { - mMainDict.getBigrams(wordComposer, prevWordForBigram, this); + for (final Dictionary dictionary : mBigramDictionaries.values()) { + dictionary.getBigrams(wordComposer, prevWordForBigram, this); } char currentChar = wordComposer.getTypedWord().charAt(0); char currentCharUpper = Character.toUpperCase(currentChar); @@ -270,15 +276,13 @@ public class Suggest implements Dictionary.WordCallback { } else if (wordComposer.size() > 1) { // At second character typed, search the unigrams (scores being affected by bigrams) - if (mUserDictionary != null || mContactsDictionary != null) { - if (mUserDictionary != null) { - mUserDictionary.getWords(wordComposer, this); - } - if (mContactsDictionary != null) { - mContactsDictionary.getWords(wordComposer, this); - } + for (final String key : mUnigramDictionaries.keySet()) { + // Skip AutoDictionary to lookup + if (key.equals(DICT_KEY_AUTO)) + continue; + final Dictionary dictionary = mUnigramDictionaries.get(key); + dictionary.getWords(wordComposer, this); } - if (mMainDict != null) mMainDict.getWords(wordComposer, this); } CharSequence autoText = null; final String typedWordString = typedWord == null ? null : typedWord.toString(); @@ -324,8 +328,9 @@ public class Suggest implements Dictionary.WordCallback { } } - mAutoCorrection.updateAutoCorrectionStatus(this, wordComposer, mSuggestions, mPriorities, - typedWord, mAutoCorrectionThreshold, mCorrectionMode, autoText); + mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries.values(), wordComposer, + mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode, + autoText); if (autoText != null) { mSuggestions.add(0, autoText); @@ -515,10 +520,11 @@ public class Suggest implements Dictionary.WordCallback { if (word == null || word.length() == 0 || mMainDict == null) { return false; } - return mMainDict.isValidWord(word) - || (mUserDictionary != null && mUserDictionary.isValidWord(word)) - || (mAutoDictionary != null && mAutoDictionary.isValidWord(word)) - || (mContactsDictionary != null && mContactsDictionary.isValidWord(word)); + for (final Dictionary dictionary : mUnigramDictionaries.values()) { + if (dictionary.isValidWord(word)) + return true; + } + return false; } private void collectGarbage(ArrayList suggestions, int prefMaxSuggestions) { @@ -539,25 +545,12 @@ public class Suggest implements Dictionary.WordCallback { } public void close() { - if (mMainDict != null) { - mMainDict.close(); - mMainDict = null; - } - if (mUserDictionary != null) { - mUserDictionary.close(); - mUserDictionary = null; - } - if (mUserBigramDictionary != null) { - mUserBigramDictionary.close(); - mUserBigramDictionary = null; - } - if (mContactsDictionary != null) { - mContactsDictionary.close(); - mContactsDictionary = null; - } - if (mAutoDictionary != null) { - mAutoDictionary.close(); - mAutoDictionary = null; + final Set dictionaries = new HashSet(); + dictionaries.addAll(mUnigramDictionaries.values()); + dictionaries.addAll(mBigramDictionaries.values()); + for (final Dictionary dictionary : dictionaries) { + dictionary.close(); } + mMainDict = null; } } -- cgit v1.2.3-83-g751a From baf263caf4681052fa627abe26efc9abfef40080 Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Sat, 5 Mar 2011 09:13:40 +0900 Subject: Fix a NPE caused by a null typedWord bug: 3514797 Change-Id: Ie1056745ded1f848d1e4f70c5344d6969de509b0 --- java/src/com/android/inputmethod/latin/ExpandableDictionary.java | 2 +- java/src/com/android/inputmethod/latin/LatinIME.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index b10e7a61e..b0bbe6d52 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -225,7 +225,7 @@ public class ExpandableDictionary extends Dictionary { /** * Returns the word's frequency or -1 if not found */ - public int getWordFrequency(CharSequence word) { + protected int getWordFrequency(CharSequence word) { Node node = searchNode(mRoots, word, 0, word.length()); return (node == null) ? -1 : node.mFrequency; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 646de66c2..c66cb5f26 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1427,7 +1427,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void saveWordInHistory(CharSequence result) { if (mWord.size() <= 1) { - mWord.reset(); return; } // Skip if result is null. It happens in some edge case. -- cgit v1.2.3-83-g751a From 07a8406bc184a354ea47fb6352e48df39e35310e Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Thu, 3 Mar 2011 10:22:10 +0900 Subject: Demote skipped characters matched words with respect to length. Words that matched user input with skipped characters used to be demoted in BinaryDictionary by a constant factor and not at all in those dictionaries implemented in java code. To represent the fact that the impact of a skipped character gets larger as the word is shorter, this change will implement a demotion that gets larger as the typed word is shorter. The demotion rate is (n - 2) / (n - 1) where n is the length of the typed word for n >= 2. It implements it for both BinaryDictionary and java dictionaries. Bug: 3340731 Change-Id: I3a18be80a9708981d56a950dc25fe08f018b5b89 --- .../inputmethod/latin/ExpandableDictionary.java | 41 +++++++++++++++++----- native/src/defines.h | 2 +- native/src/unigram_dictionary.cpp | 13 +++++-- native/src/unigram_dictionary.h | 7 ++-- 4 files changed, 47 insertions(+), 16 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index b10e7a61e..f2305f18c 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -230,6 +230,16 @@ public class ExpandableDictionary extends Dictionary { return (node == null) ? -1 : node.mFrequency; } + private static int computeSkippedWordFinalFreq(int freq, int snr, int inputLength) { + // The computation itself makes sense for >= 2, but the == 2 case returns 0 + // anyway so we may as well test against 3 instead and return the constant + if (inputLength >= 3) { + return (freq * snr * (inputLength - 2)) / (inputLength - 1); + } else { + return 0; + } + } + /** * Recursively traverse the tree for words that match the input. Input consists of * a list of arrays. Each item in the list is one input character position. An input @@ -243,13 +253,14 @@ public class ExpandableDictionary extends Dictionary { * @param completion whether the traversal is now in completion mode - meaning that we've * exhausted the input and we're looking for all possible suffixes. * @param snr current weight of the word being formed - * @param inputIndex position in the input characters. This can be off from the depth in + * @param inputIndex position in the input characters. This can be off from the depth in * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type * "wouldve", it could be matching "would've", so the depth will be one more than the * inputIndex * @param callback the callback class for adding a word */ - protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word, + // TODO: Share this routine with the native code for BinaryDictionary + protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word, final int depth, boolean completion, int snr, int inputIndex, int skipPos, WordCallback callback) { final int count = roots.mLength; @@ -275,8 +286,14 @@ public class ExpandableDictionary extends Dictionary { if (completion) { word[depth] = c; if (terminal) { - if (!callback.addWord(word, 0, depth + 1, freq * snr, mDicTypeId, - DataType.UNIGRAM)) { + final int finalFreq; + if (skipPos < 0) { + finalFreq = freq * snr; + } else { + finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength); + } + if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, + DataType.UNIGRAM)) { return; } } @@ -288,7 +305,7 @@ public class ExpandableDictionary extends Dictionary { // Skip the ' and continue deeper word[depth] = c; if (children != null) { - getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, + getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, skipPos, callback); } } else { @@ -305,10 +322,16 @@ public class ExpandableDictionary extends Dictionary { if (codeSize == inputIndex + 1) { if (terminal) { - if (INCLUDE_TYPED_WORD_IF_VALID + if (INCLUDE_TYPED_WORD_IF_VALID || !same(word, depth + 1, codes.getTypedWord())) { - int finalFreq = freq * snr * addedAttenuation; - if (skipPos < 0) finalFreq *= FULL_WORD_FREQ_MULTIPLIER; + final int finalFreq; + if (skipPos < 0) { + finalFreq = freq * snr * addedAttenuation + * FULL_WORD_FREQ_MULTIPLIER; + } else { + finalFreq = computeSkippedWordFinalFreq(freq, + snr * addedAttenuation, mInputLength); + } callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, DataType.UNIGRAM); } @@ -319,7 +342,7 @@ public class ExpandableDictionary extends Dictionary { skipPos, callback); } } else if (children != null) { - getWordsRec(children, codes, word, depth + 1, + getWordsRec(children, codes, word, depth + 1, false, snr * addedAttenuation, inputIndex + 1, skipPos, callback); } diff --git a/native/src/defines.h b/native/src/defines.h index ddd65c9fa..0d1f037e1 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -135,7 +135,7 @@ static void prof_out(void) { #define SUGGEST_WORDS_WITH_TRANSPOSED_CHARACTERS true // The following "rate"s are used as a multiplier before dividing by 100, so they are in percent. -#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 75 +#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 100 #define WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE 80 #define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75 #define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75 diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index 9aa36b064..17a87a708 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -457,10 +457,17 @@ static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(con } inline int UnigramDictionary::calculateFinalFreq(const int inputIndex, const int depth, const int matchWeight, const int skipPos, const int excessivePos, const int transposedPos, - const int freq, const bool sameLength) { + const int freq, const bool sameLength) const { // TODO: Demote by edit distance int finalFreq = freq * matchWeight; - if (skipPos >= 0) multiplyRate(WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE, &finalFreq); + if (skipPos >= 0) { + if (mInputLength >= 3) { + multiplyRate(WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE * + (mInputLength - 2) / (mInputLength - 1), &finalFreq); + } else { + finalFreq = 0; + } + } if (transposedPos >= 0) multiplyRate( WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq); if (excessivePos >= 0) { @@ -514,7 +521,7 @@ inline bool UnigramDictionary::needsToSkipCurrentNode(const unsigned short c, } inline bool UnigramDictionary::existsAdjacentProximityChars(const int inputIndex, - const int inputLength) { + const int inputLength) const { if (inputIndex < 0 || inputIndex >= inputLength) return false; const int currentChar = *getInputCharsAt(inputIndex); const int leftIndex = inputIndex - 1; diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index a95984520..2912cb0b7 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -71,7 +71,8 @@ private: const int nextLettersSize); void registerNextLetter(unsigned short c, int *nextLetters, int nextLettersSize); int calculateFinalFreq(const int inputIndex, const int depth, const int snr, const int skipPos, - const int excessivePos, const int transposedPos, const int freq, const bool sameLength); + const int excessivePos, const int transposedPos, const int freq, + const bool sameLength) const; void onTerminalWhenUserTypedLengthIsGreaterThanInputLength(unsigned short *word, const int inputIndex, const int depth, const int snr, int *nextLetters, const int nextLettersSize, const int skipPos, const int excessivePos, @@ -95,8 +96,8 @@ private: bool processCurrentNodeForExactMatch(const int firstChildPos, const int startInputIndex, const int depth, unsigned short *word, int *newChildPosition, int *newCount, bool *newTerminal, int *newFreq, int *siblingPos); - bool existsAdjacentProximityChars(const int inputIndex, const int inputLength); - inline const int* getInputCharsAt(const int index) { + bool existsAdjacentProximityChars(const int inputIndex, const int inputLength) const; + inline const int* getInputCharsAt(const int index) const { return mInputCodes + (index * MAX_PROXIMITY_CHARS); } const unsigned char *DICT; -- cgit v1.2.3-83-g751a From 817e517e463cb32726ff5a62196ac8744848e29b Mon Sep 17 00:00:00 2001 From: satok Date: Fri, 4 Mar 2011 06:06:45 -0800 Subject: Add the suggestion algorithm of words with space proximity Bug: 3311719 Change-Id: Ide12a4a6280103c092fa0f563dd5b9e3f7f5c89b --- .../com/android/inputmethod/keyboard/Keyboard.java | 4 +- .../inputmethod/keyboard/ProximityInfo.java | 26 ++++----- native/src/defines.h | 3 + native/src/proximity_info.cpp | 42 ++++++++++++-- native/src/proximity_info.h | 15 +++-- native/src/unigram_dictionary.cpp | 64 ++++++++++++++++------ native/src/unigram_dictionary.h | 4 ++ 7 files changed, 113 insertions(+), 45 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 3005920b1..06d44680d 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -174,7 +174,7 @@ public class Keyboard { mDefaultHeight = mDefaultWidth; mId = id; loadKeyboard(context, xmlLayoutResId); - mProximityInfo = new ProximityInfo(mDisplayWidth, mDisplayHeight, GRID_WIDTH, GRID_HEIGHT); + mProximityInfo = new ProximityInfo(GRID_WIDTH, GRID_HEIGHT); } public int getProximityInfo() { @@ -378,7 +378,7 @@ public class Keyboard { mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell; } } - mProximityInfo.setProximityInfo(mGridNeighbors); + mProximityInfo.setProximityInfo(mGridNeighbors, getMinWidth(), getHeight(), mKeys); } public boolean isInside(Key key, int x, int y) { diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index fcada3762..80d6de952 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -18,18 +18,17 @@ package com.android.inputmethod.keyboard; import com.android.inputmethod.latin.Utils; +import java.util.Arrays; +import java.util.List; + public class ProximityInfo { public static final int MAX_PROXIMITY_CHARS_SIZE = 16; - private final int mDisplayWidth; - private final int mDisplayHeight; private final int mGridWidth; private final int mGridHeight; private final int mGridSize; - ProximityInfo(int displayWidth, int displayHeight, int gridWidth, int gridHeight) { - mDisplayWidth = displayWidth; - mDisplayHeight = displayHeight; + ProximityInfo(int gridWidth, int gridHeight) { mGridWidth = gridWidth; mGridHeight = gridHeight; mGridSize = mGridWidth * mGridHeight; @@ -43,20 +42,19 @@ public class ProximityInfo { int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray); private native void releaseProximityInfoNative(int nativeProximityInfo); - public final void setProximityInfo(int[][] gridNeighbors) { + public final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth, + int keyboardHeight, List keys) { int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; + Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE); for (int i = 0; i < mGridSize; ++i) { - final int proximityCharsLength = gridNeighbors[i].length; - for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; ++j) { - int charCode = KeyDetector.NOT_A_KEY; - if (j < proximityCharsLength) { - charCode = gridNeighbors[i][j]; - } - proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = charCode; + final int proximityCharsLength = gridNeighborKeyIndexes[i].length; + for (int j = 0; j < proximityCharsLength; ++j) { + proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = + keys.get(gridNeighborKeyIndexes[i][j]).mCode; } } mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE, - mDisplayWidth, mDisplayHeight, mGridWidth, mGridHeight, proximityCharsArray); + keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, proximityCharsArray); } // TODO: Get rid of this function's input (keyboard). diff --git a/native/src/defines.h b/native/src/defines.h index 5d85b7c51..9534f8a87 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -129,10 +129,13 @@ static void prof_out(void) { #define DICTIONARY_HEADER_SIZE 2 #define NOT_VALID_WORD -99 +#define KEYCODE_SPACE ' ' + #define SUGGEST_WORDS_WITH_MISSING_CHARACTER true #define SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER true #define SUGGEST_WORDS_WITH_EXCESSIVE_CHARACTER true #define SUGGEST_WORDS_WITH_TRANSPOSED_CHARACTERS true +#define SUGGEST_WORDS_WITH_SPACE_PROXIMITY true // The following "rate"s are used as a multiplier before dividing by 100, so they are in percent. #define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 75 diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp index d0cba3eb6..5f2d09f5c 100644 --- a/native/src/proximity_info.cpp +++ b/native/src/proximity_info.cpp @@ -17,18 +17,48 @@ #include #include +#define LOG_TAG "LatinIME: proximity_info.cpp" + #include "proximity_info.h" namespace latinime { -ProximityInfo::ProximityInfo(int maxProximityCharsSize, int displayWidth, int displayHeight, - int gridWidth, int gridHeight, uint32_t const *proximityCharsArray) - : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), DISPLAY_WIDTH(displayWidth), - DISPLAY_HEIGHT(displayHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight) { - mProximityCharsArray = new uint32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE]; - memcpy(mProximityCharsArray, proximityCharsArray, sizeof(mProximityCharsArray)); +ProximityInfo::ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth, + const int keyboardHeight, const int gridWidth, const int gridHeight, + const uint32_t *proximityCharsArray) + : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth), + KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight), + CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth), + CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight) { + const int len = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE; + mProximityCharsArray = new uint32_t[len]; + if (DEBUG_PROXIMITY_INFO) { + LOGI("Create proximity info array %d", len); + } + memcpy(mProximityCharsArray, proximityCharsArray, len * sizeof(mProximityCharsArray[0])); } ProximityInfo::~ProximityInfo() { delete[] mProximityCharsArray; } + +inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const { + return (y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH) + * MAX_PROXIMITY_CHARS_SIZE; +} + +bool ProximityInfo::hasSpaceProximity(const int x, const int y) const { + const int startIndex = getStartIndexFromCoordinates(x, y); + if (DEBUG_PROXIMITY_INFO) { + LOGI("hasSpaceProximity: index %d", startIndex); + } + for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) { + if (DEBUG_PROXIMITY_INFO) { + LOGI("Index: %d", mProximityCharsArray[startIndex + i]); + } + if (mProximityCharsArray[startIndex + i] == KEYCODE_SPACE) { + return true; + } + } + return false; } +} // namespace latinime diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h index 54e995059..0f1201866 100644 --- a/native/src/proximity_info.h +++ b/native/src/proximity_info.h @@ -25,15 +25,20 @@ namespace latinime { class ProximityInfo { public: - ProximityInfo(int maxProximityCharsSize, int displayWidth, int displayHeight, int gridWidth, - int gridHeight, uint32_t const *proximityCharsArray); + ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth, + const int keybaordHeight, const int gridWidth, const int gridHeight, + const uint32_t *proximityCharsArray); ~ProximityInfo(); + bool hasSpaceProximity(const int x, const int y) const; private: - const int MAX_PROXIMITY_CHARS_SIZE; - const int DISPLAY_WIDTH; - const int DISPLAY_HEIGHT; + int getStartIndexFromCoordinates(const int x, const int y) const; + const int CELL_WIDTH; + const int CELL_HEIGHT; + const int KEYBOARD_WIDTH; + const int KEYBOARD_HEIGHT; const int GRID_WIDTH; const int GRID_HEIGHT; + const int MAX_PROXIMITY_CHARS_SIZE; uint32_t *mProximityCharsArray; }; }; // namespace latinime diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index b82910a40..274e1f6d3 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -142,7 +142,7 @@ int UnigramDictionary::getSuggestions(const ProximityInfo *proximityInfo, const outWords, frequencies); } - PROF_START(6); + PROF_START(20); // Get the word count int suggestedWordsCount = 0; while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) { @@ -158,7 +158,7 @@ int UnigramDictionary::getSuggestions(const ProximityInfo *proximityInfo, const } } } - PROF_END(6); + PROF_END(20); PROF_CLOSE; return suggestedWordsCount; } @@ -172,12 +172,6 @@ void UnigramDictionary::getWordSuggestions(const ProximityInfo *proximityInfo, initSuggestions(codes, codesSize, outWords, frequencies); if (DEBUG_DICT) assert(codesSize == mInputLength); - if (DEBUG_PROXIMITY_INFO) { - for (int i = 0; i < codesSize; ++i) { - LOGI("Input[%d] x = %d, y = %d", i, xcoordinates[i], ycoordinates[i]); - } - } - const int MAX_DEPTH = min(mInputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH); PROF_END(0); @@ -227,6 +221,25 @@ void UnigramDictionary::getWordSuggestions(const ProximityInfo *proximityInfo, } } PROF_END(5); + + PROF_START(6); + if (SUGGEST_WORDS_WITH_SPACE_PROXIMITY) { + // The first and last "mistyped spaces" are taken care of by excessive character handling + for (int i = 1; i < codesSize - 1; ++i) { + if (DEBUG_DICT) LOGI("--- Suggest words with proximity space %d", i); + const int x = xcoordinates[i]; + const int y = ycoordinates[i]; + if (DEBUG_PROXIMITY_INFO) + LOGI("Input[%d] x = %d, y = %d, has space proximity = %d", + i, x, y, proximityInfo->hasSpaceProximity(x, y)); + + if (proximityInfo->hasSpaceProximity(x, y)) { + getMistypedSpaceWords(mInputLength, i); + } + + } + } + PROF_END(6); } void UnigramDictionary::initSuggestions(const int *codes, const int codesSize, @@ -387,27 +400,31 @@ inline static void multiplyRate(const int rate, int *freq) { } } -bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) { - if (missingSpacePos <= 0 || missingSpacePos >= inputLength - || inputLength >= MAX_WORD_LENGTH) return false; - const int newWordLength = inputLength + 1; +bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength, + const int firstWordStartPos, const int firstWordLength, const int secondWordStartPos, + const int secondWordLength) { + if (inputLength >= MAX_WORD_LENGTH) return false; + if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos + || firstWordStartPos < 0 || secondWordStartPos >= inputLength) + return false; + const int newWordLength = firstWordLength + secondWordLength + 1; // Allocating variable length array on stack unsigned short word[newWordLength]; - const int firstFreq = getBestWordFreq(0, missingSpacePos, mWord); + const int firstFreq = getBestWordFreq(firstWordStartPos, firstWordLength, mWord); if (DEBUG_DICT) LOGI("First freq: %d", firstFreq); if (firstFreq <= 0) return false; - for (int i = 0; i < missingSpacePos; ++i) { + for (int i = 0; i < firstWordLength; ++i) { word[i] = mWord[i]; } - const int secondFreq = getBestWordFreq(missingSpacePos, inputLength - missingSpacePos, mWord); + const int secondFreq = getBestWordFreq(secondWordStartPos, secondWordLength, mWord); if (DEBUG_DICT) LOGI("Second freq: %d", secondFreq); if (secondFreq <= 0) return false; - word[missingSpacePos] = SPACE; - for (int i = (missingSpacePos + 1); i < newWordLength; ++i) { - word[i] = mWord[i - missingSpacePos - 1]; + word[firstWordLength] = SPACE; + for (int i = (firstWordLength + 1); i < newWordLength; ++i) { + word[i] = mWord[i - firstWordLength - 1]; } int pairFreq = ((firstFreq + secondFreq) / 2); @@ -417,6 +434,17 @@ bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int mi return true; } +bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) { + return getSplitTwoWordsSuggestion( + inputLength, 0, missingSpacePos, missingSpacePos, inputLength - missingSpacePos); +} + +bool UnigramDictionary::getMistypedSpaceWords(const int inputLength, const int spaceProximityPos) { + return getSplitTwoWordsSuggestion( + inputLength, 0, spaceProximityPos, spaceProximityPos + 1, + inputLength - spaceProximityPos - 1); +} + // Keep this for comparing spec to new getWords void UnigramDictionary::getWordsOld(const int initialPos, const int inputLength, const int skipPos, const int excessivePos, const int transposedPos,int *nextLetters, diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index 8a4b40e0c..7359481a8 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -64,7 +64,11 @@ private: const bool traverseAllNodes, const int snr, const int inputIndex, const int diffs, const int skipPos, const int excessivePos, const int transposedPos, int *nextLetters, const int nextLettersSize); + bool getSplitTwoWordsSuggestion(const int inputLength, + const int firstWordStartPos, const int firstWordLength, + const int secondWordStartPos, const int secondWordLength); bool getMissingSpaceWords(const int inputLength, const int missingSpacePos); + bool getMistypedSpaceWords(const int inputLength, const int spaceProximityPos); // Keep getWordsOld for comparing performance between getWords and getWordsOld void getWordsOld(const int initialPos, const int inputLength, const int skipPos, const int excessivePos, const int transposedPos, int *nextLetters, -- cgit v1.2.3-83-g751a From bcfce3b3b9dbd4f5db736948b74bd820fc639a08 Mon Sep 17 00:00:00 2001 From: satok Date: Fri, 4 Mar 2011 16:56:10 +0900 Subject: Add whitelist dictionary Bug: 3447571 Change-Id: I538e4e706982129b666ca1ab1085f1b68b69f87f --- java/res/values/whitelist.xml | 38 +++++++++ .../android/inputmethod/latin/AutoCorrection.java | 71 +++++++++++----- .../android/inputmethod/latin/AutoDictionary.java | 1 + .../com/android/inputmethod/latin/LatinIME.java | 63 +++++++------- .../src/com/android/inputmethod/latin/Suggest.java | 96 +++++++++++---------- .../inputmethod/latin/WhitelistDictionary.java | 99 ++++++++++++++++++++++ .../android/inputmethod/latin/SuggestHelper.java | 3 +- 7 files changed, 278 insertions(+), 93 deletions(-) create mode 100644 java/res/values/whitelist.xml create mode 100644 java/src/com/android/inputmethod/latin/WhitelistDictionary.java (limited to 'java/src') diff --git a/java/res/values/whitelist.xml b/java/res/values/whitelist.xml new file mode 100644 index 000000000..ced52e70e --- /dev/null +++ b/java/res/values/whitelist.xml @@ -0,0 +1,38 @@ + + + + + + + 255 + ill + I\'ll + + 255 + thisd + this\'d + + + diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index f177f6a71..092f7ad63 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -16,10 +16,11 @@ package com.android.inputmethod.latin; +import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; -import java.util.Collection; +import java.util.Map; public class AutoCorrection { private static final boolean DBG = LatinImeLogger.sDBG; @@ -46,40 +47,73 @@ public class AutoCorrection { return mNormalizedScore; } - public void updateAutoCorrectionStatus(Collection dictionaries, + public void updateAutoCorrectionStatus(Map dictionaries, WordComposer wordComposer, ArrayList suggestions, int[] priorities, CharSequence typedWord, double autoCorrectionThreshold, int correctionMode, - CharSequence quickFixedWord) { - if (hasAutoCorrectionForTypedWord( + CharSequence quickFixedWord, CharSequence whitelistedWord) { + if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) { + mHasAutoCorrection = true; + mAutoCorrectionWord = whitelistedWord; + } else if (hasAutoCorrectionForTypedWord( dictionaries, wordComposer, suggestions, typedWord, correctionMode)) { mHasAutoCorrection = true; mAutoCorrectionWord = typedWord; - } else if (hasAutoCorrectForBinaryDictionary(wordComposer, suggestions, correctionMode, - priorities, typedWord, autoCorrectionThreshold)) { - mHasAutoCorrection = true; - mAutoCorrectionWord = suggestions.get(0); } else if (hasAutoCorrectionForQuickFix(quickFixedWord)) { mHasAutoCorrection = true; mAutoCorrectionWord = quickFixedWord; + } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode, + priorities, typedWord, autoCorrectionThreshold)) { + mHasAutoCorrection = true; + mAutoCorrectionWord = suggestions.get(0); } } - private boolean hasAutoCorrectionForTypedWord(Collection dictionaries, - WordComposer wordComposer, ArrayList suggestions, CharSequence typedWord, - int correctionMode) { - boolean isValidWord = false; - for (final Dictionary dictionary : dictionaries) { - if (dictionary.isValidWord(typedWord)) { - isValidWord = true; - break; + public static boolean isValidWord( + Map dictionaries, CharSequence word, boolean ignoreCase) { + if (TextUtils.isEmpty(word)) { + return false; + } + final CharSequence lowerCasedWord = word.toString().toLowerCase(); + for (final String key : dictionaries.keySet()) { + if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue; + final Dictionary dictionary = dictionaries.get(key); + if (dictionary.isValidWord(word) + || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) { + return true; } } + return false; + } + + public static boolean isValidWordForAutoCorrection( + Map dictionaries, CharSequence word, boolean ignoreCase) { + final Dictionary whiteList = dictionaries.get(Suggest.DICT_KEY_WHITELIST); + // If "word" is in the whitelist dictionary, it should not be auto corrected. + if (whiteList != null && whiteList.isValidWord(word)) { + return false; + } + return isValidWord(dictionaries, word, ignoreCase); + } + + private static boolean hasAutoCorrectionForWhitelistedWord(CharSequence whiteListedWord) { + return whiteListedWord != null; + } + + private boolean hasAutoCorrectionForTypedWord(Map dictionaries, + WordComposer wordComposer, ArrayList suggestions, CharSequence typedWord, + int correctionMode) { + if (TextUtils.isEmpty(typedWord)) return false; + boolean isValidWord = isValidWordForAutoCorrection(dictionaries, typedWord, false); return wordComposer.size() > 1 && suggestions.size() > 0 && isValidWord && (correctionMode == Suggest.CORRECTION_FULL || correctionMode == Suggest.CORRECTION_FULL_BIGRAM); } - private boolean hasAutoCorrectForBinaryDictionary(WordComposer wordComposer, + private static boolean hasAutoCorrectionForQuickFix(CharSequence quickFixedWord) { + return quickFixedWord != null; + } + + private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer, ArrayList suggestions, int correctionMode, int[] priorities, CharSequence typedWord, double autoCorrectionThreshold) { if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL @@ -106,7 +140,4 @@ public class AutoCorrection { return false; } - private boolean hasAutoCorrectionForQuickFix(CharSequence quickFixedWord) { - return quickFixedWord != null; - } } diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java index 307b81d43..a00b0915c 100644 --- a/java/src/com/android/inputmethod/latin/AutoDictionary.java +++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java @@ -27,6 +27,7 @@ import android.provider.BaseColumns; import android.util.Log; import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; import java.util.Set; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index c66cb5f26..6e76cadf2 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -834,10 +834,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) { if (DEBUG) { Log.i(TAG, "Received completions:"); - final int count = (applicationSpecifiedCompletions != null) - ? applicationSpecifiedCompletions.length : 0; - for (int i = 0; i < count; i++) { - Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]); + if (applicationSpecifiedCompletions != null) { + for (int i = 0; i < applicationSpecifiedCompletions.length; i++) { + Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]); + } } } if (mApplicationSpecifiedCompletionOn) { @@ -968,7 +968,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } mCommittedLength = mComposing.length(); TextEntryState.acceptedTyped(mComposing); - addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED); + addToAutoAndUserBigramDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED); } updateSuggestions(); } @@ -1537,10 +1537,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection(); final CharSequence typedWord = word.getTypedWord(); - // If we're in basic correct - final boolean typedWordValid = mSuggest.isValidWord(typedWord) || - (preferCapitalization() - && mSuggest.isValidWord(typedWord.toString().toLowerCase())); + // Here, we want to promote a whitelisted word if exists. + final boolean typedWordValid = AutoCorrection.isValidWordForAutoCorrection( + mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization()); if (mCorrectionMode == Suggest.CORRECTION_FULL || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { correctionAvailable |= typedWordValid; @@ -1594,7 +1593,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mJustAccepted = true; pickSuggestion(mBestWord); // Add the word to the auto dictionary if it's not a known word - addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); + addToAutoAndUserBigramDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); return true; } @@ -1647,9 +1646,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen pickSuggestion(suggestion); // Add the word to the auto dictionary if it's not a known word if (index == 0) { - addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); + addToAutoAndUserBigramDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); } else { - addToBigramDictionary(suggestion, 1); + addToOnlyBigramDictionary(suggestion, 1); } LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(), index, suggestions.mWords); @@ -1668,13 +1667,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // and correction, so we shouldn't try to show the hint // We used to look at mCorrectionMode here, but showing the hint should have nothing // to do with the autocorrection setting. - final boolean showingAddToDictionaryHint = index == 0 && - // Test for no dictionary: - ((!mHasDictionary && null != mSuggest) || - // Test for dictionary && word is inside: - (mHasDictionary && null != mSuggest - && !mSuggest.isValidWord(suggestion) - && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))); + final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null + // If there is no dictionary the hint should be shown. + && (!mHasDictionary + // If "suggestion" is not in the dictionary, the hint should be shown. + || !AutoCorrection.isValidWord( + mSuggest.getUnigramDictionaries(), suggestion, true)); if (!recorrecting) { // Fool the state watcher so that a subsequent backspace will not do a revert, unless @@ -1726,6 +1724,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // If we didn't find a match, search for result in typed word history WordComposer foundWord = null; WordAlternatives alternatives = null; + // Search old suggestions to suggest re-corrected suggestions. for (WordAlternatives entry : mWordHistory) { if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) { if (entry instanceof TypedWordAlternatives) { @@ -1735,10 +1734,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen break; } } - // If we didn't find a match, at least suggest corrections. + // If we didn't find a match, at least suggest corrections as re-corrected suggestions. if (foundWord == null - && (mSuggest.isValidWord(touching.mWord) - || mSuggest.isValidWord(touching.mWord.toString().toLowerCase()))) { + && (AutoCorrection.isValidWord( + mSuggest.getUnigramDictionaries(), touching.mWord, true))) { foundWord = new WordComposer(); for (int i = 0; i < touching.mWord.length(); i++) { foundWord.add(touching.mWord.charAt(i), new int[] { @@ -1801,21 +1800,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen setCandidatesViewShown(isCandidateStripVisible()); } - private void addToDictionaries(CharSequence suggestion, int frequencyDelta) { + private void addToAutoAndUserBigramDictionaries(CharSequence suggestion, int frequencyDelta) { checkAddToDictionary(suggestion, frequencyDelta, false); } - private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) { + private void addToOnlyBigramDictionary(CharSequence suggestion, int frequencyDelta) { checkAddToDictionary(suggestion, frequencyDelta, true); } /** * Adds to the UserBigramDictionary and/or AutoDictionary - * @param addToBigramDictionary true if it should be added to bigram dictionary if possible + * @param selectedANotTypedWord true if it should be added to bigram dictionary if possible */ private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta, - boolean addToBigramDictionary) { + boolean selectedANotTypedWord) { if (suggestion == null || suggestion.length() < 1) return; + // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be // adding words in situations where the user or application really didn't // want corrections enabled or learned. @@ -1823,9 +1823,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) { return; } - if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion) - || (!mSuggest.isValidWord(suggestion.toString()) - && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) { + + final boolean selectedATypedWordAndItsInAutoDic = + !selectedANotTypedWord && mAutoDictionary.isValidWord(suggestion); + final boolean isValidWord = AutoCorrection.isValidWord( + mSuggest.getUnigramDictionaries(), suggestion, true); + final boolean needsToAddToAutoDictionary = selectedATypedWordAndItsInAutoDic + || !isValidWord; + if (needsToAddToAutoDictionary) { mAutoDictionary.addWord(suggestion.toString(), frequencyDelta); } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index ec86a39bb..9051bf762 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -67,6 +67,13 @@ public class Suggest implements Dictionary.WordCallback { // If you add a type of dictionary, increment DIC_TYPE_LAST_ID public static final int DIC_TYPE_LAST_ID = 4; + public static final String DICT_KEY_MAIN = "main"; + public static final String DICT_KEY_CONTACTS = "contacts"; + public static final String DICT_KEY_AUTO = "auto"; + public static final String DICT_KEY_USER = "user"; + public static final String DICT_KEY_USER_BIGRAM = "user_bigram"; + public static final String DICT_KEY_WHITELIST ="whitelist"; + static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000; private static final boolean DBG = LatinImeLogger.sDBG; @@ -74,11 +81,7 @@ public class Suggest implements Dictionary.WordCallback { private AutoCorrection mAutoCorrection; private BinaryDictionary mMainDict; - private static final String DICT_KEY_MAIN = "main"; - private static final String DICT_KEY_CONTACTS = "contacts"; - private static final String DICT_KEY_AUTO = "auto"; - private static final String DICT_KEY_USER = "user"; - private static final String DICT_KEY_USER_BIGRAM = "user_bigram"; + private WhitelistDictionary mWhiteListDictionary; private final Map mUnigramDictionaries = new HashMap(); private final Map mBigramDictionaries = new HashMap(); @@ -104,19 +107,23 @@ public class Suggest implements Dictionary.WordCallback { private int mCorrectionMode = CORRECTION_BASIC; public Suggest(Context context, int dictionaryResId) { - init(BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN)); + init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN)); } /* package for test */ Suggest(File dictionary, long startOffset, long length) { - init(BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN)); + init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN)); } - private void init(BinaryDictionary mainDict) { + private void init(Context context, BinaryDictionary mainDict) { if (mainDict != null) { mMainDict = mainDict; mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict); mBigramDictionaries.put(DICT_KEY_MAIN, mainDict); } + mWhiteListDictionary = WhitelistDictionary.init(context); + if (mWhiteListDictionary != null) { + mUnigramDictionaries.put(DICT_KEY_WHITELIST, mWhiteListDictionary); + } mAutoCorrection = new AutoCorrection(); initPool(); } @@ -144,6 +151,10 @@ public class Suggest implements Dictionary.WordCallback { return mMainDict != null && mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD; } + public Map getUnigramDictionaries() { + return mUnigramDictionaries; + } + public int getApproxMaxWordLength() { return APPROX_MAX_WORD_LENGTH; } @@ -218,6 +229,25 @@ public class Suggest implements Dictionary.WordCallback { return getSuggestedWordBuilder(view, wordComposer, prevWordForBigram).build(); } + private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) { + if (TextUtils.isEmpty(word) || !(all || first)) return word; + final int wordLength = word.length(); + final int poolSize = mStringPool.size(); + final StringBuilder sb = + poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) + : new StringBuilder(getApproxMaxWordLength()); + sb.setLength(0); + if (all) { + sb.append(word.toString().toUpperCase()); + } else if (first) { + sb.append(Character.toUpperCase(word.charAt(0))); + if (wordLength > 1) { + sb.append(word.subSequence(1, wordLength)); + } + } + return sb; + } + // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer, CharSequence prevWordForBigram) { @@ -277,8 +307,8 @@ public class Suggest implements Dictionary.WordCallback { } else if (wordComposer.size() > 1) { // At second character typed, search the unigrams (scores being affected by bigrams) for (final String key : mUnigramDictionaries.keySet()) { - // Skip AutoDictionary to lookup - if (key.equals(DICT_KEY_AUTO)) + // Skip AutoDictionary and WhitelistDictionary to lookup + if (key.equals(DICT_KEY_AUTO) || key.equals(DICT_KEY_WHITELIST)) continue; final Dictionary dictionary = mUnigramDictionaries.get(key); dictionary.getWords(wordComposer, this); @@ -290,28 +320,12 @@ public class Suggest implements Dictionary.WordCallback { // Apply quick fix only for the typed word. if (mQuickFixesEnabled) { final String lowerCaseTypedWord = typedWordString.toLowerCase(); - CharSequence tempAutoText = - AutoText.get(lowerCaseTypedWord, 0, lowerCaseTypedWord.length(), view); + CharSequence tempAutoText = capitalizeWord( + mIsAllUpperCase, mIsFirstCharCapitalized, AutoText.get( + lowerCaseTypedWord, 0, lowerCaseTypedWord.length(), view)); + // TODO: cleanup canAdd // Is there an AutoText (also known as Quick Fixes) correction? // Capitalize as needed - if (!TextUtils.isEmpty(tempAutoText) - && (mIsAllUpperCase || mIsFirstCharCapitalized)) { - final int tempAutoTextLength = tempAutoText.length(); - final int poolSize = mStringPool.size(); - final StringBuilder sb = - poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) - : new StringBuilder(getApproxMaxWordLength()); - sb.setLength(0); - if (mIsAllUpperCase) { - sb.append(tempAutoText.toString().toUpperCase()); - } else if (mIsFirstCharCapitalized) { - sb.append(Character.toUpperCase(tempAutoText.charAt(0))); - if (tempAutoTextLength > 1) { - sb.append(tempAutoText.subSequence(1, tempAutoTextLength)); - } - } - tempAutoText = sb.toString(); - } boolean canAdd = tempAutoText != null; // Is that correction already the current prediction (or original word)? canAdd &= !TextUtils.equals(tempAutoText, typedWord); @@ -328,14 +342,21 @@ public class Suggest implements Dictionary.WordCallback { } } - mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries.values(), wordComposer, + CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized, + mWhiteListDictionary.getWhiteListedWord(typedWordString)); + + mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer, mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode, - autoText); + autoText, whitelistedWord); if (autoText != null) { mSuggestions.add(0, autoText); } + if (whitelistedWord != null) { + mSuggestions.add(0, whitelistedWord); + } + if (typedWord != null) { mSuggestions.add(0, typedWordString); } @@ -516,17 +537,6 @@ public class Suggest implements Dictionary.WordCallback { return -1; } - public boolean isValidWord(final CharSequence word) { - if (word == null || word.length() == 0 || mMainDict == null) { - return false; - } - for (final Dictionary dictionary : mUnigramDictionaries.values()) { - if (dictionary.isValidWord(word)) - return true; - } - return false; - } - private void collectGarbage(ArrayList suggestions, int prefMaxSuggestions) { int poolSize = mStringPool.size(); int garbageSize = suggestions.size(); diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java new file mode 100644 index 000000000..2389d4e3c --- /dev/null +++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java @@ -0,0 +1,99 @@ +/* + * 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; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import java.util.HashMap; + +public class WhitelistDictionary extends Dictionary { + + private static final boolean DBG = LatinImeLogger.sDBG; + private static final String TAG = WhitelistDictionary.class.getSimpleName(); + + private final HashMap> mWhitelistWords = + new HashMap>(); + + private static final WhitelistDictionary sInstance = new WhitelistDictionary(); + + private WhitelistDictionary() { + } + + public static WhitelistDictionary init(Context context) { + synchronized (sInstance) { + if (context != null) { + sInstance.initWordlist( + context.getResources().getStringArray(R.array.wordlist_whitelist)); + } else { + sInstance.mWhitelistWords.clear(); + } + } + return sInstance; + } + + private void initWordlist(String[] wordlist) { + mWhitelistWords.clear(); + final int N = wordlist.length; + if (N % 3 != 0) { + if (DBG) { + Log.d(TAG, "The number of the whitelist is invalid."); + } + return; + } + try { + for (int i = 0; i < N; i += 3) { + final int score = Integer.valueOf(wordlist[i]); + final String before = wordlist[i + 1]; + final String after = wordlist[i + 2]; + if (before != null && after != null) { + mWhitelistWords.put( + before.toLowerCase(), new Pair(score, after)); + } + } + } catch (NumberFormatException e) { + if (DBG) { + Log.d(TAG, "The score of the word is invalid."); + } + } + } + + public String getWhiteListedWord(String before) { + if (before == null) return null; + final String lowerCaseBefore = before.toLowerCase(); + if(mWhitelistWords.containsKey(lowerCaseBefore)) { + if (DBG) { + Log.d(TAG, "--- found whiteListedWord: " + lowerCaseBefore); + } + return mWhitelistWords.get(lowerCaseBefore).second; + } + return null; + } + + // Not used for WhitelistDictionary. We use getWhitelistedWord() in Suggest.java instead + @Override + public void getWords(WordComposer composer, WordCallback callback) { + } + + @Override + public boolean isValidWord(CharSequence word) { + if (TextUtils.isEmpty(word)) return false; + return !TextUtils.isEmpty(getWhiteListedWord(word.toString())); + } +} diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java index ed01a753b..5930ea36e 100644 --- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java +++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java @@ -89,7 +89,8 @@ public class SuggestHelper { } public boolean isValidWord(CharSequence typed) { - return mSuggest.isValidWord(typed); + return AutoCorrection.isValidWordForAutoCorrection(mSuggest.getUnigramDictionaries(), + typed, false); } // TODO: This may be slow, but is OK for test so far. -- cgit v1.2.3-83-g751a From ab0ee37f52b87756c6d91aecd508fda821aeb4b6 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Tue, 8 Mar 2011 12:11:47 -0800 Subject: Disable debug flag in TextEntryState Bug: 4051276 Change-Id: I980c0dfefdf3ae70935aca9755a55d1c925a23f9 --- java/src/com/android/inputmethod/latin/TextEntryState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java index a4e0b359b..63196430b 100644 --- a/java/src/com/android/inputmethod/latin/TextEntryState.java +++ b/java/src/com/android/inputmethod/latin/TextEntryState.java @@ -20,7 +20,7 @@ import android.util.Log; public class TextEntryState { private static final String TAG = TextEntryState.class.getSimpleName(); - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final int UNKNOWN = 0; private static final int START = 1; -- cgit v1.2.3-83-g751a From d631651b1291aef52bdd6ea7caaf9b95c9704506 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Tue, 15 Mar 2011 15:56:29 -0700 Subject: Avoid the removal of high-ranking exactly typed candidates. It used to be the case that the scoring system turns up the same word that was entered with a different capitalization, but with a lower score than some other, more frequent word. To cope with this, there was code that would order such candidates in the first slot no matter what. This processing is now useless because fully matching words now have a huge boost that ensures they will get to the top of the list, before any non-fully matching word (which means, differing only by capitalization or accents). The bug that did happen with this was, if a fully-matching word got matched by several processing passes, and the (chronologically) later score affected to this word was weaker, it would result in the duplicate removal pass removing the stronger score. This in turn would mess with autocorrect. In an effort to keep the risk at a minimum for MR1, this change does not actually remove the useless code, but adds a check in the odd case to avoid the bad situation. Another change will remove the code for ICS release. bug: 4100269 Change-Id: I18c0575332981ffec0e257e26a360995838d521e --- java/src/com/android/inputmethod/latin/Suggest.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 9051bf762..0de474e59 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -454,7 +454,20 @@ public class Suggest implements Dictionary.WordCallback { // Check if it's the same word, only caps are different if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) { - pos = 0; + // TODO: remove this surrounding if clause and move this logic to + // getSuggestedWordBuilder. + if (suggestions.size() > 0) { + final String currentHighestWordLowerCase = + suggestions.get(0).toString().toLowerCase(); + // If the current highest word is also equal to typed word, we need to compare + // frequency to determine the insertion position. This does not ensure strictly + // correct ordering, but ensures the top score is on top which is enough for + // removing duplicates correctly. + if (compareCaseInsensitive(currentHighestWordLowerCase, word, offset, length) + && freq <= priorities[0]) { + pos = 1; + } + } } else { if (dataType == Dictionary.DataType.UNIGRAM) { // Check if the word was already added before (by bigram data) -- cgit v1.2.3-83-g751a From e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Tue, 15 Mar 2011 11:46:32 -0700 Subject: Add Utils.equalsIgnoreCase methods This change also corrects usage of "frequency", "priority" and "score" * Frequency is the relative probability in dictionary. * Score is the relative probability in suggestions. * Priority is kind a sorted score. Change-Id: Iafb135a4ecdb401cc505014a07c74dfcac44d699 --- java/proguard.flags | 4 + .../android/inputmethod/latin/AutoCorrection.java | 10 +-- .../inputmethod/latin/BinaryDictionary.java | 30 +++---- .../com/android/inputmethod/latin/Dictionary.java | 8 +- .../inputmethod/latin/ExpandableDictionary.java | 2 +- .../src/com/android/inputmethod/latin/Suggest.java | 100 +++++++++------------ java/src/com/android/inputmethod/latin/Utils.java | 66 +++++++++++++- 7 files changed, 134 insertions(+), 86 deletions(-) (limited to 'java/src') diff --git a/java/proguard.flags b/java/proguard.flags index 729f4ad61..914bd7595 100644 --- a/java/proguard.flags +++ b/java/proguard.flags @@ -18,3 +18,7 @@ -keep class com.android.inputmethod.latin.AutoCorrection { java.lang.CharSequence getAutoCorrectionWord(); } + +-keep class com.android.inputmethod.latin.Utils { + boolean equalsIgnoreCase(...); +} diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index 092f7ad63..d3119792c 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -48,7 +48,7 @@ public class AutoCorrection { } public void updateAutoCorrectionStatus(Map dictionaries, - WordComposer wordComposer, ArrayList suggestions, int[] priorities, + WordComposer wordComposer, ArrayList suggestions, int[] sortedScores, CharSequence typedWord, double autoCorrectionThreshold, int correctionMode, CharSequence quickFixedWord, CharSequence whitelistedWord) { if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) { @@ -62,7 +62,7 @@ public class AutoCorrection { mHasAutoCorrection = true; mAutoCorrectionWord = quickFixedWord; } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode, - priorities, typedWord, autoCorrectionThreshold)) { + sortedScores, typedWord, autoCorrectionThreshold)) { mHasAutoCorrection = true; mAutoCorrectionWord = suggestions.get(0); } @@ -114,13 +114,13 @@ public class AutoCorrection { } private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer, - ArrayList suggestions, int correctionMode, int[] priorities, + ArrayList suggestions, int correctionMode, int[] sortedScores, CharSequence typedWord, double autoCorrectionThreshold) { if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL || correctionMode == Suggest.CORRECTION_FULL_BIGRAM) - && typedWord != null && suggestions.size() > 0 && priorities.length > 0) { + && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) { final CharSequence autoCorrectionCandidate = suggestions.get(0); - final int autoCorrectionCandidateScore = priorities[0]; + final int autoCorrectionCandidateScore = sortedScores[0]; // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. mNormalizedScore = Utils.calcNormalizedScore( diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 08ddd25fa..c5491af47 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -54,8 +54,8 @@ public class BinaryDictionary extends Dictionary { private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE]; private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; - private final int[] mFrequencies = new int[MAX_WORDS]; - private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS]; + private final int[] mScores = new int[MAX_WORDS]; + private final int[] mBigramScores = new int[MAX_BIGRAMS]; private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance(); private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance(); @@ -149,14 +149,14 @@ public class BinaryDictionary extends Dictionary { private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates, int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars, - int[] frequencies); + int[] scores); private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, - int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies, + int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores, int maxWordLength, int maxBigrams, int maxAlternatives); private final void loadDictionary(String path, long startOffset, long length) { mNativeDict = openNative(path, startOffset, length, - TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER, + TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE); mDictLength = length; } @@ -168,7 +168,7 @@ public class BinaryDictionary extends Dictionary { char[] chars = previousWord.toString().toCharArray(); Arrays.fill(mOutputChars_bigrams, (char) 0); - Arrays.fill(mFrequencies_bigrams, 0); + Arrays.fill(mBigramScores, 0); int codesSize = codes.size(); Arrays.fill(mInputCodes, -1); @@ -177,18 +177,18 @@ public class BinaryDictionary extends Dictionary { Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize, - mOutputChars_bigrams, mFrequencies_bigrams, MAX_WORD_LENGTH, MAX_BIGRAMS, + mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS, MAX_PROXIMITY_CHARS_SIZE); for (int j = 0; j < count; ++j) { - if (mFrequencies_bigrams[j] < 1) break; + if (mBigramScores[j] < 1) break; final int start = j * MAX_WORD_LENGTH; int len = 0; while (len < MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) { ++len; } if (len > 0) { - callback.addWord(mOutputChars_bigrams, start, len, mFrequencies_bigrams[j], + callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j], mDicTypeId, DataType.BIGRAM); } } @@ -197,17 +197,17 @@ public class BinaryDictionary extends Dictionary { @Override public void getWords(final WordComposer codes, final WordCallback callback) { final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(), - mOutputChars, mFrequencies); + mOutputChars, mScores); for (int j = 0; j < count; ++j) { - if (mFrequencies[j] < 1) break; + if (mScores[j] < 1) break; final int start = j * MAX_WORD_LENGTH; int len = 0; while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { ++len; } if (len > 0) { - callback.addWord(mOutputChars, start, len, mFrequencies[j], mDicTypeId, + callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId, DataType.UNIGRAM); } } @@ -218,7 +218,7 @@ public class BinaryDictionary extends Dictionary { } /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard, - char[] outputChars, int[] frequencies) { + char[] outputChars, int[] scores) { if (!isValidDictionary()) return -1; final int codesSize = codes.size(); @@ -232,12 +232,12 @@ public class BinaryDictionary extends Dictionary { Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); } Arrays.fill(outputChars, (char) 0); - Arrays.fill(frequencies, 0); + Arrays.fill(scores, 0); return getSuggestionsNative( mNativeDict, keyboard.getProximityInfo(), codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, - mFlags, outputChars, frequencies); + mFlags, outputChars, scores); } @Override diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 56f0cc503..ac43d6477 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -29,7 +29,7 @@ public abstract class Dictionary { /** * The weight to give to a word if it's length is the same as the number of typed characters. */ - protected static final int FULL_WORD_FREQ_MULTIPLIER = 2; + protected static final int FULL_WORD_SCORE_MULTIPLIER = 2; public static enum DataType { UNIGRAM, BIGRAM @@ -42,17 +42,17 @@ public abstract class Dictionary { public interface WordCallback { /** * Adds a word to a list of suggestions. The word is expected to be ordered based on - * the provided frequency. + * the provided score. * @param word the character array containing the word * @param wordOffset starting offset of the word in the character array * @param wordLength length of valid characters in the character array - * @param frequency the frequency of occurrence. This is normalized between 1 and 255, but + * @param score the score of occurrence. This is normalized between 1 and 255, but * can exceed those limits * @param dicTypeId of the dictionary where word was from * @param dataType tells type of this data * @return true if the word was added, false if no more words are required */ - boolean addWord(char[] word, int wordOffset, int wordLength, int frequency, int dicTypeId, + boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId, DataType dataType); } diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 0318175f6..d87fbce51 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -327,7 +327,7 @@ public class ExpandableDictionary extends Dictionary { final int finalFreq; if (skipPos < 0) { finalFreq = freq * snr * addedAttenuation - * FULL_WORD_FREQ_MULTIPLIER; + * FULL_WORD_SCORE_MULTIPLIER; } else { finalFreq = computeSkippedWordFinalFreq(freq, snr * addedAttenuation, mInputLength); diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 0de474e59..94ee2c50f 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -47,7 +47,7 @@ public class Suggest implements Dictionary.WordCallback { /** * Words that appear in both bigram and unigram data gets multiplier ranging from - * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the frequency score from + * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the score from * bigram data. */ public static final double BIGRAM_MULTIPLIER_MIN = 1.2; @@ -92,13 +92,13 @@ public class Suggest implements Dictionary.WordCallback { private boolean mQuickFixesEnabled; private double mAutoCorrectionThreshold; - private int[] mPriorities = new int[mPrefMaxSuggestions]; - private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS]; + private int[] mScores = new int[mPrefMaxSuggestions]; + private int[] mBigramScores = new int[PREF_MAX_BIGRAMS]; private ArrayList mSuggestions = new ArrayList(); ArrayList mBigramSuggestions = new ArrayList(); private ArrayList mStringPool = new ArrayList(); - private String mLowerOriginalWord; + private CharSequence mTypedWord; // TODO: Remove these member variables by passing more context to addWord() callback method private boolean mIsFirstCharCapitalized; @@ -207,8 +207,8 @@ public class Suggest implements Dictionary.WordCallback { throw new IllegalArgumentException("maxSuggestions must be between 1 and 100"); } mPrefMaxSuggestions = maxSuggestions; - mPriorities = new int[mPrefMaxSuggestions]; - mBigramPriorities = new int[PREF_MAX_BIGRAMS]; + mScores = new int[mPrefMaxSuggestions]; + mBigramScores = new int[PREF_MAX_BIGRAMS]; collectGarbage(mSuggestions, mPrefMaxSuggestions); while (mStringPool.size() < mPrefMaxSuggestions) { StringBuilder sb = new StringBuilder(getApproxMaxWordLength()); @@ -256,25 +256,23 @@ public class Suggest implements Dictionary.WordCallback { mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsAllUpperCase = wordComposer.isAllUpperCase(); collectGarbage(mSuggestions, mPrefMaxSuggestions); - Arrays.fill(mPriorities, 0); + Arrays.fill(mScores, 0); // Save a lowercase version of the original word CharSequence typedWord = wordComposer.getTypedWord(); if (typedWord != null) { final String typedWordString = typedWord.toString(); typedWord = typedWordString; - mLowerOriginalWord = typedWordString.toLowerCase(); // Treating USER_TYPED as UNIGRAM suggestion for logging now. LatinImeLogger.onAddSuggestedWord(typedWordString, Suggest.DIC_USER_TYPED, Dictionary.DataType.UNIGRAM); - } else { - mLowerOriginalWord = ""; } + mTypedWord = typedWord; if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM || mCorrectionMode == CORRECTION_BASIC)) { // At first character typed, search only the bigrams - Arrays.fill(mBigramPriorities, 0); + Arrays.fill(mBigramScores, 0); collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS); if (!TextUtils.isEmpty(prevWordForBigram)) { @@ -346,7 +344,7 @@ public class Suggest implements Dictionary.WordCallback { mWhiteListDictionary.getWhiteListedWord(typedWordString)); mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer, - mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode, + mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode, autoText, whitelistedWord); if (autoText != null) { @@ -364,26 +362,25 @@ public class Suggest implements Dictionary.WordCallback { if (DBG) { double normalizedScore = mAutoCorrection.getNormalizedScore(); - ArrayList frequencyInfoList = + ArrayList scoreInfoList = new ArrayList(); - frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); - final int priorityLength = mPriorities.length; - for (int i = 0; i < priorityLength; ++i) { + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); + for (int i = 0; i < mScores.length; ++i) { if (normalizedScore > 0) { - final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" + + final String scoreThreshold = Integer.toString(mScores[i]) + " (" + normalizedScore + ")"; - frequencyInfoList.add( - new SuggestedWords.SuggestedWordInfo(priorityThreshold, false)); + scoreInfoList.add( + new SuggestedWords.SuggestedWordInfo(scoreThreshold, false)); normalizedScore = 0.0; } else { - final String priority = Integer.toString(mPriorities[i]); - frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false)); + final String score = Integer.toString(mScores[i]); + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false)); } } - for (int i = priorityLength; i < mSuggestions.size(); ++i) { - frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); + for (int i = mScores.length; i < mSuggestions.size(); ++i) { + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); } - return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList); + return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList); } return new SuggestedWords.Builder().addWords(mSuggestions, null); } @@ -419,52 +416,37 @@ public class Suggest implements Dictionary.WordCallback { return mAutoCorrection.hasAutoCorrection(); } - private static boolean compareCaseInsensitive(final String lowerOriginalWord, - final char[] word, final int offset, final int length) { - final int originalLength = lowerOriginalWord.length(); - if (originalLength == length && Character.isUpperCase(word[offset])) { - for (int i = 0; i < originalLength; i++) { - if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { - return false; - } - } - return true; - } - return false; - } - @Override - public boolean addWord(final char[] word, final int offset, final int length, int freq, + public boolean addWord(final char[] word, final int offset, final int length, int score, final int dicTypeId, final Dictionary.DataType dataType) { Dictionary.DataType dataTypeForLog = dataType; - ArrayList suggestions; - int[] priorities; - int prefMaxSuggestions; + final ArrayList suggestions; + final int[] sortedScores; + final int prefMaxSuggestions; if(dataType == Dictionary.DataType.BIGRAM) { suggestions = mBigramSuggestions; - priorities = mBigramPriorities; + sortedScores = mBigramScores; prefMaxSuggestions = PREF_MAX_BIGRAMS; } else { suggestions = mSuggestions; - priorities = mPriorities; + sortedScores = mScores; prefMaxSuggestions = mPrefMaxSuggestions; } int pos = 0; // Check if it's the same word, only caps are different - if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) { + if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) { // TODO: remove this surrounding if clause and move this logic to // getSuggestedWordBuilder. if (suggestions.size() > 0) { - final String currentHighestWordLowerCase = - suggestions.get(0).toString().toLowerCase(); + final String currentHighestWord = suggestions.get(0).toString(); // If the current highest word is also equal to typed word, we need to compare // frequency to determine the insertion position. This does not ensure strictly // correct ordering, but ensures the top score is on top which is enough for // removing duplicates correctly. - if (compareCaseInsensitive(currentHighestWordLowerCase, word, offset, length) - && freq <= priorities[0]) { + if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length) + && score <= sortedScores[0]) { pos = 1; } } @@ -475,24 +457,24 @@ public class Suggest implements Dictionary.WordCallback { if(bigramSuggestion >= 0) { dataTypeForLog = Dictionary.DataType.BIGRAM; // turn freq from bigram into multiplier specified above - double multiplier = (((double) mBigramPriorities[bigramSuggestion]) + double multiplier = (((double) mBigramScores[bigramSuggestion]) / MAXIMUM_BIGRAM_FREQUENCY) * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN) + BIGRAM_MULTIPLIER_MIN; /* Log.d(TAG,"bigram num: " + bigramSuggestion + " wordB: " + mBigramSuggestions.get(bigramSuggestion).toString() - + " currentPriority: " + freq + " bigramPriority: " - + mBigramPriorities[bigramSuggestion] + + " currentScore: " + score + " bigramScore: " + + mBigramScores[bigramSuggestion] + " multiplier: " + multiplier); */ - freq = (int)Math.round((freq * multiplier)); + score = (int)Math.round((score * multiplier)); } } - // Check the last one's priority and bail - if (priorities[prefMaxSuggestions - 1] >= freq) return true; + // Check the last one's score and bail + if (sortedScores[prefMaxSuggestions - 1] >= score) return true; while (pos < prefMaxSuggestions) { - if (priorities[pos] < freq - || (priorities[pos] == freq && length < suggestions.get(pos).length())) { + if (sortedScores[pos] < score + || (sortedScores[pos] == score && length < suggestions.get(pos).length())) { break; } pos++; @@ -502,8 +484,8 @@ public class Suggest implements Dictionary.WordCallback { return true; } - System.arraycopy(priorities, pos, priorities, pos + 1, prefMaxSuggestions - pos - 1); - priorities[pos] = freq; + System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1); + sortedScores[pos] = score; int poolSize = mStringPool.size(); StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 727e3f16d..608d7e967 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -285,7 +285,7 @@ public class Utils { // In dictionary.cpp, getSuggestion() method, // suggestion scores are computed using the below formula. - // original score (called 'frequency') + // original score // := pow(mTypedLetterMultiplier (this is defined 2), // (the number of matched characters between typed word and suggested word)) // * (individual word's score which defined in the unigram dictionary, @@ -295,7 +295,7 @@ public class Utils { // (full match up to min(before.length(), after.length()) // => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2) // - If the word is a true full match except for differences in accents or - // capitalization, then treat it as if the frequency was 255. + // capitalization, then treat it as if the score was 255. // - If before.length() == after.length() // => multiply by mFullWordMultiplier (this is defined 2)) // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2 @@ -561,4 +561,66 @@ public class Utils { Log.e(TAG, "Could not load native library jni_latinime"); } } + + /** + * Returns true if a and b are equal ignoring the case of the character. + * @param a first character to check + * @param b second character to check + * @return {@code true} if a and b are equal, {@code false} otherwise. + */ + public static boolean equalsIgnoreCase(char a, char b) { + // Some language, such as Turkish, need testing both cases. + return a == b + || Character.toLowerCase(a) == Character.toLowerCase(b) + || Character.toUpperCase(a) == Character.toUpperCase(b); + } + + /** + * Returns true if a and b are equal ignoring the case of the characters, including if they are + * both null. + * @param a first CharSequence to check + * @param b second CharSequence to check + * @return {@code true} if a and b are equal, {@code false} otherwise. + */ + public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) { + if (a == b) + return true; // including both a and b are null. + if (a == null || b == null) + return false; + final int length = a.length(); + if (length != b.length()) + return false; + for (int i = 0; i < length; i++) { + if (!equalsIgnoreCase(a.charAt(i), b.charAt(i))) + return false; + } + return true; + } + + /** + * Returns true if a and b are equal ignoring the case of the characters, including if a is null + * and b is zero length. + * @param a CharSequence to check + * @param b character array to check + * @param offset start offset of array b + * @param length length of characters in array b + * @return {@code true} if a and b are equal, {@code false} otherwise. + * @throws IndexOutOfBoundsException + * if {@code offset < 0 || length < 0 || offset + length > data.length}. + * @throws NullPointerException if {@code b == null}. + */ + public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) { + if (offset < 0 || length < 0 || length > b.length - offset) + throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset + + " length=" + length); + if (a == null) + return length == 0; // including a is null and b is zero length. + if (a.length() != length) + return false; + for (int i = 0; i < length; i++) { + if (!equalsIgnoreCase(a.charAt(i), b[offset + i])) + return false; + } + return true; + } } -- cgit v1.2.3-83-g751a From 071f47140cec02197de5e163f45c77990b39457d Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Mon, 14 Mar 2011 17:23:26 -0700 Subject: Refactor BinaryDictionary a bit This changes: * Flag initialization code in BinaryDictionary to be more unit test friendly. * Removing unnecessary class hierarchy of LatinIME inner class WordAlternatives. * Formatting normalized score neatly. Change-Id: I9f10c093d4f36258fe883cf3fb10cbbda2770497 --- .../inputmethod/latin/BinaryDictionary.java | 48 +++++++++++---------- .../com/android/inputmethod/latin/LatinIME.java | 50 +++++++--------------- .../src/com/android/inputmethod/latin/Suggest.java | 10 +++-- .../android/inputmethod/latin/SuggestHelper.java | 2 +- 4 files changed, 49 insertions(+), 61 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index c5491af47..ca7261b43 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -58,24 +58,27 @@ public class BinaryDictionary extends Dictionary { private final int[] mBigramScores = new int[MAX_BIGRAMS]; private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance(); - private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance(); - - private static class Flags { - private static class FlagEntry { - public final String mName; - public final int mValue; - public FlagEntry(String name, int value) { - mName = name; - mValue = value; - } + + public static class Flag { + public final String mName; + public final int mValue; + + public Flag(String name, int value) { + mName = name; + mValue = value; } - public static final FlagEntry[] ALL_FLAGS = { - // Here should reside all flags that trigger some special processing - // These *must* match the definition in UnigramDictionary enum in - // unigram_dictionary.h so please update both at the same time. - new FlagEntry("requiresGermanUmlautProcessing", 0x1) - }; } + + public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING = + new Flag("requiresGermanUmlautProcessing", 0x1); + + private static final Flag[] ALL_FLAGS = { + // Here should reside all flags that trigger some special processing + // These *must* match the definition in UnigramDictionary enum in + // unigram_dictionary.h so please update both at the same time. + FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING, + }; + private int mFlags = 0; private BinaryDictionary() { @@ -110,12 +113,12 @@ public class BinaryDictionary extends Dictionary { return null; } } - sInstance.initFlags(); + sInstance.mFlags = initFlags(ALL_FLAGS, SubtypeSwitcher.getInstance()); return sInstance; } /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset, - long length, int dicTypeId) { + long length, int dicTypeId, Flag[] flagArray) { synchronized (sInstance) { sInstance.closeInternal(); if (dictionary.isFile()) { @@ -126,16 +129,17 @@ public class BinaryDictionary extends Dictionary { return null; } } + sInstance.mFlags = initFlags(flagArray, null); return sInstance; } - private void initFlags() { + private static int initFlags(Flag[] flagArray, SubtypeSwitcher switcher) { int flags = 0; - for (Flags.FlagEntry entry : Flags.ALL_FLAGS) { - if (mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(entry.mName)) + for (Flag entry : flagArray) { + if (switcher == null || switcher.currentSubtypeContainsExtraValueKey(entry.mName)) flags |= entry.mValue; } - mFlags = flags; + return flags; } static { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 6e76cadf2..75391a9cd 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -212,51 +212,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private final ArrayList mWordHistory = new ArrayList(); - public abstract static class WordAlternatives { - protected CharSequence mChosenWord; + public class WordAlternatives { + private final CharSequence mChosenWord; + private final WordComposer mWordComposer; - public WordAlternatives() { - // Nothing - } - - public WordAlternatives(CharSequence chosenWord) { + public WordAlternatives(CharSequence chosenWord, WordComposer wordComposer) { mChosenWord = chosenWord; + mWordComposer = wordComposer; } - @Override - public int hashCode() { - return mChosenWord.hashCode(); - } - - public abstract CharSequence getOriginalWord(); - public CharSequence getChosenWord() { return mChosenWord; } - public abstract SuggestedWords.Builder getAlternatives(); - } - - public class TypedWordAlternatives extends WordAlternatives { - private WordComposer word; - - public TypedWordAlternatives() { - // Nothing + public CharSequence getOriginalWord() { + return mWordComposer.getTypedWord(); } - public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) { - super(chosenWord); - word = wordComposer; + public SuggestedWords.Builder getAlternatives() { + return getTypedSuggestions(mWordComposer); } @Override - public CharSequence getOriginalWord() { - return word.getTypedWord(); + public int hashCode() { + return mChosenWord.hashCode(); } @Override - public SuggestedWords.Builder getAlternatives() { - return getTypedSuggestions(word); + public boolean equals(Object o) { + return o instanceof CharSequence && TextUtils.equals(mChosenWord, (CharSequence)o); } } @@ -1436,7 +1420,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Make a copy of the CharSequence, since it is/could be a mutable CharSequence final String resultCopy = result.toString(); - TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy, + WordAlternatives entry = new WordAlternatives(resultCopy, new WordComposer(mWord)); mWordHistory.add(entry); } @@ -1727,9 +1711,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Search old suggestions to suggest re-corrected suggestions. for (WordAlternatives entry : mWordHistory) { if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) { - if (entry instanceof TypedWordAlternatives) { - foundWord = ((TypedWordAlternatives) entry).word; - } + foundWord = entry.mWordComposer; alternatives = entry; break; } @@ -1749,7 +1731,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Found a match, show suggestions if (foundWord != null || alternatives != null) { if (alternatives == null) { - alternatives = new TypedWordAlternatives(touching.mWord, foundWord); + alternatives = new WordAlternatives(touching.mWord, foundWord); } showCorrections(alternatives); if (foundWord != null) { diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 94ee2c50f..a61a7f159 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -110,8 +110,10 @@ public class Suggest implements Dictionary.WordCallback { init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN)); } - /* package for test */ Suggest(File dictionary, long startOffset, long length) { - init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN)); + /* package for test */ Suggest(File dictionary, long startOffset, long length, + BinaryDictionary.Flag[] flagArray) { + init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN, + flagArray)); } private void init(Context context, BinaryDictionary mainDict) { @@ -367,8 +369,8 @@ public class Suggest implements Dictionary.WordCallback { scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); for (int i = 0; i < mScores.length; ++i) { if (normalizedScore > 0) { - final String scoreThreshold = Integer.toString(mScores[i]) + " (" + - normalizedScore + ")"; + final String scoreThreshold = String.format("%d (%4.2f)", mScores[i], + normalizedScore); scoreInfoList.add( new SuggestedWords.SuggestedWordInfo(scoreThreshold, false)); normalizedScore = 0.0; diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java index 5930ea36e..aa7b76cc3 100644 --- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java +++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java @@ -42,7 +42,7 @@ public class SuggestHelper { protected SuggestHelper(Context context, File dictionaryPath, long startOffset, long length, KeyboardId keyboardId) { - mSuggest = new Suggest(dictionaryPath, startOffset, length); + mSuggest = new Suggest(dictionaryPath, startOffset, length, null); mKeyboard = new LatinKeyboard(context, keyboardId); mKeyDetector = new ProximityKeyDetector(); init(); -- cgit v1.2.3-83-g751a From 9807ab27eac3a10b299382af8280eb54dca50608 Mon Sep 17 00:00:00 2001 From: satok Date: Wed, 16 Mar 2011 14:57:08 -0700 Subject: (Refactor 1) Moved voice related codes to deprecated/voice Change-Id: I008ac7099c815fb74a9ab374419617b336453f97 --- java/res/layout/recognition_status.xml | 2 +- .../inputmethod/deprecated/VoiceConnector.java | 821 +++++++++++++++++++++ .../inputmethod/deprecated/voice/FieldContext.java | 104 +++ .../inputmethod/deprecated/voice/Hints.java | 188 +++++ .../deprecated/voice/RecognitionView.java | 355 +++++++++ .../inputmethod/deprecated/voice/SettingsUtil.java | 110 +++ .../deprecated/voice/SoundIndicator.java | 155 ++++ .../inputmethod/deprecated/voice/VoiceInput.java | 685 +++++++++++++++++ .../deprecated/voice/VoiceInputLogger.java | 267 +++++++ .../deprecated/voice/WaveformImage.java | 92 +++ .../inputmethod/deprecated/voice/Whitelist.java | 68 ++ .../inputmethod/keyboard/LatinKeyboardView.java | 4 +- .../com/android/inputmethod/latin/LatinIME.java | 8 +- .../com/android/inputmethod/latin/Settings.java | 23 +- .../android/inputmethod/latin/SubtypeSwitcher.java | 50 +- .../android/inputmethod/voice/FieldContext.java | 104 --- java/src/com/android/inputmethod/voice/Hints.java | 188 ----- .../android/inputmethod/voice/RecognitionView.java | 355 --------- .../android/inputmethod/voice/SettingsUtil.java | 110 --- .../android/inputmethod/voice/SoundIndicator.java | 155 ---- .../inputmethod/voice/VoiceIMEConnector.java | 728 ------------------ .../com/android/inputmethod/voice/VoiceInput.java | 685 ----------------- .../inputmethod/voice/VoiceInputLogger.java | 267 ------- .../android/inputmethod/voice/WaveformImage.java | 92 --- .../com/android/inputmethod/voice/Whitelist.java | 68 -- 25 files changed, 2877 insertions(+), 2807 deletions(-) create mode 100644 java/src/com/android/inputmethod/deprecated/VoiceConnector.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/FieldContext.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/Hints.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java create mode 100644 java/src/com/android/inputmethod/deprecated/voice/Whitelist.java delete mode 100644 java/src/com/android/inputmethod/voice/FieldContext.java delete mode 100644 java/src/com/android/inputmethod/voice/Hints.java delete mode 100644 java/src/com/android/inputmethod/voice/RecognitionView.java delete mode 100644 java/src/com/android/inputmethod/voice/SettingsUtil.java delete mode 100644 java/src/com/android/inputmethod/voice/SoundIndicator.java delete mode 100644 java/src/com/android/inputmethod/voice/VoiceIMEConnector.java delete mode 100644 java/src/com/android/inputmethod/voice/VoiceInput.java delete mode 100644 java/src/com/android/inputmethod/voice/VoiceInputLogger.java delete mode 100644 java/src/com/android/inputmethod/voice/WaveformImage.java delete mode 100644 java/src/com/android/inputmethod/voice/Whitelist.java (limited to 'java/src') diff --git a/java/res/layout/recognition_status.xml b/java/res/layout/recognition_status.xml index 9474d6f58..45f68974a 100644 --- a/java/res/layout/recognition_status.xml +++ b/java/res/layout/recognition_status.xml @@ -45,7 +45,7 @@ android:layout_height="0dip" android:layout_width="match_parent" android:layout_weight="1.0"> - > mWordToSuggestions = + new HashMap>(); + + public static VoiceConnector init(LatinIME context, SharedPreferences prefs, UIHandler h) { + sInstance.initInternal(context, prefs, h); + return sInstance; + } + + public static VoiceConnector getInstance() { + return sInstance; + } + + private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) { + mService = service; + mHandler = h; + mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); + mSubtypeSwitcher = SubtypeSwitcher.getInstance(); + if (VOICE_INSTALLED) { + mVoiceInput = new VoiceInput(service, this); + mHints = new Hints(service, prefs, new Hints.Display() { + @Override + public void showHint(int viewResource) { + View view = LayoutInflater.from(mService).inflate(viewResource, null); + mService.setCandidatesView(view); + mService.setCandidatesViewShown(true); + mIsShowingHint = true; + } + }); + } + } + + private VoiceConnector() { + // Intentional empty constructor for singleton. + } + + public void resetVoiceStates(boolean isPasswordText) { + mAfterVoiceInput = false; + mImmediatelyAfterVoiceInput = false; + mShowingVoiceSuggestions = false; + mVoiceInputHighlighted = false; + mPasswordText = isPasswordText; + } + + public void flushVoiceInputLogs(boolean configurationChanged) { + if (VOICE_INSTALLED && !configurationChanged) { + if (mAfterVoiceInput) { + mVoiceInput.flushAllTextModificationCounters(); + mVoiceInput.logInputEnded(); + } + mVoiceInput.flushLogs(); + mVoiceInput.cancel(); + } + } + + public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion, + String wordSeparators) { + if (mAfterVoiceInput && mShowingVoiceSuggestions) { + mVoiceInput.flushAllTextModificationCounters(); + // send this intent AFTER logging any prior aggregated edits. + mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index, + wordSeparators, mService.getCurrentInputConnection()); + } + } + + private void showVoiceWarningDialog(final boolean swipe, IBinder token) { + if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { + return; + } + AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService); + builder.setCancelable(true); + builder.setIcon(R.drawable.ic_mic_dialog); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + mVoiceInput.logKeyboardWarningDialogOk(); + reallyStartListening(swipe); + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + mVoiceInput.logKeyboardWarningDialogCancel(); + switchToLastInputMethod(); + } + }); + // When the dialog is dismissed by user's cancellation, switch back to the last input method + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface arg0) { + mVoiceInput.logKeyboardWarningDialogCancel(); + switchToLastInputMethod(); + } + }); + + final CharSequence message; + if (mLocaleSupportedForVoiceInput) { + message = TextUtils.concat( + mService.getText(R.string.voice_warning_may_not_understand), "\n\n", + mService.getText(R.string.voice_warning_how_to_turn_off)); + } else { + message = TextUtils.concat( + mService.getText(R.string.voice_warning_locale_not_supported), "\n\n", + mService.getText(R.string.voice_warning_may_not_understand), "\n\n", + mService.getText(R.string.voice_warning_how_to_turn_off)); + } + builder.setMessage(message); + builder.setTitle(R.string.voice_warning_title); + mVoiceWarningDialog = builder.create(); + final Window window = mVoiceWarningDialog.getWindow(); + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.token = token; + lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + window.setAttributes(lp); + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + mVoiceInput.logKeyboardWarningDialogShown(); + mVoiceWarningDialog.show(); + } + + private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder { + private AlertDialog mAlertDialog; + + public UrlLinkAlertDialogBuilder(Context context) { + super(context); + } + + @Override + public AlertDialog.Builder setMessage(CharSequence message) { + return super.setMessage(replaceURLSpan(message)); + } + + private Spanned replaceURLSpan(CharSequence message) { + // Replace all spans with the custom span + final SpannableStringBuilder ssb = new SpannableStringBuilder(message); + for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) { + int spanStart = ssb.getSpanStart(span); + int spanEnd = ssb.getSpanEnd(span); + int spanFlags = ssb.getSpanFlags(span); + ssb.removeSpan(span); + ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags); + } + return ssb; + } + + @Override + public AlertDialog create() { + final AlertDialog dialog = super.create(); + + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogInterface) { + // Make URL in the dialog message click-able. + TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message); + if (textView != null) { + textView.setMovementMethod(LinkMovementMethod.getInstance()); + } + } + }); + mAlertDialog = dialog; + return dialog; + } + + class ClickableSpan extends URLSpan { + public ClickableSpan(String url) { + super(url); + } + + @Override + public void onClick(View widget) { + Uri uri = Uri.parse(getURL()); + Context context = widget.getContext(); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + // Add this flag to start an activity from service + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + // Dismiss the warning dialog and go back to the previous IME. + // TODO: If we can find a way to bring the new activity to front while keeping + // the warning dialog, we don't need to dismiss it here. + mAlertDialog.cancel(); + context.startActivity(intent); + } + } + } + + public void showPunctuationHintIfNecessary() { + InputConnection ic = mService.getCurrentInputConnection(); + if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) { + if (mHints.showPunctuationHintIfNecessary(ic)) { + mVoiceInput.logPunctuationHintDisplayed(); + } + } + mImmediatelyAfterVoiceInput = false; + } + + public void hideVoiceWindow(boolean configurationChanging) { + if (!configurationChanging) { + if (mAfterVoiceInput) + mVoiceInput.logInputEnded(); + if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { + mVoiceInput.logKeyboardWarningDialogDismissed(); + mVoiceWarningDialog.dismiss(); + mVoiceWarningDialog = null; + } + if (VOICE_INSTALLED & mRecognizing) { + mVoiceInput.cancel(); + } + } + mWordToSuggestions.clear(); + } + + public void setCursorAndSelection(int newSelEnd, int newSelStart) { + if (mAfterVoiceInput) { + mVoiceInput.setCursorPos(newSelEnd); + mVoiceInput.setSelectionSpan(newSelEnd - newSelStart); + } + } + + public void setVoiceInputHighlighted(boolean b) { + mVoiceInputHighlighted = b; + } + + public void setShowingVoiceSuggestions(boolean b) { + mShowingVoiceSuggestions = b; + } + + public boolean isVoiceButtonEnabled() { + return mVoiceButtonEnabled; + } + + public boolean isVoiceButtonOnPrimary() { + return mVoiceButtonOnPrimary; + } + + public boolean isVoiceInputHighlighted() { + return mVoiceInputHighlighted; + } + + public boolean isRecognizing() { + return mRecognizing; + } + + public boolean needsToShowWarningDialog() { + return !mHasUsedVoiceInput + || (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale); + } + + public boolean getAndResetIsShowingHint() { + boolean ret = mIsShowingHint; + mIsShowingHint = false; + return ret; + } + + private void revertVoiceInput() { + InputConnection ic = mService.getCurrentInputConnection(); + if (ic != null) ic.commitText("", 1); + mService.updateSuggestions(); + mVoiceInputHighlighted = false; + } + + public void commitVoiceInput() { + if (VOICE_INSTALLED && mVoiceInputHighlighted) { + InputConnection ic = mService.getCurrentInputConnection(); + if (ic != null) ic.finishComposingText(); + mService.updateSuggestions(); + mVoiceInputHighlighted = false; + } + } + + public boolean logAndRevertVoiceInput() { + if (VOICE_INSTALLED && mVoiceInputHighlighted) { + mVoiceInput.incrementTextModificationDeleteCount( + mVoiceResults.candidates.get(0).toString().length()); + revertVoiceInput(); + return true; + } else { + return false; + } + } + + public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) { + if (mShowingVoiceSuggestions) { + // Retain the replaced word in the alternatives array. + String wordToBeReplaced = EditingUtils.getWordAtCursor( + mService.getCurrentInputConnection(), wordSeparators); + if (!mWordToSuggestions.containsKey(wordToBeReplaced)) { + wordToBeReplaced = wordToBeReplaced.toLowerCase(); + } + if (mWordToSuggestions.containsKey(wordToBeReplaced)) { + List suggestions = mWordToSuggestions.get(wordToBeReplaced); + if (suggestions.contains(suggestion)) { + suggestions.remove(suggestion); + } + suggestions.add(wordToBeReplaced); + mWordToSuggestions.remove(wordToBeReplaced); + mWordToSuggestions.put(suggestion.toString(), suggestions); + } + } + } + + /** + * Tries to apply any voice alternatives for the word if this was a spoken word and + * there are voice alternatives. + * @param touching The word that the cursor is touching, with position information + * @return true if an alternative was found, false otherwise. + */ + public boolean applyVoiceAlternatives(EditingUtils.SelectedWord touching) { + // Search for result in spoken word alternatives + String selectedWord = touching.mWord.toString().trim(); + if (!mWordToSuggestions.containsKey(selectedWord)) { + selectedWord = selectedWord.toLowerCase(); + } + if (mWordToSuggestions.containsKey(selectedWord)) { + mShowingVoiceSuggestions = true; + List suggestions = mWordToSuggestions.get(selectedWord); + SuggestedWords.Builder builder = new SuggestedWords.Builder(); + // If the first letter of touching is capitalized, make all the suggestions + // start with a capital letter. + if (Character.isUpperCase(touching.mWord.charAt(0))) { + for (CharSequence word : suggestions) { + String str = word.toString(); + word = Character.toUpperCase(str.charAt(0)) + str.substring(1); + builder.addWord(word); + } + } else { + builder.addWords(suggestions, null); + } + builder.setTypedWordValid(true).setHasMinimalSuggestion(true); + mService.setSuggestions(builder.build()); + mService.setCandidatesViewShown(true); + return true; + } + return false; + } + + public void handleBackspace() { + if (mAfterVoiceInput) { + // Don't log delete if the user is pressing delete at + // the beginning of the text box (hence not deleting anything) + if (mVoiceInput.getCursorPos() > 0) { + // If anything was selected before the delete was pressed, increment the + // delete count by the length of the selection + int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? + mVoiceInput.getSelectionSpan() : 1; + mVoiceInput.incrementTextModificationDeleteCount(deleteLen); + } + } + } + + public void handleCharacter() { + commitVoiceInput(); + if (mAfterVoiceInput) { + // Assume input length is 1. This assumption fails for smiley face insertions. + mVoiceInput.incrementTextModificationInsertCount(1); + } + } + + public void handleSeparator() { + commitVoiceInput(); + if (mAfterVoiceInput){ + // Assume input length is 1. This assumption fails for smiley face insertions. + mVoiceInput.incrementTextModificationInsertPunctuationCount(1); + } + } + + public void handleClose() { + if (VOICE_INSTALLED & mRecognizing) { + mVoiceInput.cancel(); + } + } + + + public void handleVoiceResults(boolean capitalizeFirstWord) { + mAfterVoiceInput = true; + mImmediatelyAfterVoiceInput = true; + + InputConnection ic = mService.getCurrentInputConnection(); + if (!mService.isFullscreenMode()) { + // Start listening for updates to the text from typing, etc. + if (ic != null) { + ExtractedTextRequest req = new ExtractedTextRequest(); + ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); + } + } + mService.vibrate(); + + final List nBest = new ArrayList(); + for (String c : mVoiceResults.candidates) { + if (capitalizeFirstWord) { + c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length()); + } + nBest.add(c); + } + if (nBest.size() == 0) { + return; + } + String bestResult = nBest.get(0).toString(); + mVoiceInput.logVoiceInputDelivered(bestResult.length()); + mHints.registerVoiceResult(bestResult); + + if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text + mService.commitTyped(ic); + EditingUtils.appendText(ic, bestResult); + if (ic != null) ic.endBatchEdit(); + + mVoiceInputHighlighted = true; + mWordToSuggestions.putAll(mVoiceResults.alternatives); + onCancelVoice(); + } + + public void switchToRecognitionStatusView(final Configuration configuration) { + mHandler.post(new Runnable() { + @Override + public void run() { + mService.setCandidatesViewShown(false); + mRecognizing = true; + mVoiceInput.newView(); + View v = mVoiceInput.getView(); + + ViewParent p = v.getParent(); + if (p != null && p instanceof ViewGroup) { + ((ViewGroup) p).removeView(v); + } + + View keyboardView = KeyboardSwitcher.getInstance().getInputView(); + + // The full height of the keyboard is difficult to calculate + // as the dimension is expressed in "mm" and not in "pixel" + // As we add mm, we don't know how the rounding is going to work + // thus we may end up with few pixels extra (or less). + if (keyboardView != null) { + View popupLayout = v.findViewById(R.id.popup_layout); + final int displayHeight = + mService.getResources().getDisplayMetrics().heightPixels; + final int currentHeight = popupLayout.getLayoutParams().height; + final int keyboardHeight = keyboardView.getHeight(); + if (keyboardHeight > currentHeight || keyboardHeight + > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) { + popupLayout.getLayoutParams().height = keyboardHeight; + } + } + mService.setInputView(v); + mService.updateInputViewShown(); + + if (configuration != null) { + mVoiceInput.onConfigurationChanged(configuration); + } + }}); + } + + private void switchToLastInputMethod() { + final IBinder token = mService.getWindow().getWindow().getAttributes().token; + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + return mImm.switchToLastInputMethod(token); + } + + @Override + protected void onPostExecute(Boolean result) { + if (!result) { + if (DEBUG) { + Log.d(TAG, "Couldn't switch back to last IME."); + } + // Needs to reset here because LatinIME failed to back to any IME and + // the same voice subtype will be triggered in the next time. + mVoiceInput.reset(); + mService.requestHideSelf(0); + } + } + }.execute(); + } + + private void reallyStartListening(boolean swipe) { + if (!VOICE_INSTALLED) { + return; + } + if (!mHasUsedVoiceInput) { + // The user has started a voice input, so remember that in the + // future (so we don't show the warning dialog after the first run). + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(mService).edit(); + editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true); + SharedPreferencesCompat.apply(editor); + mHasUsedVoiceInput = true; + } + + if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) { + // The user has started a voice input from an unsupported locale, so remember that + // in the future (so we don't show the warning dialog the next time they do this). + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(mService).edit(); + editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true); + SharedPreferencesCompat.apply(editor); + mHasUsedVoiceInputUnsupportedLocale = true; + } + + // Clear N-best suggestions + mService.clearSuggestions(); + + FieldContext context = makeFieldContext(); + mVoiceInput.startListening(context, swipe); + switchToRecognitionStatusView(null); + } + + public void startListening(final boolean swipe, IBinder token) { + // TODO: remove swipe which is no longer used. + if (VOICE_INSTALLED) { + if (needsToShowWarningDialog()) { + // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. + showVoiceWarningDialog(swipe, token); + } else { + reallyStartListening(swipe); + } + } + } + + private boolean fieldCanDoVoice(FieldContext fieldContext) { + return !mPasswordText + && mVoiceInput != null + && !mVoiceInput.isBlacklistedField(fieldContext); + } + + private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { + final boolean noMic = Utils.inPrivateImeOptions(null, + LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute) + || Utils.inPrivateImeOptions(mService.getPackageName(), + LatinIME.IME_OPTION_NO_MICROPHONE, attribute); + return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic + && SpeechRecognizer.isRecognitionAvailable(mService); + } + + public void loadSettings(EditorInfo attribute, SharedPreferences sp) { + mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false); + mHasUsedVoiceInputUnsupportedLocale = + sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false); + + mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported( + SubtypeSwitcher.getInstance().getInputLocaleStr()); + + if (VOICE_INSTALLED) { + final String voiceMode = sp.getString(PREF_VOICE_MODE, + mService.getString(R.string.voice_mode_main)); + mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off)) + && shouldShowVoiceButton(makeFieldContext(), attribute); + mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main)); + } + } + + public void destroy() { + if (VOICE_INSTALLED && mVoiceInput != null) { + mVoiceInput.destroy(); + } + } + + public void onStartInputView(IBinder keyboardViewToken) { + // If keyboardViewToken is null, keyboardView is not attached but voiceView is attached. + IBinder windowToken = keyboardViewToken != null ? keyboardViewToken + : mVoiceInput.getView().getWindowToken(); + // If IME is in voice mode, but still needs to show the voice warning dialog, + // keep showing the warning. + if (mSubtypeSwitcher.isVoiceMode() && windowToken != null) { + // Close keyboard view if it is been shown. + if (KeyboardSwitcher.getInstance().isInputViewShown()) + KeyboardSwitcher.getInstance().getInputView().purgeKeyboardAndClosing(); + startListening(false, windowToken); + } + // If we have no token, onAttachedToWindow will take care of showing dialog and start + // listening. + } + + public void onAttachedToWindow() { + // After onAttachedToWindow, we can show the voice warning dialog. See startListening() + // above. + VoiceInputConnector.getInstance().setVoiceInput(mVoiceInput, mSubtypeSwitcher); + } + + public void onConfigurationChanged(Configuration configuration) { + if (mRecognizing) { + switchToRecognitionStatusView(configuration); + } + } + + @Override + public void onCancelVoice() { + if (mRecognizing) { + if (mSubtypeSwitcher.isVoiceMode()) { + // If voice mode is being canceled within LatinIME (i.e. time-out or user + // cancellation etc.), onCancelVoice() will be called first. LatinIME thinks it's + // still in voice mode. LatinIME needs to call switchToLastInputMethod(). + // Note that onCancelVoice() will be called again from SubtypeSwitcher. + switchToLastInputMethod(); + } else if (mSubtypeSwitcher.isKeyboardMode()) { + // If voice mode is being canceled out of LatinIME (i.e. by user's IME switching or + // as a result of switchToLastInputMethod() etc.), + // onCurrentInputMethodSubtypeChanged() will be called first. LatinIME will know + // that it's in keyboard mode and SubtypeSwitcher will call onCancelVoice(). + mRecognizing = false; + mService.switchToKeyboardView(); + } + } + } + + @Override + public void onVoiceResults(List candidates, + Map> alternatives) { + if (!mRecognizing) { + return; + } + mVoiceResults.candidates = candidates; + mVoiceResults.alternatives = alternatives; + mHandler.updateVoiceResults(); + } + + private FieldContext makeFieldContext() { + SubtypeSwitcher switcher = SubtypeSwitcher.getInstance(); + return new FieldContext(mService.getCurrentInputConnection(), + mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(), + switcher.getEnabledLanguages()); + } + + private class VoiceResults { + List candidates; + Map> alternatives; + } + + public static class VoiceLoggerConnector { + private static final VoiceLoggerConnector sInstance = new VoiceLoggerConnector(); + private VoiceInputLogger mLogger; + + public static VoiceLoggerConnector getInstance(Context context) { + if (sInstance.mLogger == null) { + // Not thread safe, but it's ok. + sInstance.mLogger = VoiceInputLogger.getLogger(context); + } + return sInstance; + } + + // private for the singleton + private VoiceLoggerConnector() { + } + + public void settingsWarningDialogCancel() { + mLogger.settingsWarningDialogCancel(); + } + + public void settingsWarningDialogOk() { + mLogger.settingsWarningDialogOk(); + } + + public void settingsWarningDialogShown() { + mLogger.settingsWarningDialogShown(); + } + + public void settingsWarningDialogDismissed() { + mLogger.settingsWarningDialogDismissed(); + } + + public void voiceInputSettingEnabled(boolean enabled) { + if (enabled) { + mLogger.voiceInputSettingEnabled(); + } else { + mLogger.voiceInputSettingDisabled(); + } + } + } + + public static class VoiceInputConnector { + private static final VoiceInputConnector sInstance = new VoiceInputConnector(); + private VoiceInput mVoiceInput; + public static VoiceInputConnector getInstance() { + return sInstance; + } + public void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) { + if (mVoiceInput == null && voiceInput != null) { + mVoiceInput = voiceInput; + switcher.setVoiceInputConnector(this); + } + } + + private VoiceInputConnector() { + } + + public void cancel() { + if (mVoiceInput != null) mVoiceInput.cancel(); + } + + public void reset() { + if (mVoiceInput != null) mVoiceInput.reset(); + } + } + + // A list of locales which are supported by default for voice input, unless we get a + // different list from Gservices. + private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = + "en " + + "en_US " + + "en_GB " + + "en_AU " + + "en_CA " + + "en_IE " + + "en_IN " + + "en_NZ " + + "en_SG " + + "en_ZA "; + + public static String getSupportedLocalesString (ContentResolver resolver) { + return SettingsUtil.getSettingsString( + resolver, + SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, + DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); + } +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java new file mode 100644 index 000000000..0ef73d2d7 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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.deprecated.voice; + +import android.os.Bundle; +import android.util.Log; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; + +/** + * Represents information about a given text field, which can be passed + * to the speech recognizer as context information. + */ +public class FieldContext { + private static final boolean DBG = false; + + static final String LABEL = "label"; + static final String HINT = "hint"; + static final String PACKAGE_NAME = "packageName"; + static final String FIELD_ID = "fieldId"; + static final String FIELD_NAME = "fieldName"; + static final String SINGLE_LINE = "singleLine"; + static final String INPUT_TYPE = "inputType"; + static final String IME_OPTIONS = "imeOptions"; + static final String SELECTED_LANGUAGE = "selectedLanguage"; + static final String ENABLED_LANGUAGES = "enabledLanguages"; + + Bundle mFieldInfo; + + public FieldContext(InputConnection conn, EditorInfo info, + String selectedLanguage, String[] enabledLanguages) { + mFieldInfo = new Bundle(); + addEditorInfoToBundle(info, mFieldInfo); + addInputConnectionToBundle(conn, mFieldInfo); + addLanguageInfoToBundle(selectedLanguage, enabledLanguages, mFieldInfo); + if (DBG) Log.i("FieldContext", "Bundle = " + mFieldInfo.toString()); + } + + private static String safeToString(Object o) { + if (o == null) { + return ""; + } + return o.toString(); + } + + private static void addEditorInfoToBundle(EditorInfo info, Bundle bundle) { + if (info == null) { + return; + } + + bundle.putString(LABEL, safeToString(info.label)); + bundle.putString(HINT, safeToString(info.hintText)); + bundle.putString(PACKAGE_NAME, safeToString(info.packageName)); + bundle.putInt(FIELD_ID, info.fieldId); + bundle.putString(FIELD_NAME, safeToString(info.fieldName)); + bundle.putInt(INPUT_TYPE, info.inputType); + bundle.putInt(IME_OPTIONS, info.imeOptions); + } + + @SuppressWarnings("static-access") + private static void addInputConnectionToBundle( + InputConnection conn, Bundle bundle) { + if (conn == null) { + return; + } + + ExtractedText et = conn.getExtractedText(new ExtractedTextRequest(), 0); + if (et == null) { + return; + } + bundle.putBoolean(SINGLE_LINE, (et.flags & et.FLAG_SINGLE_LINE) > 0); + } + + private static void addLanguageInfoToBundle( + String selectedLanguage, String[] enabledLanguages, Bundle bundle) { + bundle.putString(SELECTED_LANGUAGE, selectedLanguage); + bundle.putStringArray(ENABLED_LANGUAGES, enabledLanguages); + } + + public Bundle getBundle() { + return mFieldInfo; + } + + @Override + public String toString() { + return mFieldInfo.toString(); + } +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/Hints.java b/java/src/com/android/inputmethod/deprecated/voice/Hints.java new file mode 100644 index 000000000..52a4f4e58 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/Hints.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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.deprecated.voice; + +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SharedPreferencesCompat; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.SharedPreferences; +import android.view.inputmethod.InputConnection; + +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +/** + * Logic to determine when to display hints on usage to the user. + */ +public class Hints { + public interface Display { + public void showHint(int viewResource); + } + + private static final String PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN = + "voice_hint_num_unique_days_shown"; + private static final String PREF_VOICE_HINT_LAST_TIME_SHOWN = + "voice_hint_last_time_shown"; + private static final String PREF_VOICE_INPUT_LAST_TIME_USED = + "voice_input_last_time_used"; + private static final String PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT = + "voice_punctuation_hint_view_count"; + private static final int DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW = 7; + private static final int DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS = 7; + + private final Context mContext; + private final SharedPreferences mPrefs; + private final Display mDisplay; + private boolean mVoiceResultContainedPunctuation; + private int mSwipeHintMaxDaysToShow; + private int mPunctuationHintMaxDisplays; + + // Only show punctuation hint if voice result did not contain punctuation. + static final Map SPEAKABLE_PUNCTUATION + = new HashMap(); + static { + SPEAKABLE_PUNCTUATION.put(",", "comma"); + SPEAKABLE_PUNCTUATION.put(".", "period"); + SPEAKABLE_PUNCTUATION.put("?", "question mark"); + } + + public Hints(Context context, SharedPreferences prefs, Display display) { + mContext = context; + mPrefs = prefs; + mDisplay = display; + + ContentResolver cr = mContext.getContentResolver(); + mSwipeHintMaxDaysToShow = SettingsUtil.getSettingsInt( + cr, + SettingsUtil.LATIN_IME_VOICE_INPUT_SWIPE_HINT_MAX_DAYS, + DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW); + mPunctuationHintMaxDisplays = SettingsUtil.getSettingsInt( + cr, + SettingsUtil.LATIN_IME_VOICE_INPUT_PUNCTUATION_HINT_MAX_DISPLAYS, + DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS); + } + + public boolean showSwipeHintIfNecessary(boolean fieldRecommended) { + if (fieldRecommended && shouldShowSwipeHint()) { + showHint(R.layout.voice_swipe_hint); + return true; + } + + return false; + } + + public boolean showPunctuationHintIfNecessary(InputConnection ic) { + if (!mVoiceResultContainedPunctuation + && ic != null + && getAndIncrementPref(PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT) + < mPunctuationHintMaxDisplays) { + CharSequence charBeforeCursor = ic.getTextBeforeCursor(1, 0); + if (SPEAKABLE_PUNCTUATION.containsKey(charBeforeCursor)) { + showHint(R.layout.voice_punctuation_hint); + return true; + } + } + + return false; + } + + public void registerVoiceResult(String text) { + // Update the current time as the last time voice input was used. + SharedPreferences.Editor editor = mPrefs.edit(); + editor.putLong(PREF_VOICE_INPUT_LAST_TIME_USED, System.currentTimeMillis()); + SharedPreferencesCompat.apply(editor); + + mVoiceResultContainedPunctuation = false; + for (CharSequence s : SPEAKABLE_PUNCTUATION.keySet()) { + if (text.indexOf(s.toString()) >= 0) { + mVoiceResultContainedPunctuation = true; + break; + } + } + } + + private boolean shouldShowSwipeHint() { + final SharedPreferences prefs = mPrefs; + + int numUniqueDaysShown = prefs.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0); + + // If we've already shown the hint for enough days, we'll return false. + if (numUniqueDaysShown < mSwipeHintMaxDaysToShow) { + + long lastTimeVoiceWasUsed = prefs.getLong(PREF_VOICE_INPUT_LAST_TIME_USED, 0); + + // If the user has used voice today, we'll return false. (We don't show the hint on + // any day that the user has already used voice.) + if (!isFromToday(lastTimeVoiceWasUsed)) { + return true; + } + } + + return false; + } + + /** + * Determines whether the provided time is from some time today (i.e., this day, month, + * and year). + */ + private boolean isFromToday(long timeInMillis) { + if (timeInMillis == 0) return false; + + Calendar today = Calendar.getInstance(); + today.setTimeInMillis(System.currentTimeMillis()); + + Calendar timestamp = Calendar.getInstance(); + timestamp.setTimeInMillis(timeInMillis); + + return (today.get(Calendar.YEAR) == timestamp.get(Calendar.YEAR) && + today.get(Calendar.DAY_OF_MONTH) == timestamp.get(Calendar.DAY_OF_MONTH) && + today.get(Calendar.MONTH) == timestamp.get(Calendar.MONTH)); + } + + private void showHint(int hintViewResource) { + final SharedPreferences prefs = mPrefs; + + int numUniqueDaysShown = prefs.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0); + long lastTimeHintWasShown = prefs.getLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, 0); + + // If this is the first time the hint is being shown today, increase the saved values + // to represent that. We don't need to increase the last time the hint was shown unless + // it is a different day from the current value. + if (!isFromToday(lastTimeHintWasShown)) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, numUniqueDaysShown + 1); + editor.putLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, System.currentTimeMillis()); + SharedPreferencesCompat.apply(editor); + } + + if (mDisplay != null) { + mDisplay.showHint(hintViewResource); + } + } + + private int getAndIncrementPref(String pref) { + final SharedPreferences prefs = mPrefs; + int value = prefs.getInt(pref, 0); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(pref, value + 1); + SharedPreferencesCompat.apply(editor); + return value; + } +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java new file mode 100644 index 000000000..52c73ce90 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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.deprecated.voice; + +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SubtypeSwitcher; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.CornerPathEffect; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathEffect; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.util.Locale; + +/** + * The user interface for the "Speak now" and "working" states. + * Displays a recognition dialog (with waveform, voice meter, etc.), + * plays beeps, shows errors, etc. + */ +public class RecognitionView { + @SuppressWarnings("unused") + private static final String TAG = "RecognitionView"; + + private Handler mUiHandler; // Reference to UI thread + private View mView; + private Context mContext; + + private TextView mText; + private ImageView mImage; + private View mProgress; + private SoundIndicator mSoundIndicator; + private TextView mLanguage; + private Button mButton; + + private Drawable mInitializing; + private Drawable mError; + + private static final int INIT = 0; + private static final int LISTENING = 1; + private static final int WORKING = 2; + private static final int READY = 3; + + private int mState = INIT; + + private final View mPopupLayout; + + private final Drawable mListeningBorder; + private final Drawable mWorkingBorder; + private final Drawable mErrorBorder; + + public RecognitionView(Context context, OnClickListener clickListener) { + mUiHandler = new Handler(); + + LayoutInflater inflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + mView = inflater.inflate(R.layout.recognition_status, null); + + mPopupLayout= mView.findViewById(R.id.popup_layout); + + // Pre-load volume level images + Resources r = context.getResources(); + + mListeningBorder = r.getDrawable(R.drawable.vs_dialog_red); + mWorkingBorder = r.getDrawable(R.drawable.vs_dialog_blue); + mErrorBorder = r.getDrawable(R.drawable.vs_dialog_yellow); + + mInitializing = r.getDrawable(R.drawable.mic_slash); + mError = r.getDrawable(R.drawable.caution); + + mImage = (ImageView) mView.findViewById(R.id.image); + mProgress = mView.findViewById(R.id.progress); + mSoundIndicator = (SoundIndicator) mView.findViewById(R.id.sound_indicator); + + mButton = (Button) mView.findViewById(R.id.button); + mButton.setOnClickListener(clickListener); + mText = (TextView) mView.findViewById(R.id.text); + mLanguage = (TextView) mView.findViewById(R.id.language); + + mContext = context; + } + + public View getView() { + return mView; + } + + public void restoreState() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + // Restart the spinner + if (mState == WORKING) { + ((ProgressBar) mProgress).setIndeterminate(false); + ((ProgressBar) mProgress).setIndeterminate(true); + } + } + }); + } + + public void showInitializing() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + mState = INIT; + prepareDialog(mContext.getText(R.string.voice_initializing), mInitializing, + mContext.getText(R.string.cancel)); + } + }); + } + + public void showListening() { + Log.d(TAG, "#showListening"); + mUiHandler.post(new Runnable() { + @Override + public void run() { + mState = LISTENING; + prepareDialog(mContext.getText(R.string.voice_listening), null, + mContext.getText(R.string.cancel)); + } + }); + } + + public void updateVoiceMeter(float rmsdB) { + mSoundIndicator.setRmsdB(rmsdB); + } + + public void showError(final String message) { + mUiHandler.post(new Runnable() { + @Override + public void run() { + mState = READY; + prepareDialog(message, mError, mContext.getText(R.string.ok)); + } + }); + } + + public void showWorking( + final ByteArrayOutputStream waveBuffer, + final int speechStartPosition, + final int speechEndPosition) { + mUiHandler.post(new Runnable() { + @Override + public void run() { + mState = WORKING; + prepareDialog(mContext.getText(R.string.voice_working), null, mContext + .getText(R.string.cancel)); + final ShortBuffer buf = ByteBuffer.wrap(waveBuffer.toByteArray()).order( + ByteOrder.nativeOrder()).asShortBuffer(); + buf.position(0); + waveBuffer.reset(); + showWave(buf, speechStartPosition / 2, speechEndPosition / 2); + } + }); + } + + private void prepareDialog(CharSequence text, Drawable image, + CharSequence btnTxt) { + + /* + * The mic of INIT and of LISTENING has to be displayed in the same position. To accomplish + * that, some text visibility are not set as GONE but as INVISIBLE. + */ + switch (mState) { + case INIT: + mText.setVisibility(View.INVISIBLE); + + mProgress.setVisibility(View.GONE); + + mImage.setVisibility(View.VISIBLE); + mImage.setImageResource(R.drawable.mic_slash); + + mSoundIndicator.setVisibility(View.GONE); + mSoundIndicator.stop(); + + mLanguage.setVisibility(View.INVISIBLE); + + mPopupLayout.setBackgroundDrawable(mListeningBorder); + break; + case LISTENING: + mText.setVisibility(View.VISIBLE); + mText.setText(text); + + mProgress.setVisibility(View.GONE); + + mImage.setVisibility(View.GONE); + + mSoundIndicator.setVisibility(View.VISIBLE); + mSoundIndicator.start(); + + Locale locale = SubtypeSwitcher.getInstance().getInputLocale(); + + mLanguage.setVisibility(View.VISIBLE); + mLanguage.setText(SubtypeSwitcher.getFullDisplayName(locale, true)); + + mPopupLayout.setBackgroundDrawable(mListeningBorder); + break; + case WORKING: + + mText.setVisibility(View.VISIBLE); + mText.setText(text); + + mProgress.setVisibility(View.VISIBLE); + + mImage.setVisibility(View.VISIBLE); + + mSoundIndicator.setVisibility(View.GONE); + mSoundIndicator.stop(); + + mLanguage.setVisibility(View.GONE); + + mPopupLayout.setBackgroundDrawable(mWorkingBorder); + break; + case READY: + mText.setVisibility(View.VISIBLE); + mText.setText(text); + + mProgress.setVisibility(View.GONE); + + mImage.setVisibility(View.VISIBLE); + mImage.setImageResource(R.drawable.caution); + + mSoundIndicator.setVisibility(View.GONE); + mSoundIndicator.stop(); + + mLanguage.setVisibility(View.GONE); + + mPopupLayout.setBackgroundDrawable(mErrorBorder); + break; + default: + Log.w(TAG, "Unknown state " + mState); + } + mPopupLayout.requestLayout(); + mButton.setText(btnTxt); + } + + /** + * @return an average abs of the specified buffer. + */ + private static int getAverageAbs(ShortBuffer buffer, int start, int i, int npw) { + int from = start + i * npw; + int end = from + npw; + int total = 0; + for (int x = from; x < end; x++) { + total += Math.abs(buffer.get(x)); + } + return total / npw; + } + + + /** + * Shows waveform of input audio. + * + * Copied from version in VoiceSearch's RecognitionActivity. + * + * TODO: adjust stroke width based on the size of data. + * TODO: use dip rather than pixels. + */ + private void showWave(ShortBuffer waveBuffer, int startPosition, int endPosition) { + final int w = ((View) mImage.getParent()).getWidth(); + final int h = ((View) mImage.getParent()).getHeight(); + if (w <= 0 || h <= 0) { + // view is not visible this time. Skip drawing. + return; + } + final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + final Canvas c = new Canvas(b); + final Paint paint = new Paint(); + paint.setColor(0xFFFFFFFF); // 0xAARRGGBB + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.STROKE); + paint.setAlpha(80); + + final PathEffect effect = new CornerPathEffect(3); + paint.setPathEffect(effect); + + final int numSamples = waveBuffer.remaining(); + int endIndex; + if (endPosition == 0) { + endIndex = numSamples; + } else { + endIndex = Math.min(endPosition, numSamples); + } + + int startIndex = startPosition - 2000; // include 250ms before speech + if (startIndex < 0) { + startIndex = 0; + } + final int numSamplePerWave = 200; // 8KHz 25ms = 200 samples + final float scale = 10.0f / 65536.0f; + + final int count = (endIndex - startIndex) / numSamplePerWave; + final float deltaX = 1.0f * w / count; + int yMax = h / 2; + Path path = new Path(); + c.translate(0, yMax); + float x = 0; + path.moveTo(x, 0); + for (int i = 0; i < count; i++) { + final int avabs = getAverageAbs(waveBuffer, startIndex, i , numSamplePerWave); + int sign = ( (i & 01) == 0) ? -1 : 1; + final float y = Math.min(yMax, avabs * h * scale) * sign; + path.lineTo(x, y); + x += deltaX; + path.lineTo(x, y); + } + if (deltaX > 4) { + paint.setStrokeWidth(2); + } else { + paint.setStrokeWidth(Math.max(0, (int) (deltaX -.05))); + } + c.drawPath(path, paint); + mImage.setImageBitmap(b); + } + + public void finish() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + mSoundIndicator.stop(); + } + }); + } +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java new file mode 100644 index 000000000..7721fe268 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/SettingsUtil.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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.deprecated.voice; + +import android.content.ContentResolver; +import android.provider.Settings; + +/** + * Utility for retrieving settings from Settings.Secure. + */ +public class SettingsUtil { + /** + * A whitespace-separated list of supported locales for voice input from the keyboard. + */ + public static final String LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES = + "latin_ime_voice_input_supported_locales"; + + /** + * A whitespace-separated list of recommended app packages for voice input from the + * keyboard. + */ + public static final String LATIN_IME_VOICE_INPUT_RECOMMENDED_PACKAGES = + "latin_ime_voice_input_recommended_packages"; + + /** + * The maximum number of unique days to show the swipe hint for voice input. + */ + public static final String LATIN_IME_VOICE_INPUT_SWIPE_HINT_MAX_DAYS = + "latin_ime_voice_input_swipe_hint_max_days"; + + /** + * The maximum number of times to show the punctuation hint for voice input. + */ + public static final String LATIN_IME_VOICE_INPUT_PUNCTUATION_HINT_MAX_DISPLAYS = + "latin_ime_voice_input_punctuation_hint_max_displays"; + + /** + * Endpointer parameters for voice input from the keyboard. + */ + public static final String LATIN_IME_SPEECH_MINIMUM_LENGTH_MILLIS = + "latin_ime_speech_minimum_length_millis"; + public static final String LATIN_IME_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS = + "latin_ime_speech_input_complete_silence_length_millis"; + public static final String LATIN_IME_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS = + "latin_ime_speech_input_possibly_complete_silence_length_millis"; + + /** + * Min and max volume levels that can be displayed on the "speak now" screen. + */ + public static final String LATIN_IME_MIN_MICROPHONE_LEVEL = + "latin_ime_min_microphone_level"; + public static final String LATIN_IME_MAX_MICROPHONE_LEVEL = + "latin_ime_max_microphone_level"; + + /** + * The number of sentence-level alternates to request of the server. + */ + public static final String LATIN_IME_MAX_VOICE_RESULTS = "latin_ime_max_voice_results"; + + /** + * Get a string-valued setting. + * + * @param cr The content resolver to use + * @param key The setting to look up + * @param defaultValue The default value to use if none can be found + * @return The value of the setting, or defaultValue if it couldn't be found + */ + public static String getSettingsString(ContentResolver cr, String key, String defaultValue) { + String result = Settings.Secure.getString(cr, key); + return (result == null) ? defaultValue : result; + } + + /** + * Get an int-valued setting. + * + * @param cr The content resolver to use + * @param key The setting to look up + * @param defaultValue The default value to use if the setting couldn't be found or parsed + * @return The value of the setting, or defaultValue if it couldn't be found or parsed + */ + public static int getSettingsInt(ContentResolver cr, String key, int defaultValue) { + return Settings.Secure.getInt(cr, key, defaultValue); + } + + /** + * Get a float-valued setting. + * + * @param cr The content resolver to use + * @param key The setting to look up + * @param defaultValue The default value to use if the setting couldn't be found or parsed + * @return The value of the setting, or defaultValue if it couldn't be found or parsed + */ + public static float getSettingsFloat(ContentResolver cr, String key, float defaultValue) { + return Settings.Secure.getFloat(cr, key, defaultValue); + } +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java new file mode 100644 index 000000000..8cc79de1e --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/SoundIndicator.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * 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.deprecated.voice; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.android.inputmethod.latin.R; + +/** + * A widget which shows the volume of audio using a microphone icon + */ +public class SoundIndicator extends ImageView { + @SuppressWarnings("unused") + private static final String TAG = "SoundIndicator"; + + private static final float UP_SMOOTHING_FACTOR = 0.9f; + private static final float DOWN_SMOOTHING_FACTOR = 0.4f; + + private static final float AUDIO_METER_MIN_DB = 7.0f; + private static final float AUDIO_METER_DB_RANGE = 20.0f; + + private static final long FRAME_DELAY = 50; + + private Bitmap mDrawingBuffer; + private Canvas mBufferCanvas; + private Bitmap mEdgeBitmap; + private float mLevel = 0.0f; + private Drawable mFrontDrawable; + private Paint mClearPaint; + private Paint mMultPaint; + private int mEdgeBitmapOffset; + + private Handler mHandler; + + private Runnable mDrawFrame = new Runnable() { + public void run() { + invalidate(); + mHandler.postDelayed(mDrawFrame, FRAME_DELAY); + } + }; + + public SoundIndicator(Context context) { + this(context, null); + } + + public SoundIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + + mFrontDrawable = getDrawable(); + BitmapDrawable edgeDrawable = + (BitmapDrawable) context.getResources().getDrawable(R.drawable.vs_popup_mic_edge); + mEdgeBitmap = edgeDrawable.getBitmap(); + mEdgeBitmapOffset = mEdgeBitmap.getHeight() / 2; + + mDrawingBuffer = + Bitmap.createBitmap(mFrontDrawable.getIntrinsicWidth(), + mFrontDrawable.getIntrinsicHeight(), Config.ARGB_8888); + + mBufferCanvas = new Canvas(mDrawingBuffer); + + // Initialize Paints. + mClearPaint = new Paint(); + mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + + mMultPaint = new Paint(); + mMultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); + + mHandler = new Handler(); + } + + @Override + public void onDraw(Canvas canvas) { + //super.onDraw(canvas); + + float w = getWidth(); + float h = getHeight(); + + // Clear the buffer canvas + mBufferCanvas.drawRect(0, 0, w, h, mClearPaint); + + // Set its clip so we don't draw the front image all the way to the top + Rect clip = new Rect(0, + (int) ((1.0 - mLevel) * (h + mEdgeBitmapOffset)) - mEdgeBitmapOffset, + (int) w, + (int) h); + + mBufferCanvas.save(); + mBufferCanvas.clipRect(clip); + + // Draw the front image + mFrontDrawable.setBounds(new Rect(0, 0, (int) w, (int) h)); + mFrontDrawable.draw(mBufferCanvas); + + mBufferCanvas.restore(); + + // Draw the edge image on top of the buffer image with a multiply mode + mBufferCanvas.drawBitmap(mEdgeBitmap, 0, clip.top, mMultPaint); + + // Draw the buffer image (on top of the background image) + canvas.drawBitmap(mDrawingBuffer, 0, 0, null); + } + + /** + * Sets the sound level + * + * @param rmsdB The level of the sound, in dB. + */ + public void setRmsdB(float rmsdB) { + float level = ((rmsdB - AUDIO_METER_MIN_DB) / AUDIO_METER_DB_RANGE); + + level = Math.min(Math.max(0.0f, level), 1.0f); + + // We smooth towards the new level + if (level > mLevel) { + mLevel = (level - mLevel) * UP_SMOOTHING_FACTOR + mLevel; + } else { + mLevel = (level - mLevel) * DOWN_SMOOTHING_FACTOR + mLevel; + } + invalidate(); + } + + public void start() { + mHandler.post(mDrawFrame); + } + + public void stop() { + mHandler.removeCallbacks(mDrawFrame); + } +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java new file mode 100644 index 000000000..7ee0de9c9 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/VoiceInput.java @@ -0,0 +1,685 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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.deprecated.voice; + +import com.android.inputmethod.latin.EditingUtils; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Parcelable; +import android.speech.RecognitionListener; +import android.speech.RecognizerIntent; +import android.speech.SpeechRecognizer; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.inputmethod.InputConnection; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Speech recognition input, including both user interface and a background + * process to stream audio to the network recognizer. This class supplies a + * View (getView()), which it updates as recognition occurs. The user of this + * class is responsible for making the view visible to the user, as well as + * handling various events returned through UiListener. + */ +public class VoiceInput implements OnClickListener { + private static final String TAG = "VoiceInput"; + private static final String EXTRA_RECOGNITION_CONTEXT = + "android.speech.extras.RECOGNITION_CONTEXT"; + private static final String EXTRA_CALLING_PACKAGE = "calling_package"; + private static final String EXTRA_ALTERNATES = "android.speech.extra.ALTERNATES"; + private static final int MAX_ALT_LIST_LENGTH = 6; + private static boolean DBG = LatinImeLogger.sDBG; + + private static final String DEFAULT_RECOMMENDED_PACKAGES = + "com.android.mms " + + "com.google.android.gm " + + "com.google.android.talk " + + "com.google.android.apps.googlevoice " + + "com.android.email " + + "com.android.browser "; + + // WARNING! Before enabling this, fix the problem with calling getExtractedText() in + // landscape view. It causes Extracted text updates to be rejected due to a token mismatch + public static boolean ENABLE_WORD_CORRECTIONS = true; + + // Dummy word suggestion which means "delete current word" + public static final String DELETE_SYMBOL = " \u00D7 "; // times symbol + + private Whitelist mRecommendedList; + private Whitelist mBlacklist; + + private VoiceInputLogger mLogger; + + // Names of a few extras defined in VoiceSearch's RecognitionController + // Note, the version of voicesearch that shipped in Froyo returns the raw + // RecognitionClientAlternates protocol buffer under the key "alternates", + // so a VS market update must be installed on Froyo devices in order to see + // alternatives. + private static final String ALTERNATES_BUNDLE = "alternates_bundle"; + + // This is copied from the VoiceSearch app. + @SuppressWarnings("unused") + private static final class AlternatesBundleKeys { + public static final String ALTERNATES = "alternates"; + public static final String CONFIDENCE = "confidence"; + public static final String LENGTH = "length"; + public static final String MAX_SPAN_LENGTH = "max_span_length"; + public static final String SPANS = "spans"; + public static final String SPAN_KEY_DELIMITER = ":"; + public static final String START = "start"; + public static final String TEXT = "text"; + } + + // Names of a few intent extras defined in VoiceSearch's RecognitionService. + // These let us tweak the endpointer parameters. + private static final String EXTRA_SPEECH_MINIMUM_LENGTH_MILLIS = + "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS"; + private static final String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS = + "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS"; + private static final String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS = + "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS"; + + // The usual endpointer default value for input complete silence length is 0.5 seconds, + // but that's used for things like voice search. For dictation-like voice input like this, + // we go with a more liberal value of 1 second. This value will only be used if a value + // is not provided from Gservices. + private static final String INPUT_COMPLETE_SILENCE_LENGTH_DEFAULT_VALUE_MILLIS = "1000"; + + // Used to record part of that state for logging purposes. + public static final int DEFAULT = 0; + public static final int LISTENING = 1; + public static final int WORKING = 2; + public static final int ERROR = 3; + + private int mAfterVoiceInputDeleteCount = 0; + private int mAfterVoiceInputInsertCount = 0; + private int mAfterVoiceInputInsertPunctuationCount = 0; + private int mAfterVoiceInputCursorPos = 0; + private int mAfterVoiceInputSelectionSpan = 0; + + private int mState = DEFAULT; + + private final static int MSG_RESET = 1; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_RESET) { + mState = DEFAULT; + mRecognitionView.finish(); + mUiListener.onCancelVoice(); + } + } + }; + + /** + * Events relating to the recognition UI. You must implement these. + */ + public interface UiListener { + + /** + * @param recognitionResults a set of transcripts for what the user + * spoke, sorted by likelihood. + */ + public void onVoiceResults( + List recognitionResults, + Map> alternatives); + + /** + * Called when the user cancels speech recognition. + */ + public void onCancelVoice(); + } + + private SpeechRecognizer mSpeechRecognizer; + private RecognitionListener mRecognitionListener; + private RecognitionView mRecognitionView; + private UiListener mUiListener; + private Context mContext; + + /** + * @param context the service or activity in which we're running. + * @param uiHandler object to receive events from VoiceInput. + */ + public VoiceInput(Context context, UiListener uiHandler) { + mLogger = VoiceInputLogger.getLogger(context); + mRecognitionListener = new ImeRecognitionListener(); + mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context); + mSpeechRecognizer.setRecognitionListener(mRecognitionListener); + mUiListener = uiHandler; + mContext = context; + newView(); + + String recommendedPackages = SettingsUtil.getSettingsString( + context.getContentResolver(), + SettingsUtil.LATIN_IME_VOICE_INPUT_RECOMMENDED_PACKAGES, + DEFAULT_RECOMMENDED_PACKAGES); + + mRecommendedList = new Whitelist(); + for (String recommendedPackage : recommendedPackages.split("\\s+")) { + mRecommendedList.addApp(recommendedPackage); + } + + mBlacklist = new Whitelist(); + mBlacklist.addApp("com.android.setupwizard"); + } + + public void setCursorPos(int pos) { + mAfterVoiceInputCursorPos = pos; + } + + public int getCursorPos() { + return mAfterVoiceInputCursorPos; + } + + public void setSelectionSpan(int span) { + mAfterVoiceInputSelectionSpan = span; + } + + public int getSelectionSpan() { + return mAfterVoiceInputSelectionSpan; + } + + public void incrementTextModificationDeleteCount(int count){ + mAfterVoiceInputDeleteCount += count; + // Send up intents for other text modification types + if (mAfterVoiceInputInsertCount > 0) { + logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount); + mAfterVoiceInputInsertCount = 0; + } + if (mAfterVoiceInputInsertPunctuationCount > 0) { + logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount); + mAfterVoiceInputInsertPunctuationCount = 0; + } + + } + + public void incrementTextModificationInsertCount(int count){ + mAfterVoiceInputInsertCount += count; + if (mAfterVoiceInputSelectionSpan > 0) { + // If text was highlighted before inserting the char, count this as + // a delete. + mAfterVoiceInputDeleteCount += mAfterVoiceInputSelectionSpan; + } + // Send up intents for other text modification types + if (mAfterVoiceInputDeleteCount > 0) { + logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount); + mAfterVoiceInputDeleteCount = 0; + } + if (mAfterVoiceInputInsertPunctuationCount > 0) { + logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount); + mAfterVoiceInputInsertPunctuationCount = 0; + } + } + + public void incrementTextModificationInsertPunctuationCount(int count){ + mAfterVoiceInputInsertPunctuationCount += count; + if (mAfterVoiceInputSelectionSpan > 0) { + // If text was highlighted before inserting the char, count this as + // a delete. + mAfterVoiceInputDeleteCount += mAfterVoiceInputSelectionSpan; + } + // Send up intents for aggregated non-punctuation insertions + if (mAfterVoiceInputDeleteCount > 0) { + logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount); + mAfterVoiceInputDeleteCount = 0; + } + if (mAfterVoiceInputInsertCount > 0) { + logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount); + mAfterVoiceInputInsertCount = 0; + } + } + + public void flushAllTextModificationCounters() { + if (mAfterVoiceInputInsertCount > 0) { + logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount); + mAfterVoiceInputInsertCount = 0; + } + if (mAfterVoiceInputDeleteCount > 0) { + logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount); + mAfterVoiceInputDeleteCount = 0; + } + if (mAfterVoiceInputInsertPunctuationCount > 0) { + logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount); + mAfterVoiceInputInsertPunctuationCount = 0; + } + } + + /** + * The configuration of the IME changed and may have caused the views to be layed out + * again. Restore the state of the recognition view. + */ + public void onConfigurationChanged(Configuration configuration) { + mRecognitionView.restoreState(); + mRecognitionView.getView().dispatchConfigurationChanged(configuration); + } + + /** + * @return true if field is blacklisted for voice + */ + public boolean isBlacklistedField(FieldContext context) { + return mBlacklist.matches(context); + } + + /** + * Used to decide whether to show voice input hints for this field, etc. + * + * @return true if field is recommended for voice + */ + public boolean isRecommendedField(FieldContext context) { + return mRecommendedList.matches(context); + } + + /** + * Start listening for speech from the user. This will grab the microphone + * and start updating the view provided by getView(). It is the caller's + * responsibility to ensure that the view is visible to the user at this stage. + * + * @param context the same FieldContext supplied to voiceIsEnabled() + * @param swipe whether this voice input was started by swipe, for logging purposes + */ + public void startListening(FieldContext context, boolean swipe) { + if (DBG) { + Log.d(TAG, "startListening: " + context); + } + + if (mState != DEFAULT) { + Log.w(TAG, "startListening in the wrong status " + mState); + } + + // If everything works ok, the voice input should be already in the correct state. As this + // class can be called by third-party, we call reset just to be on the safe side. + reset(); + + Locale locale = Locale.getDefault(); + String localeString = locale.getLanguage() + "-" + locale.getCountry(); + + mLogger.start(localeString, swipe); + + mState = LISTENING; + + mRecognitionView.showInitializing(); + startListeningAfterInitialization(context); + } + + /** + * Called only when the recognition manager's initialization completed + * + * @param context context with which {@link #startListening(FieldContext, boolean)} was executed + */ + private void startListeningAfterInitialization(FieldContext context) { + Intent intent = makeIntent(); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, ""); + intent.putExtra(EXTRA_RECOGNITION_CONTEXT, context.getBundle()); + intent.putExtra(EXTRA_CALLING_PACKAGE, "VoiceIME"); + intent.putExtra(EXTRA_ALTERNATES, true); + intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, + SettingsUtil.getSettingsInt( + mContext.getContentResolver(), + SettingsUtil.LATIN_IME_MAX_VOICE_RESULTS, + 1)); + // Get endpointer params from Gservices. + // TODO: Consider caching these values for improved performance on slower devices. + final ContentResolver cr = mContext.getContentResolver(); + putEndpointerExtra( + cr, + intent, + SettingsUtil.LATIN_IME_SPEECH_MINIMUM_LENGTH_MILLIS, + EXTRA_SPEECH_MINIMUM_LENGTH_MILLIS, + null /* rely on endpointer default */); + putEndpointerExtra( + cr, + intent, + SettingsUtil.LATIN_IME_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, + EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, + INPUT_COMPLETE_SILENCE_LENGTH_DEFAULT_VALUE_MILLIS + /* our default value is different from the endpointer's */); + putEndpointerExtra( + cr, + intent, + SettingsUtil. + LATIN_IME_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, + EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, + null /* rely on endpointer default */); + + mSpeechRecognizer.startListening(intent); + } + + /** + * Gets the value of the provided Gservices key, attempts to parse it into a long, + * and if successful, puts the long value as an extra in the provided intent. + */ + private void putEndpointerExtra(ContentResolver cr, Intent i, + String gservicesKey, String intentExtraKey, String defaultValue) { + long l = -1; + String s = SettingsUtil.getSettingsString(cr, gservicesKey, defaultValue); + if (s != null) { + try { + l = Long.valueOf(s); + } catch (NumberFormatException e) { + Log.e(TAG, "could not parse value for " + gservicesKey + ": " + s); + } + } + + if (l != -1) i.putExtra(intentExtraKey, l); + } + + public void destroy() { + mSpeechRecognizer.destroy(); + } + + /** + * Creates a new instance of the view that is returned by {@link #getView()} + * Clients should use this when a previously returned view is stuck in a + * layout that is being thrown away and a new one is need to show to the + * user. + */ + public void newView() { + mRecognitionView = new RecognitionView(mContext, this); + } + + /** + * @return a view that shows the recognition flow--e.g., "Speak now" and + * "working" dialogs. + */ + public View getView() { + return mRecognitionView.getView(); + } + + /** + * Handle the cancel button. + */ + @Override + public void onClick(View view) { + switch(view.getId()) { + case R.id.button: + cancel(); + break; + } + } + + public void logTextModifiedByTypingInsertion(int length) { + mLogger.textModifiedByTypingInsertion(length); + } + + public void logTextModifiedByTypingInsertionPunctuation(int length) { + mLogger.textModifiedByTypingInsertionPunctuation(length); + } + + public void logTextModifiedByTypingDeletion(int length) { + mLogger.textModifiedByTypingDeletion(length); + } + + public void logTextModifiedByChooseSuggestion(String suggestion, int index, + String wordSeparators, InputConnection ic) { + String wordToBeReplaced = EditingUtils.getWordAtCursor(ic, wordSeparators); + // If we enable phrase-based alternatives, only send up the first word + // in suggestion and wordToBeReplaced. + mLogger.textModifiedByChooseSuggestion(suggestion.length(), wordToBeReplaced.length(), + index, wordToBeReplaced, suggestion); + } + + public void logKeyboardWarningDialogShown() { + mLogger.keyboardWarningDialogShown(); + } + + public void logKeyboardWarningDialogDismissed() { + mLogger.keyboardWarningDialogDismissed(); + } + + public void logKeyboardWarningDialogOk() { + mLogger.keyboardWarningDialogOk(); + } + + public void logKeyboardWarningDialogCancel() { + mLogger.keyboardWarningDialogCancel(); + } + + public void logSwipeHintDisplayed() { + mLogger.swipeHintDisplayed(); + } + + public void logPunctuationHintDisplayed() { + mLogger.punctuationHintDisplayed(); + } + + public void logVoiceInputDelivered(int length) { + mLogger.voiceInputDelivered(length); + } + + public void logInputEnded() { + mLogger.inputEnded(); + } + + public void flushLogs() { + mLogger.flush(); + } + + private static Intent makeIntent() { + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + + // On Cupcake, use VoiceIMEHelper since VoiceSearch doesn't support. + // On Donut, always use VoiceSearch, since VoiceIMEHelper and + // VoiceSearch may conflict. + if (Build.VERSION.RELEASE.equals("1.5")) { + intent = intent.setClassName( + "com.google.android.voiceservice", + "com.google.android.voiceservice.IMERecognitionService"); + } else { + intent = intent.setClassName( + "com.google.android.voicesearch", + "com.google.android.voicesearch.RecognitionService"); + } + + return intent; + } + + /** + * Reset the current voice recognition. + */ + public void reset() { + if (mState != DEFAULT) { + mState = DEFAULT; + + // Remove all pending tasks (e.g., timers to cancel voice input) + mHandler.removeMessages(MSG_RESET); + + mSpeechRecognizer.cancel(); + mRecognitionView.finish(); + } + } + + /** + * Cancel in-progress speech recognition. + */ + public void cancel() { + switch (mState) { + case LISTENING: + mLogger.cancelDuringListening(); + break; + case WORKING: + mLogger.cancelDuringWorking(); + break; + case ERROR: + mLogger.cancelDuringError(); + break; + } + + reset(); + mUiListener.onCancelVoice(); + } + + private int getErrorStringId(int errorType, boolean endpointed) { + switch (errorType) { + // We use CLIENT_ERROR to signify that voice search is not available on the device. + case SpeechRecognizer.ERROR_CLIENT: + return R.string.voice_not_installed; + case SpeechRecognizer.ERROR_NETWORK: + return R.string.voice_network_error; + case SpeechRecognizer.ERROR_NETWORK_TIMEOUT: + return endpointed ? + R.string.voice_network_error : R.string.voice_too_much_speech; + case SpeechRecognizer.ERROR_AUDIO: + return R.string.voice_audio_error; + case SpeechRecognizer.ERROR_SERVER: + return R.string.voice_server_error; + case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: + return R.string.voice_speech_timeout; + case SpeechRecognizer.ERROR_NO_MATCH: + return R.string.voice_no_match; + default: return R.string.voice_error; + } + } + + private void onError(int errorType, boolean endpointed) { + Log.i(TAG, "error " + errorType); + mLogger.error(errorType); + onError(mContext.getString(getErrorStringId(errorType, endpointed))); + } + + private void onError(String error) { + mState = ERROR; + mRecognitionView.showError(error); + // Wait a couple seconds and then automatically dismiss message. + mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_RESET), 2000); + } + + private class ImeRecognitionListener implements RecognitionListener { + // Waveform data + final ByteArrayOutputStream mWaveBuffer = new ByteArrayOutputStream(); + int mSpeechStart; + private boolean mEndpointed = false; + + @Override + public void onReadyForSpeech(Bundle noiseParams) { + mRecognitionView.showListening(); + } + + @Override + public void onBeginningOfSpeech() { + mEndpointed = false; + mSpeechStart = mWaveBuffer.size(); + } + + @Override + public void onRmsChanged(float rmsdB) { + mRecognitionView.updateVoiceMeter(rmsdB); + } + + @Override + public void onBufferReceived(byte[] buf) { + try { + mWaveBuffer.write(buf); + } catch (IOException e) { + // ignore. + } + } + + @Override + public void onEndOfSpeech() { + mEndpointed = true; + mState = WORKING; + mRecognitionView.showWorking(mWaveBuffer, mSpeechStart, mWaveBuffer.size()); + } + + @Override + public void onError(int errorType) { + mState = ERROR; + VoiceInput.this.onError(errorType, mEndpointed); + } + + @Override + public void onResults(Bundle resultsBundle) { + List results = resultsBundle + .getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); + // VS Market update is needed for IME froyo clients to access the alternatesBundle + // TODO: verify this. + Bundle alternatesBundle = resultsBundle.getBundle(ALTERNATES_BUNDLE); + mState = DEFAULT; + + final Map> alternatives = + new HashMap>(); + + if (ENABLE_WORD_CORRECTIONS && alternatesBundle != null && results.size() > 0) { + // Use the top recognition result to map each alternative's start:length to a word. + String[] words = results.get(0).split(" "); + Bundle spansBundle = alternatesBundle.getBundle(AlternatesBundleKeys.SPANS); + for (String key : spansBundle.keySet()) { + // Get the word for which these alternates correspond to. + Bundle spanBundle = spansBundle.getBundle(key); + int start = spanBundle.getInt(AlternatesBundleKeys.START); + int length = spanBundle.getInt(AlternatesBundleKeys.LENGTH); + // Only keep single-word based alternatives. + if (length == 1 && start < words.length) { + // Get the alternatives associated with the span. + // If a word appears twice in a recognition result, + // concatenate the alternatives for the word. + List altList = alternatives.get(words[start]); + if (altList == null) { + altList = new ArrayList(); + alternatives.put(words[start], altList); + } + Parcelable[] alternatesArr = spanBundle + .getParcelableArray(AlternatesBundleKeys.ALTERNATES); + for (int j = 0; j < alternatesArr.length && + altList.size() < MAX_ALT_LIST_LENGTH; j++) { + Bundle alternateBundle = (Bundle) alternatesArr[j]; + String alternate = alternateBundle.getString(AlternatesBundleKeys.TEXT); + // Don't allow duplicates in the alternates list. + if (!altList.contains(alternate)) { + altList.add(alternate); + } + } + } + } + } + + if (results.size() > 5) { + results = results.subList(0, 5); + } + mUiListener.onVoiceResults(results, alternatives); + mRecognitionView.finish(); + } + + @Override + public void onPartialResults(final Bundle partialResults) { + // currently - do nothing + } + + @Override + public void onEvent(int eventType, Bundle params) { + // do nothing - reserved for events that might be added in the future + } + } +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java new file mode 100644 index 000000000..394193c9e --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/VoiceInputLogger.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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.deprecated.voice; + +import com.android.common.speech.LoggingEvents; +import com.android.common.userhappiness.UserHappinessSignals; + +import android.content.Context; +import android.content.Intent; + +/** + * Provides the logging facility for voice input events. This fires broadcasts back to + * the voice search app which then logs on our behalf. + * + * Note that debug console logging does not occur in this class. If you want to + * see console output of these logging events, there is a boolean switch to turn + * on on the VoiceSearch side. + */ +public class VoiceInputLogger { + @SuppressWarnings("unused") + private static final String TAG = VoiceInputLogger.class.getSimpleName(); + + private static VoiceInputLogger sVoiceInputLogger; + + private final Context mContext; + + // The base intent used to form all broadcast intents to the logger + // in VoiceSearch. + private final Intent mBaseIntent; + + // This flag is used to indicate when there are voice events that + // need to be flushed. + private boolean mHasLoggingInfo = false; + + /** + * Returns the singleton of the logger. + * + * @param contextHint a hint context used when creating the logger instance. + * Ignored if the singleton instance already exists. + */ + public static synchronized VoiceInputLogger getLogger(Context contextHint) { + if (sVoiceInputLogger == null) { + sVoiceInputLogger = new VoiceInputLogger(contextHint); + } + return sVoiceInputLogger; + } + + public VoiceInputLogger(Context context) { + mContext = context; + + mBaseIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT); + mBaseIntent.putExtra(LoggingEvents.EXTRA_APP_NAME, LoggingEvents.VoiceIme.APP_NAME); + } + + private Intent newLoggingBroadcast(int event) { + Intent i = new Intent(mBaseIntent); + i.putExtra(LoggingEvents.EXTRA_EVENT, event); + return i; + } + + public void flush() { + if (hasLoggingInfo()) { + Intent i = new Intent(mBaseIntent); + i.putExtra(LoggingEvents.EXTRA_FLUSH, true); + mContext.sendBroadcast(i); + setHasLoggingInfo(false); + } + } + + public void keyboardWarningDialogShown() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_SHOWN)); + } + + public void keyboardWarningDialogDismissed() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_DISMISSED)); + } + + public void keyboardWarningDialogOk() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_OK)); + } + + public void keyboardWarningDialogCancel() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_CANCEL)); + } + + public void settingsWarningDialogShown() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_SHOWN)); + } + + public void settingsWarningDialogDismissed() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_DISMISSED)); + } + + public void settingsWarningDialogOk() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_OK)); + } + + public void settingsWarningDialogCancel() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_CANCEL)); + } + + public void swipeHintDisplayed() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.SWIPE_HINT_DISPLAYED)); + } + + public void cancelDuringListening() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_LISTENING)); + } + + public void cancelDuringWorking() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_WORKING)); + } + + public void cancelDuringError() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_ERROR)); + } + + public void punctuationHintDisplayed() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.PUNCTUATION_HINT_DISPLAYED)); + } + + public void error(int code) { + setHasLoggingInfo(true); + Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.ERROR); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_ERROR_CODE, code); + mContext.sendBroadcast(i); + } + + public void start(String locale, boolean swipe) { + setHasLoggingInfo(true); + Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.START); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_START_LOCALE, locale); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_START_SWIPE, swipe); + i.putExtra(LoggingEvents.EXTRA_TIMESTAMP, System.currentTimeMillis()); + mContext.sendBroadcast(i); + } + + public void voiceInputDelivered(int length) { + setHasLoggingInfo(true); + Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.VOICE_INPUT_DELIVERED); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); + mContext.sendBroadcast(i); + } + + public void textModifiedByTypingInsertion(int length) { + setHasLoggingInfo(true); + Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, + LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_INSERTION); + mContext.sendBroadcast(i); + } + + public void textModifiedByTypingInsertionPunctuation(int length) { + setHasLoggingInfo(true); + Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, + LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_INSERTION_PUNCTUATION); + mContext.sendBroadcast(i); + } + + public void textModifiedByTypingDeletion(int length) { + setHasLoggingInfo(true); + Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, + LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_DELETION); + + mContext.sendBroadcast(i); + } + + + public void textModifiedByChooseSuggestion(int suggestionLength, int replacedPhraseLength, + int index, String before, String after) { + setHasLoggingInfo(true); + Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, + LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION); + + i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_BEFORE_N_BEST_CHOOSE, before); + i.putExtra(LoggingEvents.VoiceIme.EXTRA_AFTER_N_BEST_CHOOSE, after); + mContext.sendBroadcast(i); + } + + public void inputEnded() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.INPUT_ENDED)); + } + + public void voiceInputSettingEnabled() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.VOICE_INPUT_SETTING_ENABLED)); + } + + public void voiceInputSettingDisabled() { + setHasLoggingInfo(true); + mContext.sendBroadcast(newLoggingBroadcast( + LoggingEvents.VoiceIme.VOICE_INPUT_SETTING_DISABLED)); + } + + private void setHasLoggingInfo(boolean hasLoggingInfo) { + mHasLoggingInfo = hasLoggingInfo; + // If applications that call UserHappinessSignals.userAcceptedImeText + // make that call after VoiceInputLogger.flush() calls this method with false, we + // will lose those happiness signals. For example, consider the gmail sequence: + // 1. compose message + // 2. speak message into message field + // 3. type subject into subject field + // 4. press send + // We will NOT get the signal that the user accepted the voice inputted message text + // because when the user tapped on the subject field, the ime's flush will be triggered + // and the hasLoggingInfo will be then set to false. So by the time the user hits send + // we have essentially forgotten about any voice input. + // However the following (more common) use case is properly logged + // 1. compose message + // 2. type subject in subject field + // 3. speak message in message field + // 4. press send + UserHappinessSignals.setHasVoiceLoggingInfo(hasLoggingInfo); + } + + private boolean hasLoggingInfo(){ + return mHasLoggingInfo; + } + +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java new file mode 100644 index 000000000..a3025f252 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/WaveformImage.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * 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.deprecated.voice; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; + +/** + * Utility class to draw a waveform into a bitmap, given a byte array + * that represents the waveform as a sequence of 16-bit integers. + * Adapted from RecognitionActivity.java. + */ +public class WaveformImage { + private static final int SAMPLING_RATE = 8000; + + private WaveformImage() { + // Intentional empty constructor. + } + + public static Bitmap drawWaveform( + ByteArrayOutputStream waveBuffer, int w, int h, int start, int end) { + final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + final Canvas c = new Canvas(b); + final Paint paint = new Paint(); + paint.setColor(0xFFFFFFFF); // 0xRRGGBBAA + paint.setAntiAlias(true); + paint.setStrokeWidth(0); + + final ShortBuffer buf = ByteBuffer + .wrap(waveBuffer.toByteArray()) + .order(ByteOrder.nativeOrder()) + .asShortBuffer(); + buf.position(0); + + final int numSamples = waveBuffer.size() / 2; + final int delay = (SAMPLING_RATE * 100 / 1000); + int endIndex = end / 2 + delay; + if (end == 0 || endIndex >= numSamples) { + endIndex = numSamples; + } + int index = start / 2 - delay; + if (index < 0) { + index = 0; + } + final int size = endIndex - index; + int numSamplePerPixel = 32; + int delta = size / (numSamplePerPixel * w); + if (delta == 0) { + numSamplePerPixel = size / w; + delta = 1; + } + + final float scale = 3.5f / 65536.0f; + // do one less column to make sure we won't read past + // the buffer. + try { + for (int i = 0; i < w - 1 ; i++) { + final float x = i; + for (int j = 0; j < numSamplePerPixel; j++) { + final short s = buf.get(index); + final float y = (h / 2) - (s * h * scale); + c.drawPoint(x, y, paint); + index += delta; + } + } + } catch (IndexOutOfBoundsException e) { + // this can happen, but we don't care + } + + return b; + } +} diff --git a/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java new file mode 100644 index 000000000..310689cb2 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/voice/Whitelist.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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.deprecated.voice; + +import android.os.Bundle; + +import java.util.ArrayList; +import java.util.List; + +/** + * A set of text fields where speech has been explicitly enabled. + */ +public class Whitelist { + private List mConditions; + + public Whitelist() { + mConditions = new ArrayList(); + } + + public Whitelist(List conditions) { + this.mConditions = conditions; + } + + public void addApp(String app) { + Bundle bundle = new Bundle(); + bundle.putString("packageName", app); + mConditions.add(bundle); + } + + /** + * @return true if the field is a member of the whitelist. + */ + public boolean matches(FieldContext context) { + for (Bundle condition : mConditions) { + if (matches(condition, context.getBundle())) { + return true; + } + } + return false; + } + + /** + * @return true of all values in condition are matched by a value + * in target. + */ + private boolean matches(Bundle condition, Bundle target) { + for (String key : condition.keySet()) { + if (!condition.getString(key).equals(target.getString(key))) { + return false; + } + } + return true; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index 77e9caecc..bba3e0dc3 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -16,9 +16,9 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.deprecated.VoiceConnector; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.Utils; -import com.android.inputmethod.voice.VoiceIMEConnector; import android.content.Context; import android.graphics.Canvas; @@ -264,6 +264,6 @@ public class LatinKeyboardView extends KeyboardView { @Override protected void onAttachedToWindow() { // Token is available from here. - VoiceIMEConnector.getInstance().onAttachedToWindow(); + VoiceConnector.getInstance().onAttachedToWindow(); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 75391a9cd..3d1b4e031 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.deprecated.VoiceConnector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardSwitcher; @@ -23,7 +24,6 @@ import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.LatinKeyboard; import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.latin.Utils.RingCharBuffer; -import com.android.inputmethod.voice.VoiceIMEConnector; import android.app.AlertDialog; import android.content.BroadcastReceiver; @@ -146,7 +146,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private String mInputMethodId; private KeyboardSwitcher mKeyboardSwitcher; private SubtypeSwitcher mSubtypeSwitcher; - private VoiceIMEConnector mVoiceConnector; + private VoiceConnector mVoiceConnector; private UserDictionary mUserDictionary; private UserBigramDictionary mUserBigramDictionary; @@ -419,7 +419,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mReceiver, filter); - mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler); + mVoiceConnector = VoiceConnector.init(this, prefs, mHandler); } private void initSuggest() { @@ -531,7 +531,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to // know now whether this is a password text field, because we need to know now whether we // want to enable the voice button. - final VoiceIMEConnector voiceIme = mVoiceConnector; + final VoiceConnector voiceIme = mVoiceConnector; voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) || Utils.isVisiblePasswordInputType(attribute.inputType)); diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 341d5add0..15ea62c7a 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -16,8 +16,7 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.voice.VoiceIMEConnector; -import com.android.inputmethod.voice.VoiceInputLogger; +import com.android.inputmethod.deprecated.VoiceConnector; import android.app.AlertDialog; import android.app.Dialog; @@ -82,7 +81,7 @@ public class Settings extends PreferenceActivity private AlertDialog mDialog; - private VoiceInputLogger mLogger; + private VoiceConnector.VoiceLoggerConnector mVoiceLogger; private boolean mOkClicked = false; private String mVoiceModeOff; @@ -111,7 +110,7 @@ public class Settings extends PreferenceActivity mVoiceModeOff = getString(R.string.voice_mode_off); mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff) .equals(mVoiceModeOff)); - mLogger = VoiceInputLogger.getLogger(this); + mVoiceLogger = VoiceConnector.VoiceLoggerConnector.getInstance(this); mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD); mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS); @@ -184,7 +183,7 @@ public class Settings extends PreferenceActivity ((PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY)) .removePreference(mQuickFixes); } - if (!VoiceIMEConnector.VOICE_INSTALLED + if (!VoiceConnector.VOICE_INSTALLED || !SpeechRecognizer.isRecognitionAvailable(this)) { getPreferenceScreen().removePreference(mVoicePreference); } else { @@ -277,10 +276,10 @@ public class Settings extends PreferenceActivity public void onClick(DialogInterface dialog, int whichButton) { if (whichButton == DialogInterface.BUTTON_NEGATIVE) { mVoicePreference.setValue(mVoiceModeOff); - mLogger.settingsWarningDialogCancel(); + mVoiceLogger.settingsWarningDialogCancel(); } else if (whichButton == DialogInterface.BUTTON_POSITIVE) { mOkClicked = true; - mLogger.settingsWarningDialogOk(); + mVoiceLogger.settingsWarningDialogOk(); } updateVoicePreference(); } @@ -311,7 +310,7 @@ public class Settings extends PreferenceActivity AlertDialog dialog = builder.create(); mDialog = dialog; dialog.setOnDismissListener(this); - mLogger.settingsWarningDialogShown(); + mVoiceLogger.settingsWarningDialogShown(); return dialog; default: Log.e(TAG, "unknown dialog " + id); @@ -321,7 +320,7 @@ public class Settings extends PreferenceActivity @Override public void onDismiss(DialogInterface dialog) { - mLogger.settingsWarningDialogDismissed(); + mVoiceLogger.settingsWarningDialogDismissed(); if (!mOkClicked) { // This assumes that onPreferenceClick gets called first, and this if the user // agreed after the warning, we set the mOkClicked value to true. @@ -331,10 +330,6 @@ public class Settings extends PreferenceActivity private void updateVoicePreference() { boolean isChecked = !mVoicePreference.getValue().equals(mVoiceModeOff); - if (isChecked) { - mLogger.voiceInputSettingEnabled(); - } else { - mLogger.voiceInputSettingDisabled(); - } + mVoiceLogger.voiceInputSettingEnabled(isChecked); } } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index dc14d770a..25e52c672 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -16,11 +16,9 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.deprecated.VoiceConnector; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.LatinKeyboard; -import com.android.inputmethod.voice.SettingsUtil; -import com.android.inputmethod.voice.VoiceIMEConnector; -import com.android.inputmethod.voice.VoiceInput; import android.content.Context; import android.content.Intent; @@ -78,7 +76,7 @@ public class SubtypeSwitcher { private Locale mSystemLocale; private Locale mInputLocale; private String mInputLocaleStr; - private VoiceInput mVoiceInput; + private VoiceConnector.VoiceInputConnector mVoiceInputConnector; /*-----------------------------------------------------------*/ private boolean mIsNetworkConnected; @@ -113,7 +111,7 @@ public class SubtypeSwitcher { mCurrentSubtype = null; mAllEnabledSubtypesOfCurrentInputMethod = null; // TODO: Voice input should be created here - mVoiceInput = null; + mVoiceInputConnector = null; mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean( R.bool.config_use_spacebar_language_switcher); if (mConfigUseSpacebarLanguageSwitcher) @@ -243,30 +241,30 @@ public class SubtypeSwitcher { // We cancel its status when we change mode, while we reset otherwise. if (isKeyboardMode()) { if (modeChanged) { - if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { - mVoiceInput.cancel(); + if (VOICE_MODE.equals(oldMode) && mVoiceInputConnector != null) { + mVoiceInputConnector.cancel(); } } if (modeChanged || languageChanged) { updateShortcutIME(); mService.onRefreshKeyboard(); } - } else if (isVoiceMode() && mVoiceInput != null) { + } else if (isVoiceMode() && mVoiceInputConnector != null) { if (VOICE_MODE.equals(oldMode)) { - mVoiceInput.reset(); + mVoiceInputConnector.reset(); } // If needsToShowWarningDialog is true, voice input need to show warning before // show recognition view. if (languageChanged || modeChanged - || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) { + || VoiceConnector.getInstance().needsToShowWarningDialog()) { triggerVoiceIME(); } } else { Log.w(TAG, "Unknown subtype mode: " + newMode); - if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { + if (VOICE_MODE.equals(oldMode) && mVoiceInputConnector != null) { // We need to reset the voice input to release the resources and to reset its status // as it is not the current input mode. - mVoiceInput.reset(); + mVoiceInputConnector.reset(); } } } @@ -507,9 +505,9 @@ public class SubtypeSwitcher { // Voice Input functions // /////////////////////////// - public boolean setVoiceInput(VoiceInput vi) { - if (mVoiceInput == null && vi != null) { - mVoiceInput = vi; + public boolean setVoiceInputConnector(VoiceConnector.VoiceInputConnector vi) { + if (mVoiceInputConnector == null && vi != null) { + mVoiceInputConnector = vi; if (isVoiceMode()) { if (DBG) { Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr()); @@ -527,7 +525,7 @@ public class SubtypeSwitcher { private void triggerVoiceIME() { if (!mService.isInputViewShown()) return; - VoiceIMEConnector.getInstance().startListening(false, + VoiceConnector.getInstance().startListening(false, KeyboardSwitcher.getInstance().getInputView().getWindowToken()); } @@ -612,30 +610,14 @@ public class SubtypeSwitcher { } - // A list of locales which are supported by default for voice input, unless we get a - // different list from Gservices. - private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = - "en " + - "en_US " + - "en_GB " + - "en_AU " + - "en_CA " + - "en_IE " + - "en_IN " + - "en_NZ " + - "en_SG " + - "en_ZA "; - public boolean isVoiceSupported(String locale) { // Get the current list of supported locales and check the current locale against that // list. We cache this value so as not to check it every time the user starts a voice // input. Because this method is called by onStartInputView, this should mean that as // long as the locale doesn't change while the user is keeping the IME open, the // value should never be stale. - String supportedLocalesString = SettingsUtil.getSettingsString( - mService.getContentResolver(), - SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, - DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); + String supportedLocalesString = VoiceConnector.getSupportedLocalesString( + mService.getContentResolver()); List voiceInputSupportedLocales = Arrays.asList( supportedLocalesString.split("\\s+")); return voiceInputSupportedLocales.contains(locale); diff --git a/java/src/com/android/inputmethod/voice/FieldContext.java b/java/src/com/android/inputmethod/voice/FieldContext.java deleted file mode 100644 index dfdfbaa9f..000000000 --- a/java/src/com/android/inputmethod/voice/FieldContext.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. - * - * 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.voice; - -import android.os.Bundle; -import android.util.Log; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; - -/** - * Represents information about a given text field, which can be passed - * to the speech recognizer as context information. - */ -public class FieldContext { - private static final boolean DBG = false; - - static final String LABEL = "label"; - static final String HINT = "hint"; - static final String PACKAGE_NAME = "packageName"; - static final String FIELD_ID = "fieldId"; - static final String FIELD_NAME = "fieldName"; - static final String SINGLE_LINE = "singleLine"; - static final String INPUT_TYPE = "inputType"; - static final String IME_OPTIONS = "imeOptions"; - static final String SELECTED_LANGUAGE = "selectedLanguage"; - static final String ENABLED_LANGUAGES = "enabledLanguages"; - - Bundle mFieldInfo; - - public FieldContext(InputConnection conn, EditorInfo info, - String selectedLanguage, String[] enabledLanguages) { - mFieldInfo = new Bundle(); - addEditorInfoToBundle(info, mFieldInfo); - addInputConnectionToBundle(conn, mFieldInfo); - addLanguageInfoToBundle(selectedLanguage, enabledLanguages, mFieldInfo); - if (DBG) Log.i("FieldContext", "Bundle = " + mFieldInfo.toString()); - } - - private static String safeToString(Object o) { - if (o == null) { - return ""; - } - return o.toString(); - } - - private static void addEditorInfoToBundle(EditorInfo info, Bundle bundle) { - if (info == null) { - return; - } - - bundle.putString(LABEL, safeToString(info.label)); - bundle.putString(HINT, safeToString(info.hintText)); - bundle.putString(PACKAGE_NAME, safeToString(info.packageName)); - bundle.putInt(FIELD_ID, info.fieldId); - bundle.putString(FIELD_NAME, safeToString(info.fieldName)); - bundle.putInt(INPUT_TYPE, info.inputType); - bundle.putInt(IME_OPTIONS, info.imeOptions); - } - - @SuppressWarnings("static-access") - private static void addInputConnectionToBundle( - InputConnection conn, Bundle bundle) { - if (conn == null) { - return; - } - - ExtractedText et = conn.getExtractedText(new ExtractedTextRequest(), 0); - if (et == null) { - return; - } - bundle.putBoolean(SINGLE_LINE, (et.flags & et.FLAG_SINGLE_LINE) > 0); - } - - private static void addLanguageInfoToBundle( - String selectedLanguage, String[] enabledLanguages, Bundle bundle) { - bundle.putString(SELECTED_LANGUAGE, selectedLanguage); - bundle.putStringArray(ENABLED_LANGUAGES, enabledLanguages); - } - - public Bundle getBundle() { - return mFieldInfo; - } - - @Override - public String toString() { - return mFieldInfo.toString(); - } -} diff --git a/java/src/com/android/inputmethod/voice/Hints.java b/java/src/com/android/inputmethod/voice/Hints.java deleted file mode 100644 index d11d3b042..000000000 --- a/java/src/com/android/inputmethod/voice/Hints.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. - * - * 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.voice; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SharedPreferencesCompat; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.SharedPreferences; -import android.view.inputmethod.InputConnection; - -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; - -/** - * Logic to determine when to display hints on usage to the user. - */ -public class Hints { - public interface Display { - public void showHint(int viewResource); - } - - private static final String PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN = - "voice_hint_num_unique_days_shown"; - private static final String PREF_VOICE_HINT_LAST_TIME_SHOWN = - "voice_hint_last_time_shown"; - private static final String PREF_VOICE_INPUT_LAST_TIME_USED = - "voice_input_last_time_used"; - private static final String PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT = - "voice_punctuation_hint_view_count"; - private static final int DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW = 7; - private static final int DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS = 7; - - private final Context mContext; - private final SharedPreferences mPrefs; - private final Display mDisplay; - private boolean mVoiceResultContainedPunctuation; - private int mSwipeHintMaxDaysToShow; - private int mPunctuationHintMaxDisplays; - - // Only show punctuation hint if voice result did not contain punctuation. - static final Map SPEAKABLE_PUNCTUATION - = new HashMap(); - static { - SPEAKABLE_PUNCTUATION.put(",", "comma"); - SPEAKABLE_PUNCTUATION.put(".", "period"); - SPEAKABLE_PUNCTUATION.put("?", "question mark"); - } - - public Hints(Context context, SharedPreferences prefs, Display display) { - mContext = context; - mPrefs = prefs; - mDisplay = display; - - ContentResolver cr = mContext.getContentResolver(); - mSwipeHintMaxDaysToShow = SettingsUtil.getSettingsInt( - cr, - SettingsUtil.LATIN_IME_VOICE_INPUT_SWIPE_HINT_MAX_DAYS, - DEFAULT_SWIPE_HINT_MAX_DAYS_TO_SHOW); - mPunctuationHintMaxDisplays = SettingsUtil.getSettingsInt( - cr, - SettingsUtil.LATIN_IME_VOICE_INPUT_PUNCTUATION_HINT_MAX_DISPLAYS, - DEFAULT_PUNCTUATION_HINT_MAX_DISPLAYS); - } - - public boolean showSwipeHintIfNecessary(boolean fieldRecommended) { - if (fieldRecommended && shouldShowSwipeHint()) { - showHint(R.layout.voice_swipe_hint); - return true; - } - - return false; - } - - public boolean showPunctuationHintIfNecessary(InputConnection ic) { - if (!mVoiceResultContainedPunctuation - && ic != null - && getAndIncrementPref(PREF_VOICE_PUNCTUATION_HINT_VIEW_COUNT) - < mPunctuationHintMaxDisplays) { - CharSequence charBeforeCursor = ic.getTextBeforeCursor(1, 0); - if (SPEAKABLE_PUNCTUATION.containsKey(charBeforeCursor)) { - showHint(R.layout.voice_punctuation_hint); - return true; - } - } - - return false; - } - - public void registerVoiceResult(String text) { - // Update the current time as the last time voice input was used. - SharedPreferences.Editor editor = mPrefs.edit(); - editor.putLong(PREF_VOICE_INPUT_LAST_TIME_USED, System.currentTimeMillis()); - SharedPreferencesCompat.apply(editor); - - mVoiceResultContainedPunctuation = false; - for (CharSequence s : SPEAKABLE_PUNCTUATION.keySet()) { - if (text.indexOf(s.toString()) >= 0) { - mVoiceResultContainedPunctuation = true; - break; - } - } - } - - private boolean shouldShowSwipeHint() { - final SharedPreferences prefs = mPrefs; - - int numUniqueDaysShown = prefs.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0); - - // If we've already shown the hint for enough days, we'll return false. - if (numUniqueDaysShown < mSwipeHintMaxDaysToShow) { - - long lastTimeVoiceWasUsed = prefs.getLong(PREF_VOICE_INPUT_LAST_TIME_USED, 0); - - // If the user has used voice today, we'll return false. (We don't show the hint on - // any day that the user has already used voice.) - if (!isFromToday(lastTimeVoiceWasUsed)) { - return true; - } - } - - return false; - } - - /** - * Determines whether the provided time is from some time today (i.e., this day, month, - * and year). - */ - private boolean isFromToday(long timeInMillis) { - if (timeInMillis == 0) return false; - - Calendar today = Calendar.getInstance(); - today.setTimeInMillis(System.currentTimeMillis()); - - Calendar timestamp = Calendar.getInstance(); - timestamp.setTimeInMillis(timeInMillis); - - return (today.get(Calendar.YEAR) == timestamp.get(Calendar.YEAR) && - today.get(Calendar.DAY_OF_MONTH) == timestamp.get(Calendar.DAY_OF_MONTH) && - today.get(Calendar.MONTH) == timestamp.get(Calendar.MONTH)); - } - - private void showHint(int hintViewResource) { - final SharedPreferences prefs = mPrefs; - - int numUniqueDaysShown = prefs.getInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, 0); - long lastTimeHintWasShown = prefs.getLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, 0); - - // If this is the first time the hint is being shown today, increase the saved values - // to represent that. We don't need to increase the last time the hint was shown unless - // it is a different day from the current value. - if (!isFromToday(lastTimeHintWasShown)) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(PREF_VOICE_HINT_NUM_UNIQUE_DAYS_SHOWN, numUniqueDaysShown + 1); - editor.putLong(PREF_VOICE_HINT_LAST_TIME_SHOWN, System.currentTimeMillis()); - SharedPreferencesCompat.apply(editor); - } - - if (mDisplay != null) { - mDisplay.showHint(hintViewResource); - } - } - - private int getAndIncrementPref(String pref) { - final SharedPreferences prefs = mPrefs; - int value = prefs.getInt(pref, 0); - SharedPreferences.Editor editor = prefs.edit(); - editor.putInt(pref, value + 1); - SharedPreferencesCompat.apply(editor); - return value; - } -} diff --git a/java/src/com/android/inputmethod/voice/RecognitionView.java b/java/src/com/android/inputmethod/voice/RecognitionView.java deleted file mode 100644 index 95a79f463..000000000 --- a/java/src/com/android/inputmethod/voice/RecognitionView.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. - * - * 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.voice; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SubtypeSwitcher; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.CornerPathEffect; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PathEffect; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.ShortBuffer; -import java.util.Locale; - -/** - * The user interface for the "Speak now" and "working" states. - * Displays a recognition dialog (with waveform, voice meter, etc.), - * plays beeps, shows errors, etc. - */ -public class RecognitionView { - @SuppressWarnings("unused") - private static final String TAG = "RecognitionView"; - - private Handler mUiHandler; // Reference to UI thread - private View mView; - private Context mContext; - - private TextView mText; - private ImageView mImage; - private View mProgress; - private SoundIndicator mSoundIndicator; - private TextView mLanguage; - private Button mButton; - - private Drawable mInitializing; - private Drawable mError; - - private static final int INIT = 0; - private static final int LISTENING = 1; - private static final int WORKING = 2; - private static final int READY = 3; - - private int mState = INIT; - - private final View mPopupLayout; - - private final Drawable mListeningBorder; - private final Drawable mWorkingBorder; - private final Drawable mErrorBorder; - - public RecognitionView(Context context, OnClickListener clickListener) { - mUiHandler = new Handler(); - - LayoutInflater inflater = (LayoutInflater) context.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - - mView = inflater.inflate(R.layout.recognition_status, null); - - mPopupLayout= mView.findViewById(R.id.popup_layout); - - // Pre-load volume level images - Resources r = context.getResources(); - - mListeningBorder = r.getDrawable(R.drawable.vs_dialog_red); - mWorkingBorder = r.getDrawable(R.drawable.vs_dialog_blue); - mErrorBorder = r.getDrawable(R.drawable.vs_dialog_yellow); - - mInitializing = r.getDrawable(R.drawable.mic_slash); - mError = r.getDrawable(R.drawable.caution); - - mImage = (ImageView) mView.findViewById(R.id.image); - mProgress = mView.findViewById(R.id.progress); - mSoundIndicator = (SoundIndicator) mView.findViewById(R.id.sound_indicator); - - mButton = (Button) mView.findViewById(R.id.button); - mButton.setOnClickListener(clickListener); - mText = (TextView) mView.findViewById(R.id.text); - mLanguage = (TextView) mView.findViewById(R.id.language); - - mContext = context; - } - - public View getView() { - return mView; - } - - public void restoreState() { - mUiHandler.post(new Runnable() { - @Override - public void run() { - // Restart the spinner - if (mState == WORKING) { - ((ProgressBar) mProgress).setIndeterminate(false); - ((ProgressBar) mProgress).setIndeterminate(true); - } - } - }); - } - - public void showInitializing() { - mUiHandler.post(new Runnable() { - @Override - public void run() { - mState = INIT; - prepareDialog(mContext.getText(R.string.voice_initializing), mInitializing, - mContext.getText(R.string.cancel)); - } - }); - } - - public void showListening() { - Log.d(TAG, "#showListening"); - mUiHandler.post(new Runnable() { - @Override - public void run() { - mState = LISTENING; - prepareDialog(mContext.getText(R.string.voice_listening), null, - mContext.getText(R.string.cancel)); - } - }); - } - - public void updateVoiceMeter(float rmsdB) { - mSoundIndicator.setRmsdB(rmsdB); - } - - public void showError(final String message) { - mUiHandler.post(new Runnable() { - @Override - public void run() { - mState = READY; - prepareDialog(message, mError, mContext.getText(R.string.ok)); - } - }); - } - - public void showWorking( - final ByteArrayOutputStream waveBuffer, - final int speechStartPosition, - final int speechEndPosition) { - mUiHandler.post(new Runnable() { - @Override - public void run() { - mState = WORKING; - prepareDialog(mContext.getText(R.string.voice_working), null, mContext - .getText(R.string.cancel)); - final ShortBuffer buf = ByteBuffer.wrap(waveBuffer.toByteArray()).order( - ByteOrder.nativeOrder()).asShortBuffer(); - buf.position(0); - waveBuffer.reset(); - showWave(buf, speechStartPosition / 2, speechEndPosition / 2); - } - }); - } - - private void prepareDialog(CharSequence text, Drawable image, - CharSequence btnTxt) { - - /* - * The mic of INIT and of LISTENING has to be displayed in the same position. To accomplish - * that, some text visibility are not set as GONE but as INVISIBLE. - */ - switch (mState) { - case INIT: - mText.setVisibility(View.INVISIBLE); - - mProgress.setVisibility(View.GONE); - - mImage.setVisibility(View.VISIBLE); - mImage.setImageResource(R.drawable.mic_slash); - - mSoundIndicator.setVisibility(View.GONE); - mSoundIndicator.stop(); - - mLanguage.setVisibility(View.INVISIBLE); - - mPopupLayout.setBackgroundDrawable(mListeningBorder); - break; - case LISTENING: - mText.setVisibility(View.VISIBLE); - mText.setText(text); - - mProgress.setVisibility(View.GONE); - - mImage.setVisibility(View.GONE); - - mSoundIndicator.setVisibility(View.VISIBLE); - mSoundIndicator.start(); - - Locale locale = SubtypeSwitcher.getInstance().getInputLocale(); - - mLanguage.setVisibility(View.VISIBLE); - mLanguage.setText(SubtypeSwitcher.getFullDisplayName(locale, true)); - - mPopupLayout.setBackgroundDrawable(mListeningBorder); - break; - case WORKING: - - mText.setVisibility(View.VISIBLE); - mText.setText(text); - - mProgress.setVisibility(View.VISIBLE); - - mImage.setVisibility(View.VISIBLE); - - mSoundIndicator.setVisibility(View.GONE); - mSoundIndicator.stop(); - - mLanguage.setVisibility(View.GONE); - - mPopupLayout.setBackgroundDrawable(mWorkingBorder); - break; - case READY: - mText.setVisibility(View.VISIBLE); - mText.setText(text); - - mProgress.setVisibility(View.GONE); - - mImage.setVisibility(View.VISIBLE); - mImage.setImageResource(R.drawable.caution); - - mSoundIndicator.setVisibility(View.GONE); - mSoundIndicator.stop(); - - mLanguage.setVisibility(View.GONE); - - mPopupLayout.setBackgroundDrawable(mErrorBorder); - break; - default: - Log.w(TAG, "Unknown state " + mState); - } - mPopupLayout.requestLayout(); - mButton.setText(btnTxt); - } - - /** - * @return an average abs of the specified buffer. - */ - private static int getAverageAbs(ShortBuffer buffer, int start, int i, int npw) { - int from = start + i * npw; - int end = from + npw; - int total = 0; - for (int x = from; x < end; x++) { - total += Math.abs(buffer.get(x)); - } - return total / npw; - } - - - /** - * Shows waveform of input audio. - * - * Copied from version in VoiceSearch's RecognitionActivity. - * - * TODO: adjust stroke width based on the size of data. - * TODO: use dip rather than pixels. - */ - private void showWave(ShortBuffer waveBuffer, int startPosition, int endPosition) { - final int w = ((View) mImage.getParent()).getWidth(); - final int h = ((View) mImage.getParent()).getHeight(); - if (w <= 0 || h <= 0) { - // view is not visible this time. Skip drawing. - return; - } - final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - final Canvas c = new Canvas(b); - final Paint paint = new Paint(); - paint.setColor(0xFFFFFFFF); // 0xAARRGGBB - paint.setAntiAlias(true); - paint.setStyle(Paint.Style.STROKE); - paint.setAlpha(80); - - final PathEffect effect = new CornerPathEffect(3); - paint.setPathEffect(effect); - - final int numSamples = waveBuffer.remaining(); - int endIndex; - if (endPosition == 0) { - endIndex = numSamples; - } else { - endIndex = Math.min(endPosition, numSamples); - } - - int startIndex = startPosition - 2000; // include 250ms before speech - if (startIndex < 0) { - startIndex = 0; - } - final int numSamplePerWave = 200; // 8KHz 25ms = 200 samples - final float scale = 10.0f / 65536.0f; - - final int count = (endIndex - startIndex) / numSamplePerWave; - final float deltaX = 1.0f * w / count; - int yMax = h / 2; - Path path = new Path(); - c.translate(0, yMax); - float x = 0; - path.moveTo(x, 0); - for (int i = 0; i < count; i++) { - final int avabs = getAverageAbs(waveBuffer, startIndex, i , numSamplePerWave); - int sign = ( (i & 01) == 0) ? -1 : 1; - final float y = Math.min(yMax, avabs * h * scale) * sign; - path.lineTo(x, y); - x += deltaX; - path.lineTo(x, y); - } - if (deltaX > 4) { - paint.setStrokeWidth(2); - } else { - paint.setStrokeWidth(Math.max(0, (int) (deltaX -.05))); - } - c.drawPath(path, paint); - mImage.setImageBitmap(b); - } - - public void finish() { - mUiHandler.post(new Runnable() { - @Override - public void run() { - mSoundIndicator.stop(); - } - }); - } -} diff --git a/java/src/com/android/inputmethod/voice/SettingsUtil.java b/java/src/com/android/inputmethod/voice/SettingsUtil.java deleted file mode 100644 index 4d746e120..000000000 --- a/java/src/com/android/inputmethod/voice/SettingsUtil.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. - * - * 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.voice; - -import android.content.ContentResolver; -import android.provider.Settings; - -/** - * Utility for retrieving settings from Settings.Secure. - */ -public class SettingsUtil { - /** - * A whitespace-separated list of supported locales for voice input from the keyboard. - */ - public static final String LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES = - "latin_ime_voice_input_supported_locales"; - - /** - * A whitespace-separated list of recommended app packages for voice input from the - * keyboard. - */ - public static final String LATIN_IME_VOICE_INPUT_RECOMMENDED_PACKAGES = - "latin_ime_voice_input_recommended_packages"; - - /** - * The maximum number of unique days to show the swipe hint for voice input. - */ - public static final String LATIN_IME_VOICE_INPUT_SWIPE_HINT_MAX_DAYS = - "latin_ime_voice_input_swipe_hint_max_days"; - - /** - * The maximum number of times to show the punctuation hint for voice input. - */ - public static final String LATIN_IME_VOICE_INPUT_PUNCTUATION_HINT_MAX_DISPLAYS = - "latin_ime_voice_input_punctuation_hint_max_displays"; - - /** - * Endpointer parameters for voice input from the keyboard. - */ - public static final String LATIN_IME_SPEECH_MINIMUM_LENGTH_MILLIS = - "latin_ime_speech_minimum_length_millis"; - public static final String LATIN_IME_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS = - "latin_ime_speech_input_complete_silence_length_millis"; - public static final String LATIN_IME_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS = - "latin_ime_speech_input_possibly_complete_silence_length_millis"; - - /** - * Min and max volume levels that can be displayed on the "speak now" screen. - */ - public static final String LATIN_IME_MIN_MICROPHONE_LEVEL = - "latin_ime_min_microphone_level"; - public static final String LATIN_IME_MAX_MICROPHONE_LEVEL = - "latin_ime_max_microphone_level"; - - /** - * The number of sentence-level alternates to request of the server. - */ - public static final String LATIN_IME_MAX_VOICE_RESULTS = "latin_ime_max_voice_results"; - - /** - * Get a string-valued setting. - * - * @param cr The content resolver to use - * @param key The setting to look up - * @param defaultValue The default value to use if none can be found - * @return The value of the setting, or defaultValue if it couldn't be found - */ - public static String getSettingsString(ContentResolver cr, String key, String defaultValue) { - String result = Settings.Secure.getString(cr, key); - return (result == null) ? defaultValue : result; - } - - /** - * Get an int-valued setting. - * - * @param cr The content resolver to use - * @param key The setting to look up - * @param defaultValue The default value to use if the setting couldn't be found or parsed - * @return The value of the setting, or defaultValue if it couldn't be found or parsed - */ - public static int getSettingsInt(ContentResolver cr, String key, int defaultValue) { - return Settings.Secure.getInt(cr, key, defaultValue); - } - - /** - * Get a float-valued setting. - * - * @param cr The content resolver to use - * @param key The setting to look up - * @param defaultValue The default value to use if the setting couldn't be found or parsed - * @return The value of the setting, or defaultValue if it couldn't be found or parsed - */ - public static float getSettingsFloat(ContentResolver cr, String key, float defaultValue) { - return Settings.Secure.getFloat(cr, key, defaultValue); - } -} diff --git a/java/src/com/android/inputmethod/voice/SoundIndicator.java b/java/src/com/android/inputmethod/voice/SoundIndicator.java deleted file mode 100644 index 543290b32..000000000 --- a/java/src/com/android/inputmethod/voice/SoundIndicator.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * - * 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.voice; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.util.AttributeSet; -import android.widget.ImageView; - -import com.android.inputmethod.latin.R; - -/** - * A widget which shows the volume of audio using a microphone icon - */ -public class SoundIndicator extends ImageView { - @SuppressWarnings("unused") - private static final String TAG = "SoundIndicator"; - - private static final float UP_SMOOTHING_FACTOR = 0.9f; - private static final float DOWN_SMOOTHING_FACTOR = 0.4f; - - private static final float AUDIO_METER_MIN_DB = 7.0f; - private static final float AUDIO_METER_DB_RANGE = 20.0f; - - private static final long FRAME_DELAY = 50; - - private Bitmap mDrawingBuffer; - private Canvas mBufferCanvas; - private Bitmap mEdgeBitmap; - private float mLevel = 0.0f; - private Drawable mFrontDrawable; - private Paint mClearPaint; - private Paint mMultPaint; - private int mEdgeBitmapOffset; - - private Handler mHandler; - - private Runnable mDrawFrame = new Runnable() { - public void run() { - invalidate(); - mHandler.postDelayed(mDrawFrame, FRAME_DELAY); - } - }; - - public SoundIndicator(Context context) { - this(context, null); - } - - public SoundIndicator(Context context, AttributeSet attrs) { - super(context, attrs); - - mFrontDrawable = getDrawable(); - BitmapDrawable edgeDrawable = - (BitmapDrawable) context.getResources().getDrawable(R.drawable.vs_popup_mic_edge); - mEdgeBitmap = edgeDrawable.getBitmap(); - mEdgeBitmapOffset = mEdgeBitmap.getHeight() / 2; - - mDrawingBuffer = - Bitmap.createBitmap(mFrontDrawable.getIntrinsicWidth(), - mFrontDrawable.getIntrinsicHeight(), Config.ARGB_8888); - - mBufferCanvas = new Canvas(mDrawingBuffer); - - // Initialize Paints. - mClearPaint = new Paint(); - mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - - mMultPaint = new Paint(); - mMultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); - - mHandler = new Handler(); - } - - @Override - public void onDraw(Canvas canvas) { - //super.onDraw(canvas); - - float w = getWidth(); - float h = getHeight(); - - // Clear the buffer canvas - mBufferCanvas.drawRect(0, 0, w, h, mClearPaint); - - // Set its clip so we don't draw the front image all the way to the top - Rect clip = new Rect(0, - (int) ((1.0 - mLevel) * (h + mEdgeBitmapOffset)) - mEdgeBitmapOffset, - (int) w, - (int) h); - - mBufferCanvas.save(); - mBufferCanvas.clipRect(clip); - - // Draw the front image - mFrontDrawable.setBounds(new Rect(0, 0, (int) w, (int) h)); - mFrontDrawable.draw(mBufferCanvas); - - mBufferCanvas.restore(); - - // Draw the edge image on top of the buffer image with a multiply mode - mBufferCanvas.drawBitmap(mEdgeBitmap, 0, clip.top, mMultPaint); - - // Draw the buffer image (on top of the background image) - canvas.drawBitmap(mDrawingBuffer, 0, 0, null); - } - - /** - * Sets the sound level - * - * @param rmsdB The level of the sound, in dB. - */ - public void setRmsdB(float rmsdB) { - float level = ((rmsdB - AUDIO_METER_MIN_DB) / AUDIO_METER_DB_RANGE); - - level = Math.min(Math.max(0.0f, level), 1.0f); - - // We smooth towards the new level - if (level > mLevel) { - mLevel = (level - mLevel) * UP_SMOOTHING_FACTOR + mLevel; - } else { - mLevel = (level - mLevel) * DOWN_SMOOTHING_FACTOR + mLevel; - } - invalidate(); - } - - public void start() { - mHandler.post(mDrawFrame); - } - - public void stop() { - mHandler.removeCallbacks(mDrawFrame); - } -} diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java deleted file mode 100644 index 105656fe0..000000000 --- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java +++ /dev/null @@ -1,728 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.voice; - -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.latin.EditingUtils; -import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.LatinIME.UIHandler; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SharedPreferencesCompat; -import com.android.inputmethod.latin.SubtypeSwitcher; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.Utils; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.provider.Browser; -import android.speech.SpeechRecognizer; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.text.style.URLSpan; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.Window; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class VoiceIMEConnector implements VoiceInput.UiListener { - private static final VoiceIMEConnector sInstance = new VoiceIMEConnector(); - - public static final boolean VOICE_INSTALLED = true; - private static final boolean ENABLE_VOICE_BUTTON = true; - private static final String PREF_VOICE_MODE = "voice_mode"; - // Whether or not the user has used voice input before (and thus, whether to show the - // first-run warning dialog or not). - private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input"; - // Whether or not the user has used voice input from an unsupported locale UI before. - // For example, the user has a Chinese UI but activates voice input. - private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = - "has_used_voice_input_unsupported_locale"; - private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6; - - private static final String TAG = VoiceIMEConnector.class.getSimpleName(); - private static final boolean DEBUG = LatinImeLogger.sDBG; - - private boolean mAfterVoiceInput; - private boolean mHasUsedVoiceInput; - private boolean mHasUsedVoiceInputUnsupportedLocale; - private boolean mImmediatelyAfterVoiceInput; - private boolean mIsShowingHint; - private boolean mLocaleSupportedForVoiceInput; - private boolean mPasswordText; - private boolean mRecognizing; - private boolean mShowingVoiceSuggestions; - private boolean mVoiceButtonEnabled; - private boolean mVoiceButtonOnPrimary; - private boolean mVoiceInputHighlighted; - - private InputMethodManager mImm; - private LatinIME mService; - private AlertDialog mVoiceWarningDialog; - private VoiceInput mVoiceInput; - private final VoiceResults mVoiceResults = new VoiceResults(); - private Hints mHints; - private UIHandler mHandler; - private SubtypeSwitcher mSubtypeSwitcher; - // For each word, a list of potential replacements, usually from voice. - private final Map> mWordToSuggestions = - new HashMap>(); - - public static VoiceIMEConnector init(LatinIME context, SharedPreferences prefs, UIHandler h) { - sInstance.initInternal(context, prefs, h); - return sInstance; - } - - public static VoiceIMEConnector getInstance() { - return sInstance; - } - - private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) { - mService = service; - mHandler = h; - mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); - mSubtypeSwitcher = SubtypeSwitcher.getInstance(); - if (VOICE_INSTALLED) { - mVoiceInput = new VoiceInput(service, this); - mHints = new Hints(service, prefs, new Hints.Display() { - @Override - public void showHint(int viewResource) { - View view = LayoutInflater.from(mService).inflate(viewResource, null); - mService.setCandidatesView(view); - mService.setCandidatesViewShown(true); - mIsShowingHint = true; - } - }); - } - } - - private VoiceIMEConnector() { - // Intentional empty constructor for singleton. - } - - public void resetVoiceStates(boolean isPasswordText) { - mAfterVoiceInput = false; - mImmediatelyAfterVoiceInput = false; - mShowingVoiceSuggestions = false; - mVoiceInputHighlighted = false; - mPasswordText = isPasswordText; - } - - public void flushVoiceInputLogs(boolean configurationChanged) { - if (VOICE_INSTALLED && !configurationChanged) { - if (mAfterVoiceInput) { - mVoiceInput.flushAllTextModificationCounters(); - mVoiceInput.logInputEnded(); - } - mVoiceInput.flushLogs(); - mVoiceInput.cancel(); - } - } - - public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion, - String wordSeparators) { - if (mAfterVoiceInput && mShowingVoiceSuggestions) { - mVoiceInput.flushAllTextModificationCounters(); - // send this intent AFTER logging any prior aggregated edits. - mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index, - wordSeparators, mService.getCurrentInputConnection()); - } - } - - private void showVoiceWarningDialog(final boolean swipe, IBinder token) { - if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { - return; - } - AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService); - builder.setCancelable(true); - builder.setIcon(R.drawable.ic_mic_dialog); - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - mVoiceInput.logKeyboardWarningDialogOk(); - reallyStartListening(swipe); - } - }); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - mVoiceInput.logKeyboardWarningDialogCancel(); - switchToLastInputMethod(); - } - }); - // When the dialog is dismissed by user's cancellation, switch back to the last input method - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface arg0) { - mVoiceInput.logKeyboardWarningDialogCancel(); - switchToLastInputMethod(); - } - }); - - final CharSequence message; - if (mLocaleSupportedForVoiceInput) { - message = TextUtils.concat( - mService.getText(R.string.voice_warning_may_not_understand), "\n\n", - mService.getText(R.string.voice_warning_how_to_turn_off)); - } else { - message = TextUtils.concat( - mService.getText(R.string.voice_warning_locale_not_supported), "\n\n", - mService.getText(R.string.voice_warning_may_not_understand), "\n\n", - mService.getText(R.string.voice_warning_how_to_turn_off)); - } - builder.setMessage(message); - builder.setTitle(R.string.voice_warning_title); - mVoiceWarningDialog = builder.create(); - final Window window = mVoiceWarningDialog.getWindow(); - final WindowManager.LayoutParams lp = window.getAttributes(); - lp.token = token; - lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; - window.setAttributes(lp); - window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - mVoiceInput.logKeyboardWarningDialogShown(); - mVoiceWarningDialog.show(); - } - - private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder { - private AlertDialog mAlertDialog; - - public UrlLinkAlertDialogBuilder(Context context) { - super(context); - } - - @Override - public AlertDialog.Builder setMessage(CharSequence message) { - return super.setMessage(replaceURLSpan(message)); - } - - private Spanned replaceURLSpan(CharSequence message) { - // Replace all spans with the custom span - final SpannableStringBuilder ssb = new SpannableStringBuilder(message); - for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) { - int spanStart = ssb.getSpanStart(span); - int spanEnd = ssb.getSpanEnd(span); - int spanFlags = ssb.getSpanFlags(span); - ssb.removeSpan(span); - ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags); - } - return ssb; - } - - @Override - public AlertDialog create() { - final AlertDialog dialog = super.create(); - - dialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialogInterface) { - // Make URL in the dialog message click-able. - TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message); - if (textView != null) { - textView.setMovementMethod(LinkMovementMethod.getInstance()); - } - } - }); - mAlertDialog = dialog; - return dialog; - } - - class ClickableSpan extends URLSpan { - public ClickableSpan(String url) { - super(url); - } - - @Override - public void onClick(View widget) { - Uri uri = Uri.parse(getURL()); - Context context = widget.getContext(); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - // Add this flag to start an activity from service - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); - // Dismiss the warning dialog and go back to the previous IME. - // TODO: If we can find a way to bring the new activity to front while keeping - // the warning dialog, we don't need to dismiss it here. - mAlertDialog.cancel(); - context.startActivity(intent); - } - } - } - - public void showPunctuationHintIfNecessary() { - InputConnection ic = mService.getCurrentInputConnection(); - if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) { - if (mHints.showPunctuationHintIfNecessary(ic)) { - mVoiceInput.logPunctuationHintDisplayed(); - } - } - mImmediatelyAfterVoiceInput = false; - } - - public void hideVoiceWindow(boolean configurationChanging) { - if (!configurationChanging) { - if (mAfterVoiceInput) - mVoiceInput.logInputEnded(); - if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { - mVoiceInput.logKeyboardWarningDialogDismissed(); - mVoiceWarningDialog.dismiss(); - mVoiceWarningDialog = null; - } - if (VOICE_INSTALLED & mRecognizing) { - mVoiceInput.cancel(); - } - } - mWordToSuggestions.clear(); - } - - public void setCursorAndSelection(int newSelEnd, int newSelStart) { - if (mAfterVoiceInput) { - mVoiceInput.setCursorPos(newSelEnd); - mVoiceInput.setSelectionSpan(newSelEnd - newSelStart); - } - } - - public void setVoiceInputHighlighted(boolean b) { - mVoiceInputHighlighted = b; - } - - public void setShowingVoiceSuggestions(boolean b) { - mShowingVoiceSuggestions = b; - } - - public boolean isVoiceButtonEnabled() { - return mVoiceButtonEnabled; - } - - public boolean isVoiceButtonOnPrimary() { - return mVoiceButtonOnPrimary; - } - - public boolean isVoiceInputHighlighted() { - return mVoiceInputHighlighted; - } - - public boolean isRecognizing() { - return mRecognizing; - } - - public boolean needsToShowWarningDialog() { - return !mHasUsedVoiceInput - || (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale); - } - - public boolean getAndResetIsShowingHint() { - boolean ret = mIsShowingHint; - mIsShowingHint = false; - return ret; - } - - private void revertVoiceInput() { - InputConnection ic = mService.getCurrentInputConnection(); - if (ic != null) ic.commitText("", 1); - mService.updateSuggestions(); - mVoiceInputHighlighted = false; - } - - public void commitVoiceInput() { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - InputConnection ic = mService.getCurrentInputConnection(); - if (ic != null) ic.finishComposingText(); - mService.updateSuggestions(); - mVoiceInputHighlighted = false; - } - } - - public boolean logAndRevertVoiceInput() { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - mVoiceInput.incrementTextModificationDeleteCount( - mVoiceResults.candidates.get(0).toString().length()); - revertVoiceInput(); - return true; - } else { - return false; - } - } - - public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) { - if (mShowingVoiceSuggestions) { - // Retain the replaced word in the alternatives array. - String wordToBeReplaced = EditingUtils.getWordAtCursor( - mService.getCurrentInputConnection(), wordSeparators); - if (!mWordToSuggestions.containsKey(wordToBeReplaced)) { - wordToBeReplaced = wordToBeReplaced.toLowerCase(); - } - if (mWordToSuggestions.containsKey(wordToBeReplaced)) { - List suggestions = mWordToSuggestions.get(wordToBeReplaced); - if (suggestions.contains(suggestion)) { - suggestions.remove(suggestion); - } - suggestions.add(wordToBeReplaced); - mWordToSuggestions.remove(wordToBeReplaced); - mWordToSuggestions.put(suggestion.toString(), suggestions); - } - } - } - - /** - * Tries to apply any voice alternatives for the word if this was a spoken word and - * there are voice alternatives. - * @param touching The word that the cursor is touching, with position information - * @return true if an alternative was found, false otherwise. - */ - public boolean applyVoiceAlternatives(EditingUtils.SelectedWord touching) { - // Search for result in spoken word alternatives - String selectedWord = touching.mWord.toString().trim(); - if (!mWordToSuggestions.containsKey(selectedWord)) { - selectedWord = selectedWord.toLowerCase(); - } - if (mWordToSuggestions.containsKey(selectedWord)) { - mShowingVoiceSuggestions = true; - List suggestions = mWordToSuggestions.get(selectedWord); - SuggestedWords.Builder builder = new SuggestedWords.Builder(); - // If the first letter of touching is capitalized, make all the suggestions - // start with a capital letter. - if (Character.isUpperCase(touching.mWord.charAt(0))) { - for (CharSequence word : suggestions) { - String str = word.toString(); - word = Character.toUpperCase(str.charAt(0)) + str.substring(1); - builder.addWord(word); - } - } else { - builder.addWords(suggestions, null); - } - builder.setTypedWordValid(true).setHasMinimalSuggestion(true); - mService.setSuggestions(builder.build()); - mService.setCandidatesViewShown(true); - return true; - } - return false; - } - - public void handleBackspace() { - if (mAfterVoiceInput) { - // Don't log delete if the user is pressing delete at - // the beginning of the text box (hence not deleting anything) - if (mVoiceInput.getCursorPos() > 0) { - // If anything was selected before the delete was pressed, increment the - // delete count by the length of the selection - int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? - mVoiceInput.getSelectionSpan() : 1; - mVoiceInput.incrementTextModificationDeleteCount(deleteLen); - } - } - } - - public void handleCharacter() { - commitVoiceInput(); - if (mAfterVoiceInput) { - // Assume input length is 1. This assumption fails for smiley face insertions. - mVoiceInput.incrementTextModificationInsertCount(1); - } - } - - public void handleSeparator() { - commitVoiceInput(); - if (mAfterVoiceInput){ - // Assume input length is 1. This assumption fails for smiley face insertions. - mVoiceInput.incrementTextModificationInsertPunctuationCount(1); - } - } - - public void handleClose() { - if (VOICE_INSTALLED & mRecognizing) { - mVoiceInput.cancel(); - } - } - - - public void handleVoiceResults(boolean capitalizeFirstWord) { - mAfterVoiceInput = true; - mImmediatelyAfterVoiceInput = true; - - InputConnection ic = mService.getCurrentInputConnection(); - if (!mService.isFullscreenMode()) { - // Start listening for updates to the text from typing, etc. - if (ic != null) { - ExtractedTextRequest req = new ExtractedTextRequest(); - ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); - } - } - mService.vibrate(); - - final List nBest = new ArrayList(); - for (String c : mVoiceResults.candidates) { - if (capitalizeFirstWord) { - c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length()); - } - nBest.add(c); - } - if (nBest.size() == 0) { - return; - } - String bestResult = nBest.get(0).toString(); - mVoiceInput.logVoiceInputDelivered(bestResult.length()); - mHints.registerVoiceResult(bestResult); - - if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text - mService.commitTyped(ic); - EditingUtils.appendText(ic, bestResult); - if (ic != null) ic.endBatchEdit(); - - mVoiceInputHighlighted = true; - mWordToSuggestions.putAll(mVoiceResults.alternatives); - onCancelVoice(); - } - - public void switchToRecognitionStatusView(final Configuration configuration) { - mHandler.post(new Runnable() { - @Override - public void run() { - mService.setCandidatesViewShown(false); - mRecognizing = true; - mVoiceInput.newView(); - View v = mVoiceInput.getView(); - - ViewParent p = v.getParent(); - if (p != null && p instanceof ViewGroup) { - ((ViewGroup) p).removeView(v); - } - - View keyboardView = KeyboardSwitcher.getInstance().getInputView(); - - // The full height of the keyboard is difficult to calculate - // as the dimension is expressed in "mm" and not in "pixel" - // As we add mm, we don't know how the rounding is going to work - // thus we may end up with few pixels extra (or less). - if (keyboardView != null) { - View popupLayout = v.findViewById(R.id.popup_layout); - final int displayHeight = - mService.getResources().getDisplayMetrics().heightPixels; - final int currentHeight = popupLayout.getLayoutParams().height; - final int keyboardHeight = keyboardView.getHeight(); - if (keyboardHeight > currentHeight || keyboardHeight - > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) { - popupLayout.getLayoutParams().height = keyboardHeight; - } - } - mService.setInputView(v); - mService.updateInputViewShown(); - - if (configuration != null) { - mVoiceInput.onConfigurationChanged(configuration); - } - }}); - } - - private void switchToLastInputMethod() { - final IBinder token = mService.getWindow().getWindow().getAttributes().token; - new AsyncTask() { - @Override - protected Boolean doInBackground(Void... params) { - return mImm.switchToLastInputMethod(token); - } - - @Override - protected void onPostExecute(Boolean result) { - if (!result) { - if (DEBUG) { - Log.d(TAG, "Couldn't switch back to last IME."); - } - // Needs to reset here because LatinIME failed to back to any IME and - // the same voice subtype will be triggered in the next time. - mVoiceInput.reset(); - mService.requestHideSelf(0); - } - } - }.execute(); - } - - private void reallyStartListening(boolean swipe) { - if (!VOICE_INSTALLED) { - return; - } - if (!mHasUsedVoiceInput) { - // The user has started a voice input, so remember that in the - // future (so we don't show the warning dialog after the first run). - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences(mService).edit(); - editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true); - SharedPreferencesCompat.apply(editor); - mHasUsedVoiceInput = true; - } - - if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) { - // The user has started a voice input from an unsupported locale, so remember that - // in the future (so we don't show the warning dialog the next time they do this). - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences(mService).edit(); - editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true); - SharedPreferencesCompat.apply(editor); - mHasUsedVoiceInputUnsupportedLocale = true; - } - - // Clear N-best suggestions - mService.clearSuggestions(); - - FieldContext context = makeFieldContext(); - mVoiceInput.startListening(context, swipe); - switchToRecognitionStatusView(null); - } - - public void startListening(final boolean swipe, IBinder token) { - // TODO: remove swipe which is no longer used. - if (VOICE_INSTALLED) { - if (needsToShowWarningDialog()) { - // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. - showVoiceWarningDialog(swipe, token); - } else { - reallyStartListening(swipe); - } - } - } - - private boolean fieldCanDoVoice(FieldContext fieldContext) { - return !mPasswordText - && mVoiceInput != null - && !mVoiceInput.isBlacklistedField(fieldContext); - } - - private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { - final boolean noMic = Utils.inPrivateImeOptions(null, - LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute) - || Utils.inPrivateImeOptions(mService.getPackageName(), - LatinIME.IME_OPTION_NO_MICROPHONE, attribute); - return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic - && SpeechRecognizer.isRecognitionAvailable(mService); - } - - public void loadSettings(EditorInfo attribute, SharedPreferences sp) { - mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false); - mHasUsedVoiceInputUnsupportedLocale = - sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false); - - mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported( - SubtypeSwitcher.getInstance().getInputLocaleStr()); - - if (VOICE_INSTALLED) { - final String voiceMode = sp.getString(PREF_VOICE_MODE, - mService.getString(R.string.voice_mode_main)); - mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off)) - && shouldShowVoiceButton(makeFieldContext(), attribute); - mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main)); - } - } - - public void destroy() { - if (VOICE_INSTALLED && mVoiceInput != null) { - mVoiceInput.destroy(); - } - } - - public void onStartInputView(IBinder keyboardViewToken) { - // If keyboardViewToken is null, keyboardView is not attached but voiceView is attached. - IBinder windowToken = keyboardViewToken != null ? keyboardViewToken - : mVoiceInput.getView().getWindowToken(); - // If IME is in voice mode, but still needs to show the voice warning dialog, - // keep showing the warning. - if (mSubtypeSwitcher.isVoiceMode() && windowToken != null) { - // Close keyboard view if it is been shown. - if (KeyboardSwitcher.getInstance().isInputViewShown()) - KeyboardSwitcher.getInstance().getInputView().purgeKeyboardAndClosing(); - startListening(false, windowToken); - } - // If we have no token, onAttachedToWindow will take care of showing dialog and start - // listening. - } - - public void onAttachedToWindow() { - // After onAttachedToWindow, we can show the voice warning dialog. See startListening() - // above. - mSubtypeSwitcher.setVoiceInput(mVoiceInput); - } - - public void onConfigurationChanged(Configuration configuration) { - if (mRecognizing) { - switchToRecognitionStatusView(configuration); - } - } - - @Override - public void onCancelVoice() { - if (mRecognizing) { - if (mSubtypeSwitcher.isVoiceMode()) { - // If voice mode is being canceled within LatinIME (i.e. time-out or user - // cancellation etc.), onCancelVoice() will be called first. LatinIME thinks it's - // still in voice mode. LatinIME needs to call switchToLastInputMethod(). - // Note that onCancelVoice() will be called again from SubtypeSwitcher. - switchToLastInputMethod(); - } else if (mSubtypeSwitcher.isKeyboardMode()) { - // If voice mode is being canceled out of LatinIME (i.e. by user's IME switching or - // as a result of switchToLastInputMethod() etc.), - // onCurrentInputMethodSubtypeChanged() will be called first. LatinIME will know - // that it's in keyboard mode and SubtypeSwitcher will call onCancelVoice(). - mRecognizing = false; - mService.switchToKeyboardView(); - } - } - } - - @Override - public void onVoiceResults(List candidates, - Map> alternatives) { - if (!mRecognizing) { - return; - } - mVoiceResults.candidates = candidates; - mVoiceResults.alternatives = alternatives; - mHandler.updateVoiceResults(); - } - - private FieldContext makeFieldContext() { - SubtypeSwitcher switcher = SubtypeSwitcher.getInstance(); - return new FieldContext(mService.getCurrentInputConnection(), - mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(), - switcher.getEnabledLanguages()); - } - - private class VoiceResults { - List candidates; - Map> alternatives; - } -} diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java deleted file mode 100644 index 2df9e8588..000000000 --- a/java/src/com/android/inputmethod/voice/VoiceInput.java +++ /dev/null @@ -1,685 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. - * - * 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.voice; - -import com.android.inputmethod.latin.EditingUtils; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.R; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Parcelable; -import android.speech.RecognitionListener; -import android.speech.RecognizerIntent; -import android.speech.SpeechRecognizer; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.inputmethod.InputConnection; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** - * Speech recognition input, including both user interface and a background - * process to stream audio to the network recognizer. This class supplies a - * View (getView()), which it updates as recognition occurs. The user of this - * class is responsible for making the view visible to the user, as well as - * handling various events returned through UiListener. - */ -public class VoiceInput implements OnClickListener { - private static final String TAG = "VoiceInput"; - private static final String EXTRA_RECOGNITION_CONTEXT = - "android.speech.extras.RECOGNITION_CONTEXT"; - private static final String EXTRA_CALLING_PACKAGE = "calling_package"; - private static final String EXTRA_ALTERNATES = "android.speech.extra.ALTERNATES"; - private static final int MAX_ALT_LIST_LENGTH = 6; - private static boolean DBG = LatinImeLogger.sDBG; - - private static final String DEFAULT_RECOMMENDED_PACKAGES = - "com.android.mms " + - "com.google.android.gm " + - "com.google.android.talk " + - "com.google.android.apps.googlevoice " + - "com.android.email " + - "com.android.browser "; - - // WARNING! Before enabling this, fix the problem with calling getExtractedText() in - // landscape view. It causes Extracted text updates to be rejected due to a token mismatch - public static boolean ENABLE_WORD_CORRECTIONS = true; - - // Dummy word suggestion which means "delete current word" - public static final String DELETE_SYMBOL = " \u00D7 "; // times symbol - - private Whitelist mRecommendedList; - private Whitelist mBlacklist; - - private VoiceInputLogger mLogger; - - // Names of a few extras defined in VoiceSearch's RecognitionController - // Note, the version of voicesearch that shipped in Froyo returns the raw - // RecognitionClientAlternates protocol buffer under the key "alternates", - // so a VS market update must be installed on Froyo devices in order to see - // alternatives. - private static final String ALTERNATES_BUNDLE = "alternates_bundle"; - - // This is copied from the VoiceSearch app. - @SuppressWarnings("unused") - private static final class AlternatesBundleKeys { - public static final String ALTERNATES = "alternates"; - public static final String CONFIDENCE = "confidence"; - public static final String LENGTH = "length"; - public static final String MAX_SPAN_LENGTH = "max_span_length"; - public static final String SPANS = "spans"; - public static final String SPAN_KEY_DELIMITER = ":"; - public static final String START = "start"; - public static final String TEXT = "text"; - } - - // Names of a few intent extras defined in VoiceSearch's RecognitionService. - // These let us tweak the endpointer parameters. - private static final String EXTRA_SPEECH_MINIMUM_LENGTH_MILLIS = - "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS"; - private static final String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS = - "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS"; - private static final String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS = - "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS"; - - // The usual endpointer default value for input complete silence length is 0.5 seconds, - // but that's used for things like voice search. For dictation-like voice input like this, - // we go with a more liberal value of 1 second. This value will only be used if a value - // is not provided from Gservices. - private static final String INPUT_COMPLETE_SILENCE_LENGTH_DEFAULT_VALUE_MILLIS = "1000"; - - // Used to record part of that state for logging purposes. - public static final int DEFAULT = 0; - public static final int LISTENING = 1; - public static final int WORKING = 2; - public static final int ERROR = 3; - - private int mAfterVoiceInputDeleteCount = 0; - private int mAfterVoiceInputInsertCount = 0; - private int mAfterVoiceInputInsertPunctuationCount = 0; - private int mAfterVoiceInputCursorPos = 0; - private int mAfterVoiceInputSelectionSpan = 0; - - private int mState = DEFAULT; - - private final static int MSG_RESET = 1; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_RESET) { - mState = DEFAULT; - mRecognitionView.finish(); - mUiListener.onCancelVoice(); - } - } - }; - - /** - * Events relating to the recognition UI. You must implement these. - */ - public interface UiListener { - - /** - * @param recognitionResults a set of transcripts for what the user - * spoke, sorted by likelihood. - */ - public void onVoiceResults( - List recognitionResults, - Map> alternatives); - - /** - * Called when the user cancels speech recognition. - */ - public void onCancelVoice(); - } - - private SpeechRecognizer mSpeechRecognizer; - private RecognitionListener mRecognitionListener; - private RecognitionView mRecognitionView; - private UiListener mUiListener; - private Context mContext; - - /** - * @param context the service or activity in which we're running. - * @param uiHandler object to receive events from VoiceInput. - */ - public VoiceInput(Context context, UiListener uiHandler) { - mLogger = VoiceInputLogger.getLogger(context); - mRecognitionListener = new ImeRecognitionListener(); - mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context); - mSpeechRecognizer.setRecognitionListener(mRecognitionListener); - mUiListener = uiHandler; - mContext = context; - newView(); - - String recommendedPackages = SettingsUtil.getSettingsString( - context.getContentResolver(), - SettingsUtil.LATIN_IME_VOICE_INPUT_RECOMMENDED_PACKAGES, - DEFAULT_RECOMMENDED_PACKAGES); - - mRecommendedList = new Whitelist(); - for (String recommendedPackage : recommendedPackages.split("\\s+")) { - mRecommendedList.addApp(recommendedPackage); - } - - mBlacklist = new Whitelist(); - mBlacklist.addApp("com.android.setupwizard"); - } - - public void setCursorPos(int pos) { - mAfterVoiceInputCursorPos = pos; - } - - public int getCursorPos() { - return mAfterVoiceInputCursorPos; - } - - public void setSelectionSpan(int span) { - mAfterVoiceInputSelectionSpan = span; - } - - public int getSelectionSpan() { - return mAfterVoiceInputSelectionSpan; - } - - public void incrementTextModificationDeleteCount(int count){ - mAfterVoiceInputDeleteCount += count; - // Send up intents for other text modification types - if (mAfterVoiceInputInsertCount > 0) { - logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount); - mAfterVoiceInputInsertCount = 0; - } - if (mAfterVoiceInputInsertPunctuationCount > 0) { - logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount); - mAfterVoiceInputInsertPunctuationCount = 0; - } - - } - - public void incrementTextModificationInsertCount(int count){ - mAfterVoiceInputInsertCount += count; - if (mAfterVoiceInputSelectionSpan > 0) { - // If text was highlighted before inserting the char, count this as - // a delete. - mAfterVoiceInputDeleteCount += mAfterVoiceInputSelectionSpan; - } - // Send up intents for other text modification types - if (mAfterVoiceInputDeleteCount > 0) { - logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount); - mAfterVoiceInputDeleteCount = 0; - } - if (mAfterVoiceInputInsertPunctuationCount > 0) { - logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount); - mAfterVoiceInputInsertPunctuationCount = 0; - } - } - - public void incrementTextModificationInsertPunctuationCount(int count){ - mAfterVoiceInputInsertPunctuationCount += count; - if (mAfterVoiceInputSelectionSpan > 0) { - // If text was highlighted before inserting the char, count this as - // a delete. - mAfterVoiceInputDeleteCount += mAfterVoiceInputSelectionSpan; - } - // Send up intents for aggregated non-punctuation insertions - if (mAfterVoiceInputDeleteCount > 0) { - logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount); - mAfterVoiceInputDeleteCount = 0; - } - if (mAfterVoiceInputInsertCount > 0) { - logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount); - mAfterVoiceInputInsertCount = 0; - } - } - - public void flushAllTextModificationCounters() { - if (mAfterVoiceInputInsertCount > 0) { - logTextModifiedByTypingInsertion(mAfterVoiceInputInsertCount); - mAfterVoiceInputInsertCount = 0; - } - if (mAfterVoiceInputDeleteCount > 0) { - logTextModifiedByTypingDeletion(mAfterVoiceInputDeleteCount); - mAfterVoiceInputDeleteCount = 0; - } - if (mAfterVoiceInputInsertPunctuationCount > 0) { - logTextModifiedByTypingInsertionPunctuation(mAfterVoiceInputInsertPunctuationCount); - mAfterVoiceInputInsertPunctuationCount = 0; - } - } - - /** - * The configuration of the IME changed and may have caused the views to be layed out - * again. Restore the state of the recognition view. - */ - public void onConfigurationChanged(Configuration configuration) { - mRecognitionView.restoreState(); - mRecognitionView.getView().dispatchConfigurationChanged(configuration); - } - - /** - * @return true if field is blacklisted for voice - */ - public boolean isBlacklistedField(FieldContext context) { - return mBlacklist.matches(context); - } - - /** - * Used to decide whether to show voice input hints for this field, etc. - * - * @return true if field is recommended for voice - */ - public boolean isRecommendedField(FieldContext context) { - return mRecommendedList.matches(context); - } - - /** - * Start listening for speech from the user. This will grab the microphone - * and start updating the view provided by getView(). It is the caller's - * responsibility to ensure that the view is visible to the user at this stage. - * - * @param context the same FieldContext supplied to voiceIsEnabled() - * @param swipe whether this voice input was started by swipe, for logging purposes - */ - public void startListening(FieldContext context, boolean swipe) { - if (DBG) { - Log.d(TAG, "startListening: " + context); - } - - if (mState != DEFAULT) { - Log.w(TAG, "startListening in the wrong status " + mState); - } - - // If everything works ok, the voice input should be already in the correct state. As this - // class can be called by third-party, we call reset just to be on the safe side. - reset(); - - Locale locale = Locale.getDefault(); - String localeString = locale.getLanguage() + "-" + locale.getCountry(); - - mLogger.start(localeString, swipe); - - mState = LISTENING; - - mRecognitionView.showInitializing(); - startListeningAfterInitialization(context); - } - - /** - * Called only when the recognition manager's initialization completed - * - * @param context context with which {@link #startListening(FieldContext, boolean)} was executed - */ - private void startListeningAfterInitialization(FieldContext context) { - Intent intent = makeIntent(); - intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, ""); - intent.putExtra(EXTRA_RECOGNITION_CONTEXT, context.getBundle()); - intent.putExtra(EXTRA_CALLING_PACKAGE, "VoiceIME"); - intent.putExtra(EXTRA_ALTERNATES, true); - intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, - SettingsUtil.getSettingsInt( - mContext.getContentResolver(), - SettingsUtil.LATIN_IME_MAX_VOICE_RESULTS, - 1)); - // Get endpointer params from Gservices. - // TODO: Consider caching these values for improved performance on slower devices. - final ContentResolver cr = mContext.getContentResolver(); - putEndpointerExtra( - cr, - intent, - SettingsUtil.LATIN_IME_SPEECH_MINIMUM_LENGTH_MILLIS, - EXTRA_SPEECH_MINIMUM_LENGTH_MILLIS, - null /* rely on endpointer default */); - putEndpointerExtra( - cr, - intent, - SettingsUtil.LATIN_IME_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, - EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, - INPUT_COMPLETE_SILENCE_LENGTH_DEFAULT_VALUE_MILLIS - /* our default value is different from the endpointer's */); - putEndpointerExtra( - cr, - intent, - SettingsUtil. - LATIN_IME_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, - EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, - null /* rely on endpointer default */); - - mSpeechRecognizer.startListening(intent); - } - - /** - * Gets the value of the provided Gservices key, attempts to parse it into a long, - * and if successful, puts the long value as an extra in the provided intent. - */ - private void putEndpointerExtra(ContentResolver cr, Intent i, - String gservicesKey, String intentExtraKey, String defaultValue) { - long l = -1; - String s = SettingsUtil.getSettingsString(cr, gservicesKey, defaultValue); - if (s != null) { - try { - l = Long.valueOf(s); - } catch (NumberFormatException e) { - Log.e(TAG, "could not parse value for " + gservicesKey + ": " + s); - } - } - - if (l != -1) i.putExtra(intentExtraKey, l); - } - - public void destroy() { - mSpeechRecognizer.destroy(); - } - - /** - * Creates a new instance of the view that is returned by {@link #getView()} - * Clients should use this when a previously returned view is stuck in a - * layout that is being thrown away and a new one is need to show to the - * user. - */ - public void newView() { - mRecognitionView = new RecognitionView(mContext, this); - } - - /** - * @return a view that shows the recognition flow--e.g., "Speak now" and - * "working" dialogs. - */ - public View getView() { - return mRecognitionView.getView(); - } - - /** - * Handle the cancel button. - */ - @Override - public void onClick(View view) { - switch(view.getId()) { - case R.id.button: - cancel(); - break; - } - } - - public void logTextModifiedByTypingInsertion(int length) { - mLogger.textModifiedByTypingInsertion(length); - } - - public void logTextModifiedByTypingInsertionPunctuation(int length) { - mLogger.textModifiedByTypingInsertionPunctuation(length); - } - - public void logTextModifiedByTypingDeletion(int length) { - mLogger.textModifiedByTypingDeletion(length); - } - - public void logTextModifiedByChooseSuggestion(String suggestion, int index, - String wordSeparators, InputConnection ic) { - String wordToBeReplaced = EditingUtils.getWordAtCursor(ic, wordSeparators); - // If we enable phrase-based alternatives, only send up the first word - // in suggestion and wordToBeReplaced. - mLogger.textModifiedByChooseSuggestion(suggestion.length(), wordToBeReplaced.length(), - index, wordToBeReplaced, suggestion); - } - - public void logKeyboardWarningDialogShown() { - mLogger.keyboardWarningDialogShown(); - } - - public void logKeyboardWarningDialogDismissed() { - mLogger.keyboardWarningDialogDismissed(); - } - - public void logKeyboardWarningDialogOk() { - mLogger.keyboardWarningDialogOk(); - } - - public void logKeyboardWarningDialogCancel() { - mLogger.keyboardWarningDialogCancel(); - } - - public void logSwipeHintDisplayed() { - mLogger.swipeHintDisplayed(); - } - - public void logPunctuationHintDisplayed() { - mLogger.punctuationHintDisplayed(); - } - - public void logVoiceInputDelivered(int length) { - mLogger.voiceInputDelivered(length); - } - - public void logInputEnded() { - mLogger.inputEnded(); - } - - public void flushLogs() { - mLogger.flush(); - } - - private static Intent makeIntent() { - Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); - - // On Cupcake, use VoiceIMEHelper since VoiceSearch doesn't support. - // On Donut, always use VoiceSearch, since VoiceIMEHelper and - // VoiceSearch may conflict. - if (Build.VERSION.RELEASE.equals("1.5")) { - intent = intent.setClassName( - "com.google.android.voiceservice", - "com.google.android.voiceservice.IMERecognitionService"); - } else { - intent = intent.setClassName( - "com.google.android.voicesearch", - "com.google.android.voicesearch.RecognitionService"); - } - - return intent; - } - - /** - * Reset the current voice recognition. - */ - public void reset() { - if (mState != DEFAULT) { - mState = DEFAULT; - - // Remove all pending tasks (e.g., timers to cancel voice input) - mHandler.removeMessages(MSG_RESET); - - mSpeechRecognizer.cancel(); - mRecognitionView.finish(); - } - } - - /** - * Cancel in-progress speech recognition. - */ - public void cancel() { - switch (mState) { - case LISTENING: - mLogger.cancelDuringListening(); - break; - case WORKING: - mLogger.cancelDuringWorking(); - break; - case ERROR: - mLogger.cancelDuringError(); - break; - } - - reset(); - mUiListener.onCancelVoice(); - } - - private int getErrorStringId(int errorType, boolean endpointed) { - switch (errorType) { - // We use CLIENT_ERROR to signify that voice search is not available on the device. - case SpeechRecognizer.ERROR_CLIENT: - return R.string.voice_not_installed; - case SpeechRecognizer.ERROR_NETWORK: - return R.string.voice_network_error; - case SpeechRecognizer.ERROR_NETWORK_TIMEOUT: - return endpointed ? - R.string.voice_network_error : R.string.voice_too_much_speech; - case SpeechRecognizer.ERROR_AUDIO: - return R.string.voice_audio_error; - case SpeechRecognizer.ERROR_SERVER: - return R.string.voice_server_error; - case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: - return R.string.voice_speech_timeout; - case SpeechRecognizer.ERROR_NO_MATCH: - return R.string.voice_no_match; - default: return R.string.voice_error; - } - } - - private void onError(int errorType, boolean endpointed) { - Log.i(TAG, "error " + errorType); - mLogger.error(errorType); - onError(mContext.getString(getErrorStringId(errorType, endpointed))); - } - - private void onError(String error) { - mState = ERROR; - mRecognitionView.showError(error); - // Wait a couple seconds and then automatically dismiss message. - mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_RESET), 2000); - } - - private class ImeRecognitionListener implements RecognitionListener { - // Waveform data - final ByteArrayOutputStream mWaveBuffer = new ByteArrayOutputStream(); - int mSpeechStart; - private boolean mEndpointed = false; - - @Override - public void onReadyForSpeech(Bundle noiseParams) { - mRecognitionView.showListening(); - } - - @Override - public void onBeginningOfSpeech() { - mEndpointed = false; - mSpeechStart = mWaveBuffer.size(); - } - - @Override - public void onRmsChanged(float rmsdB) { - mRecognitionView.updateVoiceMeter(rmsdB); - } - - @Override - public void onBufferReceived(byte[] buf) { - try { - mWaveBuffer.write(buf); - } catch (IOException e) { - // ignore. - } - } - - @Override - public void onEndOfSpeech() { - mEndpointed = true; - mState = WORKING; - mRecognitionView.showWorking(mWaveBuffer, mSpeechStart, mWaveBuffer.size()); - } - - @Override - public void onError(int errorType) { - mState = ERROR; - VoiceInput.this.onError(errorType, mEndpointed); - } - - @Override - public void onResults(Bundle resultsBundle) { - List results = resultsBundle - .getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); - // VS Market update is needed for IME froyo clients to access the alternatesBundle - // TODO: verify this. - Bundle alternatesBundle = resultsBundle.getBundle(ALTERNATES_BUNDLE); - mState = DEFAULT; - - final Map> alternatives = - new HashMap>(); - - if (ENABLE_WORD_CORRECTIONS && alternatesBundle != null && results.size() > 0) { - // Use the top recognition result to map each alternative's start:length to a word. - String[] words = results.get(0).split(" "); - Bundle spansBundle = alternatesBundle.getBundle(AlternatesBundleKeys.SPANS); - for (String key : spansBundle.keySet()) { - // Get the word for which these alternates correspond to. - Bundle spanBundle = spansBundle.getBundle(key); - int start = spanBundle.getInt(AlternatesBundleKeys.START); - int length = spanBundle.getInt(AlternatesBundleKeys.LENGTH); - // Only keep single-word based alternatives. - if (length == 1 && start < words.length) { - // Get the alternatives associated with the span. - // If a word appears twice in a recognition result, - // concatenate the alternatives for the word. - List altList = alternatives.get(words[start]); - if (altList == null) { - altList = new ArrayList(); - alternatives.put(words[start], altList); - } - Parcelable[] alternatesArr = spanBundle - .getParcelableArray(AlternatesBundleKeys.ALTERNATES); - for (int j = 0; j < alternatesArr.length && - altList.size() < MAX_ALT_LIST_LENGTH; j++) { - Bundle alternateBundle = (Bundle) alternatesArr[j]; - String alternate = alternateBundle.getString(AlternatesBundleKeys.TEXT); - // Don't allow duplicates in the alternates list. - if (!altList.contains(alternate)) { - altList.add(alternate); - } - } - } - } - } - - if (results.size() > 5) { - results = results.subList(0, 5); - } - mUiListener.onVoiceResults(results, alternatives); - mRecognitionView.finish(); - } - - @Override - public void onPartialResults(final Bundle partialResults) { - // currently - do nothing - } - - @Override - public void onEvent(int eventType, Bundle params) { - // do nothing - reserved for events that might be added in the future - } - } -} diff --git a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java deleted file mode 100644 index 3e65434a2..000000000 --- a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2008 Google Inc. - * - * 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.voice; - -import com.android.common.speech.LoggingEvents; -import com.android.common.userhappiness.UserHappinessSignals; - -import android.content.Context; -import android.content.Intent; - -/** - * Provides the logging facility for voice input events. This fires broadcasts back to - * the voice search app which then logs on our behalf. - * - * Note that debug console logging does not occur in this class. If you want to - * see console output of these logging events, there is a boolean switch to turn - * on on the VoiceSearch side. - */ -public class VoiceInputLogger { - @SuppressWarnings("unused") - private static final String TAG = VoiceInputLogger.class.getSimpleName(); - - private static VoiceInputLogger sVoiceInputLogger; - - private final Context mContext; - - // The base intent used to form all broadcast intents to the logger - // in VoiceSearch. - private final Intent mBaseIntent; - - // This flag is used to indicate when there are voice events that - // need to be flushed. - private boolean mHasLoggingInfo = false; - - /** - * Returns the singleton of the logger. - * - * @param contextHint a hint context used when creating the logger instance. - * Ignored if the singleton instance already exists. - */ - public static synchronized VoiceInputLogger getLogger(Context contextHint) { - if (sVoiceInputLogger == null) { - sVoiceInputLogger = new VoiceInputLogger(contextHint); - } - return sVoiceInputLogger; - } - - public VoiceInputLogger(Context context) { - mContext = context; - - mBaseIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT); - mBaseIntent.putExtra(LoggingEvents.EXTRA_APP_NAME, LoggingEvents.VoiceIme.APP_NAME); - } - - private Intent newLoggingBroadcast(int event) { - Intent i = new Intent(mBaseIntent); - i.putExtra(LoggingEvents.EXTRA_EVENT, event); - return i; - } - - public void flush() { - if (hasLoggingInfo()) { - Intent i = new Intent(mBaseIntent); - i.putExtra(LoggingEvents.EXTRA_FLUSH, true); - mContext.sendBroadcast(i); - setHasLoggingInfo(false); - } - } - - public void keyboardWarningDialogShown() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_SHOWN)); - } - - public void keyboardWarningDialogDismissed() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_DISMISSED)); - } - - public void keyboardWarningDialogOk() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_OK)); - } - - public void keyboardWarningDialogCancel() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.KEYBOARD_WARNING_DIALOG_CANCEL)); - } - - public void settingsWarningDialogShown() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_SHOWN)); - } - - public void settingsWarningDialogDismissed() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_DISMISSED)); - } - - public void settingsWarningDialogOk() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_OK)); - } - - public void settingsWarningDialogCancel() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.SETTINGS_WARNING_DIALOG_CANCEL)); - } - - public void swipeHintDisplayed() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.SWIPE_HINT_DISPLAYED)); - } - - public void cancelDuringListening() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_LISTENING)); - } - - public void cancelDuringWorking() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_WORKING)); - } - - public void cancelDuringError() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.CANCEL_DURING_ERROR)); - } - - public void punctuationHintDisplayed() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.PUNCTUATION_HINT_DISPLAYED)); - } - - public void error(int code) { - setHasLoggingInfo(true); - Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.ERROR); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_ERROR_CODE, code); - mContext.sendBroadcast(i); - } - - public void start(String locale, boolean swipe) { - setHasLoggingInfo(true); - Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.START); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_START_LOCALE, locale); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_START_SWIPE, swipe); - i.putExtra(LoggingEvents.EXTRA_TIMESTAMP, System.currentTimeMillis()); - mContext.sendBroadcast(i); - } - - public void voiceInputDelivered(int length) { - setHasLoggingInfo(true); - Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.VOICE_INPUT_DELIVERED); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); - mContext.sendBroadcast(i); - } - - public void textModifiedByTypingInsertion(int length) { - setHasLoggingInfo(true); - Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, - LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_INSERTION); - mContext.sendBroadcast(i); - } - - public void textModifiedByTypingInsertionPunctuation(int length) { - setHasLoggingInfo(true); - Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, - LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_INSERTION_PUNCTUATION); - mContext.sendBroadcast(i); - } - - public void textModifiedByTypingDeletion(int length) { - setHasLoggingInfo(true); - Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, - LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_TYPING_DELETION); - - mContext.sendBroadcast(i); - } - - - public void textModifiedByChooseSuggestion(int suggestionLength, int replacedPhraseLength, - int index, String before, String after) { - setHasLoggingInfo(true); - Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE, - LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION); - - i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_BEFORE_N_BEST_CHOOSE, before); - i.putExtra(LoggingEvents.VoiceIme.EXTRA_AFTER_N_BEST_CHOOSE, after); - mContext.sendBroadcast(i); - } - - public void inputEnded() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.INPUT_ENDED)); - } - - public void voiceInputSettingEnabled() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.VOICE_INPUT_SETTING_ENABLED)); - } - - public void voiceInputSettingDisabled() { - setHasLoggingInfo(true); - mContext.sendBroadcast(newLoggingBroadcast( - LoggingEvents.VoiceIme.VOICE_INPUT_SETTING_DISABLED)); - } - - private void setHasLoggingInfo(boolean hasLoggingInfo) { - mHasLoggingInfo = hasLoggingInfo; - // If applications that call UserHappinessSignals.userAcceptedImeText - // make that call after VoiceInputLogger.flush() calls this method with false, we - // will lose those happiness signals. For example, consider the gmail sequence: - // 1. compose message - // 2. speak message into message field - // 3. type subject into subject field - // 4. press send - // We will NOT get the signal that the user accepted the voice inputted message text - // because when the user tapped on the subject field, the ime's flush will be triggered - // and the hasLoggingInfo will be then set to false. So by the time the user hits send - // we have essentially forgotten about any voice input. - // However the following (more common) use case is properly logged - // 1. compose message - // 2. type subject in subject field - // 3. speak message in message field - // 4. press send - UserHappinessSignals.setHasVoiceLoggingInfo(hasLoggingInfo); - } - - private boolean hasLoggingInfo(){ - return mHasLoggingInfo; - } - -} diff --git a/java/src/com/android/inputmethod/voice/WaveformImage.java b/java/src/com/android/inputmethod/voice/WaveformImage.java deleted file mode 100644 index 8bac669fc..000000000 --- a/java/src/com/android/inputmethod/voice/WaveformImage.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2008-2009 Google Inc. - * - * 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.voice; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.ShortBuffer; - -/** - * Utility class to draw a waveform into a bitmap, given a byte array - * that represents the waveform as a sequence of 16-bit integers. - * Adapted from RecognitionActivity.java. - */ -public class WaveformImage { - private static final int SAMPLING_RATE = 8000; - - private WaveformImage() { - // Intentional empty constructor. - } - - public static Bitmap drawWaveform( - ByteArrayOutputStream waveBuffer, int w, int h, int start, int end) { - final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - final Canvas c = new Canvas(b); - final Paint paint = new Paint(); - paint.setColor(0xFFFFFFFF); // 0xRRGGBBAA - paint.setAntiAlias(true); - paint.setStrokeWidth(0); - - final ShortBuffer buf = ByteBuffer - .wrap(waveBuffer.toByteArray()) - .order(ByteOrder.nativeOrder()) - .asShortBuffer(); - buf.position(0); - - final int numSamples = waveBuffer.size() / 2; - final int delay = (SAMPLING_RATE * 100 / 1000); - int endIndex = end / 2 + delay; - if (end == 0 || endIndex >= numSamples) { - endIndex = numSamples; - } - int index = start / 2 - delay; - if (index < 0) { - index = 0; - } - final int size = endIndex - index; - int numSamplePerPixel = 32; - int delta = size / (numSamplePerPixel * w); - if (delta == 0) { - numSamplePerPixel = size / w; - delta = 1; - } - - final float scale = 3.5f / 65536.0f; - // do one less column to make sure we won't read past - // the buffer. - try { - for (int i = 0; i < w - 1 ; i++) { - final float x = i; - for (int j = 0; j < numSamplePerPixel; j++) { - final short s = buf.get(index); - final float y = (h / 2) - (s * h * scale); - c.drawPoint(x, y, paint); - index += delta; - } - } - } catch (IndexOutOfBoundsException e) { - // this can happen, but we don't care - } - - return b; - } -} diff --git a/java/src/com/android/inputmethod/voice/Whitelist.java b/java/src/com/android/inputmethod/voice/Whitelist.java deleted file mode 100644 index f4c24de0c..000000000 --- a/java/src/com/android/inputmethod/voice/Whitelist.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. - * - * 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.voice; - -import android.os.Bundle; - -import java.util.ArrayList; -import java.util.List; - -/** - * A set of text fields where speech has been explicitly enabled. - */ -public class Whitelist { - private List mConditions; - - public Whitelist() { - mConditions = new ArrayList(); - } - - public Whitelist(List conditions) { - this.mConditions = conditions; - } - - public void addApp(String app) { - Bundle bundle = new Bundle(); - bundle.putString("packageName", app); - mConditions.add(bundle); - } - - /** - * @return true if the field is a member of the whitelist. - */ - public boolean matches(FieldContext context) { - for (Bundle condition : mConditions) { - if (matches(condition, context.getBundle())) { - return true; - } - } - return false; - } - - /** - * @return true of all values in condition are matched by a value - * in target. - */ - private boolean matches(Bundle condition, Bundle target) { - for (String key : condition.keySet()) { - if (!condition.getString(key).equals(target.getString(key))) { - return false; - } - } - return true; - } -} -- cgit v1.2.3-83-g751a From 2cff4d7e4abdb192151f9b4027fc93fe28a8bdaa Mon Sep 17 00:00:00 2001 From: satok Date: Thu, 17 Mar 2011 11:37:51 +0900 Subject: Added CompatUtils for starting a language selector Change-Id: I158414efaff43d0c6abe1f9af83ed3425992dfb9 --- .../android/inputmethod/compat/CompatUtils.java | 51 ++++++++++++++++++++++ .../com/android/inputmethod/latin/LatinIME.java | 8 ++-- .../com/android/inputmethod/latin/Settings.java | 17 +++----- 3 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 java/src/com/android/inputmethod/compat/CompatUtils.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java new file mode 100644 index 000000000..e1e812d83 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/CompatUtils.java @@ -0,0 +1,51 @@ +/* + * 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.compat; + +import android.content.Intent; +import android.text.TextUtils; + +public class CompatUtils { + private static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; + // TODO: Can these be constants instead of literal String constants? + private static final String INPUT_METHOD_SUBTYPE_SETTINGS = + "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; + private static final String INPUT_LANGUAGE_SELECTION = + "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION"; + + public static Intent getInputLanguageSelectionIntent(String inputMethodId, + int flagsForSubtypeSettings) { + final String action; + Intent intent; + if (android.os.Build.VERSION.SDK_INT + >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) { + // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS + action = INPUT_METHOD_SUBTYPE_SETTINGS; + intent = new Intent(action); + if (!TextUtils.isEmpty(inputMethodId)) { + intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId); + } + if (flagsForSubtypeSettings > 0) { + intent.setFlags(flagsForSubtypeSettings); + } + } else { + action = INPUT_LANGUAGE_SELECTION; + intent = new Intent(action); + } + return intent; + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 3d1b4e031..cd99de892 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.CompatUtils; import com.android.inputmethod.deprecated.VoiceConnector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; @@ -2215,13 +2216,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen di.dismiss(); switch (position) { case 0: - Intent intent = new Intent( - android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + Intent intent = CompatUtils.getInputLanguageSelectionIntent( + mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID, - mInputMethodId); startActivity(intent); break; case 1: diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 15ea62c7a..873b9efb5 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -16,11 +16,13 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.CompatUtils; import com.android.inputmethod.deprecated.VoiceConnector; import android.app.AlertDialog; import android.app.Dialog; import android.app.backup.BackupManager; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; @@ -38,6 +40,7 @@ import android.text.AutoText; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.util.Log; +import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import java.util.Locale; @@ -221,16 +224,10 @@ public class Settings extends PreferenceActivity @Override public boolean onPreferenceClick(Preference pref) { if (pref == mInputLanguageSelection) { - final String action; - if (android.os.Build.VERSION.SDK_INT - >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) { - // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS - // TODO: Can this be a constant instead of literal String constant? - action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; - } else { - action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION"; - } - startActivity(new Intent(action)); + startActivity(CompatUtils.getInputLanguageSelectionIntent( + Utils.getInputMethodId( + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE), + getApplicationInfo().packageName), 0)); return true; } return false; -- cgit v1.2.3-83-g751a From cba93f50c3d46ada773ec49435689dc3e2094385 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Mon, 14 Mar 2011 11:46:15 -0700 Subject: Add different ways of reading the dictionary file. This change adds basic support for an external dictionary provider. It adds methods for reading the dictionary itself from an asset in the dictionary provider package directly, obtaining the file name through the ContentProvider interface; it also adds a way of getting the data through an InputStream and copying the file locally. Incidentally this change also adds the code needed to listen for updating the dictionary provider package and reloading it in time. This change also goes hand-in-hand with Iab31db6e, which implements the small closed part of this. Issue: 3414944 Change-Id: I5e4fff99a59bb99dbdb002102db6c90e6cb41c8a --- .../inputmethod/latin/AssetFileAddress.java | 52 ++++++++ .../inputmethod/latin/BinaryDictionary.java | 42 +++++- .../latin/BinaryDictionaryFileDumper.java | 141 +++++++++++++++++++++ .../inputmethod/latin/BinaryDictionaryGetter.java | 96 ++++++++++++++ .../DictionaryPackInstallBroadcastReceiver.java | 83 ++++++++++++ .../inputmethod/latin/InputLanguageSelection.java | 4 +- .../com/android/inputmethod/latin/LatinIME.java | 40 +++++- .../latin/PrivateBinaryDictionaryGetter.java | 28 ++++ .../src/com/android/inputmethod/latin/Suggest.java | 19 ++- java/src/com/android/inputmethod/latin/Utils.java | 4 +- .../android/inputmethod/latin/SuggestHelper.java | 4 +- 11 files changed, 497 insertions(+), 16 deletions(-) create mode 100644 java/src/com/android/inputmethod/latin/AssetFileAddress.java create mode 100644 java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java create mode 100644 java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java create mode 100644 java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java create mode 100644 java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java new file mode 100644 index 000000000..074ecacc5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java @@ -0,0 +1,52 @@ +/* + * 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; + +import java.io.File; + +/** + * Immutable class to hold the address of an asset. + * As opposed to a normal file, an asset is usually represented as a contiguous byte array in + * the package file. Open it correctly thus requires the name of the package it is in, but + * also the offset in the file and the length of this data. This class encapsulates these three. + */ +class AssetFileAddress { + public final String mFilename; + public final long mOffset; + public final long mLength; + + public AssetFileAddress(final String filename, final long offset, final long length) { + mFilename = filename; + mOffset = offset; + mLength = length; + } + + public static AssetFileAddress makeFromFileName(final String filename) { + if (null == filename) return null; + File f = new File(filename); + if (null == f || !f.isFile()) return null; + return new AssetFileAddress(filename, 0l, f.length()); + } + + public static AssetFileAddress makeFromFileNameAndOffset(final String filename, + final long offset, final long length) { + if (null == filename) return null; + File f = new File(filename); + if (null == f || !f.isFile()) return null; + return new AssetFileAddress(filename, offset, length); + } +} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index ca7261b43..fa90fce67 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -26,14 +26,18 @@ import android.util.Log; import java.io.File; import java.util.Arrays; +import java.util.Locale; /** * Implements a static, compacted, binary dictionary of standard words. */ public class BinaryDictionary extends Dictionary { + public static final String DICTIONARY_PACK_AUTHORITY = + "com.android.inputmethod.latin.dictionarypack"; + /** - * There is difference between what java and native code can handle. + * There is a difference between what java and native code can handle. * This value should only be used in BinaryDictionary.java * It is necessary to keep it at this value because some languages e.g. German have * really long words. @@ -85,10 +89,11 @@ public class BinaryDictionary extends Dictionary { } /** - * Initialize a dictionary from a raw resource file + * Initializes a dictionary from a raw resource file * @param context application context for reading resources * @param resId the resource containing the raw binary dictionary - * @return initialized instance of BinaryDictionary + * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_* + * @return an initialized instance of BinaryDictionary */ public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) { synchronized (sInstance) { @@ -146,6 +151,37 @@ public class BinaryDictionary extends Dictionary { Utils.loadNativeLibrary(); } + /** + * Initializes a dictionary from a dictionary pack. + * + * This searches for a content provider providing a dictionary pack for the specified + * locale. If none is found, it falls back to using the resource passed as fallBackResId + * as a dictionary. + * @param context application context for reading resources + * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_* + * @param locale the locale for which to create the dictionary + * @param fallBackResId the id of the resource to use as a fallback if no pack is found + * @return an initialized instance of BinaryDictionary + */ + public static BinaryDictionary initDictionaryFromManager(Context context, int dicTypeId, + Locale locale, int fallbackResId) { + if (null == locale) { + Log.e(TAG, "No locale defined for dictionary"); + return initDictionary(context, fallbackResId, dicTypeId); + } + synchronized (sInstance) { + sInstance.closeInternal(); + + final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale, + context, fallbackResId); + if (null != dictFile) { + sInstance.loadDictionary(dictFile.mFilename, dictFile.mOffset, dictFile.mLength); + sInstance.mDicTypeId = dicTypeId; + } + } + return sInstance; + } + private native int openNative(String sourceDir, long dictOffset, long dictSize, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java new file mode 100644 index 000000000..d0464dd94 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -0,0 +1,141 @@ +/* + * 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; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.text.TextUtils; + +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.Locale; + +/** + * Group class for static methods to help with creation and getting of the binary dictionary + * file from the dictionary provider + */ +public class BinaryDictionaryFileDumper { + /** + * The size of the temporary buffer to copy files. + */ + static final int FILE_READ_BUFFER_SIZE = 1024; + + // 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 + */ + 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; + } + + /** + * Return for a given locale the provider URI to query to get the dictionary. + */ + public static Uri getProviderUri(Locale locale) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath( + locale.toString()).build(); + } + + /** + * Queries a content provider for dictionary data for some locale and returns it as a file name. + * + * This will query a content provider for dictionary data for a given locale, and return + * the name of a file suitable to be mmap'ed. It will copy it to local storage if needed. + * It should also check the dictionary version 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. + * @returns the name of the file, 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 String getDictionaryFileFromContentProvider(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. + final ContentResolver resolver = context.getContentResolver(); + final Uri dictionaryPackUri = getProviderUri(locale); + final InputStream stream = resolver.openInputStream(dictionaryPackUri); + if (null == stream) return null; + return copyFileTo(stream, getCacheFileNameForLocale(locale, context)); + } + + /** + * 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)); + } + + /** + * Accepts a resource number as dictionary data for some locale and returns the name of a file. + * + * This will make the resource the cached dictionary for this locale, overwriting any previous + * cached data. + */ + public static String getDictionaryFileFromResource(int resource, Locale locale, + Context context) throws FileNotFoundException, IOException { + return copyFileTo(context.getResources().openRawResource(resource), + getCacheFileNameForLocale(locale, context)); + } + + /** + * Copies the data in an input stream to a target file, creating the file if necessary and + * overwriting it if it already exists. + */ + private static String copyFileTo(final InputStream input, final String outputFileName) + throws FileNotFoundException, IOException { + final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE]; + final FileOutputStream output = new FileOutputStream(outputFileName); + for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) + output.write(buffer, 0, readBytes); + input.close(); + return outputFileName; + } +} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java new file mode 100644 index 000000000..72512c7e1 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -0,0 +1,96 @@ +/* + * 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; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.util.Log; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Locale; + +/** + * Helper class to get the address of a mmap'able dictionary file. + */ +class BinaryDictionaryGetter { + + /** + * Used for Log actions from this class + */ + private static final String TAG = BinaryDictionaryGetter.class.getSimpleName(); + + // Prevents this from being instantiated + private BinaryDictionaryGetter() {} + + /** + * Returns a file address from a resource, or null if it cannot be opened. + */ + private static AssetFileAddress loadFallbackResource(Context context, int fallbackResId) { + final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId); + if (afd == null) { + Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId=" + + fallbackResId); + return null; + } + return AssetFileAddress.makeFromFileNameAndOffset( + context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength()); + } + + /** + * Returns a file address for a given locale, trying relevant methods in order. + * + * Tries to get a binary dictionary from various sources, in order: + * - Uses a private method of getting a private dictionary, as implemented by the + * PrivateBinaryDictionaryGetter class. + * If that fails: + * - Uses a content provider to get a public dictionary, as per the protocol described + * in BinaryDictionaryFileDumper. + * If that fails: + * - Gets a file name from the fallback resource passed as an argument. + * If that fails: + * - Returns null. + * @return The address of a valid file, or null. + * @throws FileNotFoundException if a dictionary provider returned a file name, but the + * file cannot be found. + * @throws IOException if there was an I/O problem reading or copying a file. + */ + public static AssetFileAddress getDictionaryFile(Locale locale, Context context, + int fallbackResId) { + // Try first to query a private file signed the same way. + final AssetFileAddress privateFile = + PrivateBinaryDictionaryGetter.getDictionaryFile(locale, context); + if (null != privateFile) { + return privateFile; + } else { + try { + // If that was no-go, try to find a publicly exported dictionary. + final String fileName = BinaryDictionaryFileDumper. + getDictionaryFileFromContentProvider(locale, context); + return AssetFileAddress.makeFromFileName(fileName); + } catch (FileNotFoundException e) { + Log.e(TAG, "Unable to create dictionary file from provider for locale " + + locale.toString() + ": falling back to internal dictionary"); + return loadFallbackResource(context, fallbackResId); + } catch (IOException e) { + Log.e(TAG, "Unable to read source data for locale " + + locale.toString() + ": falling back to internal dictionary"); + return loadFallbackResource(context, fallbackResId); + } + } + } +} diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java new file mode 100644 index 000000000..7a3bcd8f7 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java @@ -0,0 +1,83 @@ +/* + * 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; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.net.Uri; + +/** + * Takes action to reload the necessary data when a dictionary pack was added/removed. + */ +public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver { + + final LatinIME mService; + + public DictionaryPackInstallBroadcastReceiver(final LatinIME service) { + mService = service; + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final PackageManager manager = context.getPackageManager(); + + // We need to reread the dictionary if a new dictionary package is installed. + if (action.equals(Intent.ACTION_PACKAGE_ADDED)) { + final Uri packageUri = intent.getData(); + if (null == packageUri) return; // No package name : we can't do anything + final String packageName = packageUri.getSchemeSpecificPart(); + if (null == packageName) return; + final PackageInfo packageInfo; + try { + packageInfo = manager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS); + } catch (android.content.pm.PackageManager.NameNotFoundException e) { + return; // No package info : we can't do anything + } + final ProviderInfo[] providers = packageInfo.providers; + if (null == providers) return; // No providers : it is not a dictionary. + + // Search for some dictionary pack in the just-installed package. If found, reread. + boolean found = false; + for (ProviderInfo info : providers) { + if (BinaryDictionary.DICTIONARY_PACK_AUTHORITY.equals(info.authority)) { + mService.resetSuggestMainDict(); + return; + } + } + // If we come here none of the authorities matched the one we searched for. + // We can exit safely. + return; + } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) + && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + // When the dictionary package is removed, we need to reread dictionary (to use the + // next-priority one, or stop using a dictionary at all if this was the only one, + // since this is the user request). + // If we are replacing the package, we will receive ADDED right away so no need to + // remove the dictionary at the moment, since we will do it when we receive the + // ADDED broadcast. + + // TODO: Only reload dictionary on REMOVED when the removed package is the one we + // read dictionary from? + mService.resetSuggestMainDict(); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java index 5587c685f..be5e015aa 100644 --- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java +++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java @@ -106,8 +106,8 @@ public class InputLanguageSelection extends PreferenceActivity { conf.locale = locale; res.updateConfiguration(conf, res.getDisplayMetrics()); - int mainDicResId = Utils.getMainDictionaryResourceId(res); - BinaryDictionary bd = BinaryDictionary.initDictionary(this, mainDicResId, Suggest.DIC_MAIN); + BinaryDictionary bd = BinaryDictionary.initDictionaryFromManager(this, Suggest.DIC_MAIN, + locale, Utils.getMainDictionaryResourceId(res)); // Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of // 4000-5000 words, whereas the LARGE_DICTIONARY is about 20000+ words. diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 3d1b4e031..e5beb2c4e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -119,6 +119,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Key events coming any faster than this are long-presses. private static final int QUICK_PRESS = 200; + /** + * The name of the scheme used by the Package Manager to warn of a new package installation, + * replacement or removal. + */ + private static final String SCHEME_PACKAGE = "package"; + private int mSuggestionVisibility; private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE = R.string.prefs_suggestion_visibility_show_value; @@ -207,6 +213,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: Move this flag to VoiceIMEConnector private boolean mConfigurationChanging; + // Object for reacting to adding/removing a dictionary pack. + private BroadcastReceiver mDictionaryPackInstallReceiver = + new DictionaryPackInstallBroadcastReceiver(this); + // Keeps track of most recently inserted text (multi-character key) for reverting private CharSequence mEnteredText; @@ -414,18 +424,26 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mOrientation = res.getConfiguration().orientation; initSuggestPuncList(); - // register to receive ringer mode change and network state change. + // Register to receive ringer mode change and network state change. + // Also receive installation and removal of a dictionary pack. final IntentFilter filter = new IntentFilter(); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mReceiver, filter); mVoiceConnector = VoiceConnector.init(this, prefs, mHandler); + + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme(SCHEME_PACKAGE); + registerReceiver(mDictionaryPackInstallReceiver, packageFilter); } private void initSuggest() { - String locale = mSubtypeSwitcher.getInputLocaleStr(); + final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); + final Locale keyboardLocale = new Locale(localeStr); - Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale)); + final Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(keyboardLocale); if (mSuggest != null) { mSuggest.close(); } @@ -434,20 +452,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final Resources res = mResources; int mainDicResId = Utils.getMainDictionaryResourceId(res); - mSuggest = new Suggest(this, mainDicResId); + mSuggest = new Suggest(this, mainDicResId, keyboardLocale); loadAndSetAutoCorrectionThreshold(prefs); updateAutoTextEnabled(); - mUserDictionary = new UserDictionary(this, locale); + mUserDictionary = new UserDictionary(this, localeStr); mSuggest.setUserDictionary(mUserDictionary); mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS); mSuggest.setContactsDictionary(mContactsDictionary); - mAutoDictionary = new AutoDictionary(this, this, locale, Suggest.DIC_AUTO); + mAutoDictionary = new AutoDictionary(this, this, localeStr, Suggest.DIC_AUTO); mSuggest.setAutoDictionary(mAutoDictionary); - mUserBigramDictionary = new UserBigramDictionary(this, this, locale, Suggest.DIC_USER); + mUserBigramDictionary = new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER); mSuggest.setUserBigramDictionary(mUserBigramDictionary); updateCorrectionMode(); @@ -457,6 +475,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeSwitcher.changeSystemLocale(savedLocale); } + /* package private */ void resetSuggestMainDict() { + final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); + final Locale keyboardLocale = new Locale(localeStr); + int mainDicResId = Utils.getMainDictionaryResourceId(mResources); + mSuggest.resetMainDict(this, mainDicResId, keyboardLocale); + } + @Override public void onDestroy() { if (mSuggest != null) { @@ -464,6 +489,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSuggest = null; } unregisterReceiver(mReceiver); + unregisterReceiver(mDictionaryPackInstallReceiver); mVoiceConnector.destroy(); LatinImeLogger.commit(); LatinImeLogger.onDestroy(); diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java new file mode 100644 index 000000000..90726b0d8 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java @@ -0,0 +1,28 @@ +/* + * 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; + +import android.content.Context; + +import java.util.Locale; + +class PrivateBinaryDictionaryGetter { + private PrivateBinaryDictionaryGetter() {} + public static AssetFileAddress getDictionaryFile(Locale locale, Context context) { + return null; + } +} diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index a61a7f159..0cc9d4198 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -106,8 +107,9 @@ public class Suggest implements Dictionary.WordCallback { private int mCorrectionMode = CORRECTION_BASIC; - public Suggest(Context context, int dictionaryResId) { - init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN)); + public Suggest(Context context, int dictionaryResId, Locale locale) { + init(context, BinaryDictionary.initDictionaryFromManager(context, DIC_MAIN, locale, + dictionaryResId)); } /* package for test */ Suggest(File dictionary, long startOffset, long length, @@ -130,6 +132,19 @@ public class Suggest implements Dictionary.WordCallback { initPool(); } + public void resetMainDict(Context context, int dictionaryResId, Locale locale) { + final BinaryDictionary newMainDict = BinaryDictionary.initDictionaryFromManager(context, + DIC_MAIN, locale, dictionaryResId); + mMainDict = newMainDict; + if (null == newMainDict) { + mUnigramDictionaries.remove(DICT_KEY_MAIN); + mBigramDictionaries.remove(DICT_KEY_MAIN); + } else { + mUnigramDictionaries.put(DICT_KEY_MAIN, newMainDict); + mBigramDictionaries.put(DICT_KEY_MAIN, newMainDict); + } + } + private void initPool() { for (int i = 0; i < mPrefMaxSuggestions; i++) { StringBuilder sb = new StringBuilder(getApproxMaxWordLength()); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 608d7e967..e176f76b8 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -551,7 +551,9 @@ public class Utils { * @return main dictionary resource id */ public static int getMainDictionaryResourceId(Resources res) { - return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName()); + final String MAIN_DIC_NAME = "main"; + String packageName = LatinIME.class.getPackage().getName(); + return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName); } public static void loadNativeLibrary() { diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java index aa7b76cc3..1d0a5b7eb 100644 --- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java +++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java @@ -34,7 +34,9 @@ public class SuggestHelper { private final KeyDetector mKeyDetector; public SuggestHelper(Context context, int dictionaryId, KeyboardId keyboardId) { - mSuggest = new Suggest(context, dictionaryId); + // Use null as the locale for Suggest so as to force it to use the internal dictionary + // (and not try to find a dictionary provider for a specified locale) + mSuggest = new Suggest(context, dictionaryId, null); mKeyboard = new LatinKeyboard(context, keyboardId); mKeyDetector = new ProximityKeyDetector(); init(); -- cgit v1.2.3-83-g751a From 610f1dc8553cf2ed97e763a06a19380c4a6cd636 Mon Sep 17 00:00:00 2001 From: satok Date: Thu, 17 Mar 2011 22:49:06 -0700 Subject: Use reflections for classes related to InputMethodSubtype Change-Id: Ica53ce879c2b4c5eb47f757fb788a795a881c30e --- .../inputmethod/compat/AbstractCompatWrapper.java | 35 +++++++ .../android/inputmethod/compat/CompatUtils.java | 56 ++++++++++ .../compat/InputMethodManagerCompatWrapper.java | 115 +++++++++++++++++++++ .../compat/InputMethodSubtypeCompatWrapper.java | 93 +++++++++++++++++ .../inputmethod/deprecated/VoiceConnector.java | 6 +- .../inputmethod/keyboard/KeyboardSwitcher.java | 5 +- .../com/android/inputmethod/latin/LatinIME.java | 9 +- .../com/android/inputmethod/latin/Settings.java | 7 +- .../android/inputmethod/latin/SubtypeSwitcher.java | 36 ++++--- java/src/com/android/inputmethod/latin/Utils.java | 6 +- 10 files changed, 333 insertions(+), 35 deletions(-) create mode 100644 java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java create mode 100644 java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java create mode 100644 java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java new file mode 100644 index 000000000..99262c434 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/AbstractCompatWrapper.java @@ -0,0 +1,35 @@ +/* + * 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.compat; + +import android.util.Log; + +public abstract class AbstractCompatWrapper { + private static final String TAG = AbstractCompatWrapper.class.getSimpleName(); + protected final Object mObj; + + public AbstractCompatWrapper(Object obj) { + if (obj == null) { + Log.e(TAG, "Invalid input to AbstructCompatWrapper"); + } + mObj = obj; + } + + public Object getOriginalObject() { + return mObj; + } +} diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java index e1e812d83..157446654 100644 --- a/java/src/com/android/inputmethod/compat/CompatUtils.java +++ b/java/src/com/android/inputmethod/compat/CompatUtils.java @@ -18,8 +18,15 @@ package com.android.inputmethod.compat; import android.content.Intent; import android.text.TextUtils; +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; public class CompatUtils { + private static final String TAG = CompatUtils.class.getSimpleName(); private static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; // TODO: Can these be constants instead of literal String constants? private static final String INPUT_METHOD_SUBTYPE_SETTINGS = @@ -48,4 +55,53 @@ public class CompatUtils { } return intent; } + + public static Class getClass(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + return null; + } + } + + public static Method getMethod(Class targetClass, String name, + Class... parameterTypes) { + try { + return targetClass.getMethod(name, parameterTypes); + } catch (SecurityException e) { + // ignore + return null; + } catch (NoSuchMethodException e) { + // ignore + return null; + } + } + + public static Object invoke( + Object receiver, Object defaultValue, Method method, Object... args) { + if (receiver == null || method == null) return defaultValue; + try { + return method.invoke(receiver, args); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Exception in invoke: IllegalArgmentException"); + return defaultValue; + } catch (IllegalAccessException e) { + Log.e(TAG, "Exception in invoke: IllegalAccessException"); + return defaultValue; + } catch (InvocationTargetException e) { + Log.e(TAG, "Exception in invoke: IllegalTargetException"); + return defaultValue; + } + } + + public static List copyInputMethodSubtypeListToWrappler( + Object listObject) { + if (!(listObject instanceof List)) return null; + final List subtypes = + new ArrayList(); + for (Object o: (List)listObject) { + subtypes.add(new InputMethodSubtypeCompatWrapper(o)); + } + return subtypes; + } } diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java new file mode 100644 index 000000000..648b1892b --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java @@ -0,0 +1,115 @@ +/* + * 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.compat; + +import android.content.Context; +import android.os.IBinder; +import android.util.Log; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// TODO: Override this class with the concrete implementation if we need to take care of the +// performance. +public class InputMethodManagerCompatWrapper { + private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName(); + private static final Method METHOD_getCurrentInputMethodSubtype = + CompatUtils.getMethod(InputMethodManager.class, "getCurrentInputMethodSubtype"); + private static final Method METHOD_getEnabledInputMethodSubtypeList = + CompatUtils.getMethod(InputMethodManager.class, "getEnabledInputMethodSubtypeList", + InputMethodInfo.class, boolean.class); + private static final Method METHOD_getShortcutInputMethodsAndSubtypes = + CompatUtils.getMethod(InputMethodManager.class, "getShortcutInputMethodsAndSubtypes"); + private static final Method METHOD_setInputMethodAndSubtype = + CompatUtils.getMethod( + InputMethodManager.class, "setInputMethodAndSubtype", IBinder.class, + String.class, InputMethodSubtypeCompatWrapper.CLASS_InputMethodSubtype); + + private static final InputMethodManagerCompatWrapper sInstance = + new InputMethodManagerCompatWrapper(); + + private InputMethodManager mImm; + private InputMethodManagerCompatWrapper() { + } + + public static InputMethodManagerCompatWrapper getInstance(Context context) { + if (sInstance.mImm == null) { + sInstance.init(context); + } + return sInstance; + } + + private synchronized void init(Context context) { + mImm = (InputMethodManager) context.getSystemService( + Context.INPUT_METHOD_SERVICE); + } + + public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() { + return new InputMethodSubtypeCompatWrapper( + CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype)); + } + + public List getEnabledInputMethodSubtypeList( + InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { + Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList, + imi, allowsImplicitlySelectedSubtypes); + return CompatUtils.copyInputMethodSubtypeListToWrappler((List)retval); + } + + public Map> + getShortcutInputMethodsAndSubtypes() { + Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes); + if (!(retval instanceof Map)) return null; + Map> shortcutMap = + new HashMap>(); + final Map retvalMap = (Map)retval; + for (Object key: retvalMap.keySet()) { + if (!(key instanceof InputMethodInfo)) { + Log.e(TAG, "Class type error."); + return null; + } + shortcutMap.put((InputMethodInfo)key, CompatUtils.copyInputMethodSubtypeListToWrappler( + retvalMap.get(key))); + } + return shortcutMap; + } + + public void setInputMethodAndSubtype( + IBinder token, String id, InputMethodSubtypeCompatWrapper subtype) { + CompatUtils.invoke(mImm, null, METHOD_setInputMethodAndSubtype, + token, id, subtype.getOriginalObject()); + } + + public boolean switchToLastInputMethod(IBinder token) { + if (mImm == null) return false; + return mImm.switchToLastInputMethod(token); + } + + public List getEnabledInputMethodList() { + if (mImm == null) return null; + return mImm.getEnabledInputMethodList(); + } + + public void showInputMethodPicker() { + if (mImm == null) return; + mImm.showInputMethodPicker(); + } +} diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java new file mode 100644 index 000000000..ce031eea5 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java @@ -0,0 +1,93 @@ +/* + * 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.compat; + +import com.android.inputmethod.latin.LatinImeLogger; + +import android.util.Log; + +import java.lang.reflect.Method; + +// TODO: Override this class with the concrete implementation if we need to take care of the +// performance. +public final class InputMethodSubtypeCompatWrapper extends AbstractCompatWrapper { + private static final boolean DBG = LatinImeLogger.sDBG; + private static final String TAG = InputMethodSubtypeCompatWrapper.class.getSimpleName(); + + public static final Class CLASS_InputMethodSubtype = + CompatUtils.getClass("android.view.inputmethod.InputMethodSubtype"); + private static final Method METHOD_getNameResId = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getNameResId"); + private static final Method METHOD_getIconResId = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getIconResId"); + private static final Method METHOD_getLocale = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getLocale"); + private static final Method METHOD_getMode = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getMode"); + private static final Method METHOD_getExtraValue = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValue"); + private static final Method METHOD_containsExtraValueKey = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "containsExtraValueKey", String.class); + private static final Method METHOD_getExtraValueOf = + CompatUtils.getMethod(CLASS_InputMethodSubtype, "getExtraValueOf", String.class); + + public InputMethodSubtypeCompatWrapper(Object subtype) { + super(CLASS_InputMethodSubtype.isInstance(subtype) ? subtype : null); + if (DBG) { + Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper"); + } + } + + public int getNameResId() { + return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getNameResId); + } + + public int getIconResId() { + return (Integer)CompatUtils.invoke(mObj, 0, METHOD_getIconResId); + } + + public String getLocale() { + return (String)CompatUtils.invoke(mObj, null, METHOD_getLocale); + } + + public String getMode() { + return (String)CompatUtils.invoke(mObj, null, METHOD_getMode); + } + + public String getExtraValue() { + return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValue); + } + + public boolean containsExtraValueKey(String key) { + return (Boolean)CompatUtils.invoke(mObj, null, METHOD_containsExtraValueKey, key); + } + + public String getExtraValueOf(String key) { + return (String)CompatUtils.invoke(mObj, null, METHOD_getExtraValueOf, key); + } + + @Override + public boolean equals(Object o) { + if (o instanceof InputMethodSubtypeCompatWrapper) { + InputMethodSubtypeCompatWrapper subtype = (InputMethodSubtypeCompatWrapper)o; + return mObj.equals(subtype.getOriginalObject()); + } else { + return mObj.equals(o); + } + } + +} diff --git a/java/src/com/android/inputmethod/deprecated/VoiceConnector.java b/java/src/com/android/inputmethod/deprecated/VoiceConnector.java index 7c2260985..5c78e9d24 100644 --- a/java/src/com/android/inputmethod/deprecated/VoiceConnector.java +++ b/java/src/com/android/inputmethod/deprecated/VoiceConnector.java @@ -16,6 +16,7 @@ package com.android.inputmethod.deprecated; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.deprecated.voice.FieldContext; import com.android.inputmethod.deprecated.voice.Hints; import com.android.inputmethod.deprecated.voice.SettingsUtil; @@ -60,7 +61,6 @@ import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import java.util.ArrayList; @@ -99,7 +99,7 @@ public class VoiceConnector implements VoiceInput.UiListener { private boolean mVoiceButtonOnPrimary; private boolean mVoiceInputHighlighted; - private InputMethodManager mImm; + private InputMethodManagerCompatWrapper mImm; private LatinIME mService; private AlertDialog mVoiceWarningDialog; private VoiceInput mVoiceInput; @@ -123,7 +123,7 @@ public class VoiceConnector implements VoiceInput.UiListener { private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) { mService = service; mHandler = h; - mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); + mImm = InputMethodManagerCompatWrapper.getInstance(service); mSubtypeSwitcher = SubtypeSwitcher.getInstance(); if (VOICE_INSTALLED) { mVoiceInput = new VoiceInput(service, this); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 64a23ab92..cfa3c446e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -16,6 +16,7 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -29,7 +30,6 @@ import android.content.res.Resources; import android.util.Log; import android.view.InflateException; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import java.lang.ref.SoftReference; import java.util.HashMap; @@ -752,8 +752,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW)) || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO)) && Utils.hasMultipleEnabledIMEsOrSubtypes( - ((InputMethodManager) context.getSystemService( - Context.INPUT_METHOD_SERVICE))))) { + (InputMethodManagerCompatWrapper.getInstance(context))))) { return true; } return false; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 297abfc6d..b34d4575b 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -17,6 +17,8 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.CompatUtils; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; import com.android.inputmethod.deprecated.VoiceConnector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; @@ -67,7 +69,6 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import android.widget.FrameLayout; import android.widget.HorizontalScrollView; @@ -147,7 +148,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private AlertDialog mOptionsDialog; - private InputMethodManager mImm; + private InputMethodManagerCompatWrapper mImm; private Resources mResources; private SharedPreferences mPrefs; private String mInputMethodId; @@ -380,7 +381,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen super.onCreate(); - mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)); + mImm = InputMethodManagerCompatWrapper.getInstance(this); mInputMethodId = Utils.getInputMethodId(mImm, getPackageName()); mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mKeyboardSwitcher = KeyboardSwitcher.getInstance(); @@ -2341,6 +2342,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { - SubtypeSwitcher.getInstance().updateSubtype(subtype); + SubtypeSwitcher.getInstance().updateSubtype(new InputMethodSubtypeCompatWrapper(subtype)); } } diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 873b9efb5..7bb1745fb 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -17,14 +17,13 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.CompatUtils; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.deprecated.VoiceConnector; import android.app.AlertDialog; import android.app.Dialog; import android.app.backup.BackupManager; -import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Vibrator; @@ -40,7 +39,6 @@ import android.text.AutoText; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.util.Log; -import android.view.inputmethod.InputMethodManager; import android.widget.TextView; import java.util.Locale; @@ -225,8 +223,7 @@ public class Settings extends PreferenceActivity public boolean onPreferenceClick(Preference pref) { if (pref == mInputLanguageSelection) { startActivity(CompatUtils.getInputLanguageSelectionIntent( - Utils.getInputMethodId( - (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE), + Utils.getInputMethodId(InputMethodManagerCompatWrapper.getInstance(this), getApplicationInfo().packageName), 0)); return true; } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 25e52c672..4bdd01556 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; import com.android.inputmethod.deprecated.VoiceConnector; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.LatinKeyboard; @@ -33,8 +35,6 @@ import android.os.IBinder; import android.text.TextUtils; import android.util.Log; import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; import java.util.ArrayList; import java.util.Arrays; @@ -57,12 +57,13 @@ public class SubtypeSwitcher { private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); private /* final */ LatinIME mService; private /* final */ SharedPreferences mPrefs; - private /* final */ InputMethodManager mImm; + private /* final */ InputMethodManagerCompatWrapper mImm; private /* final */ Resources mResources; private /* final */ ConnectivityManager mConnectivityManager; private /* final */ boolean mConfigUseSpacebarLanguageSwitcher; - private final ArrayList mEnabledKeyboardSubtypesOfCurrentInputMethod = - new ArrayList(); + private final ArrayList + mEnabledKeyboardSubtypesOfCurrentInputMethod = + new ArrayList(); private final ArrayList mEnabledLanguagesOfCurrentInputMethod = new ArrayList(); /*-----------------------------------------------------------*/ @@ -70,9 +71,9 @@ public class SubtypeSwitcher { private boolean mNeedsToDisplayLanguage; private boolean mIsSystemLanguageSameAsInputLanguage; private InputMethodInfo mShortcutInputMethodInfo; - private InputMethodSubtype mShortcutSubtype; - private List mAllEnabledSubtypesOfCurrentInputMethod; - private InputMethodSubtype mCurrentSubtype; + private InputMethodSubtypeCompatWrapper mShortcutSubtype; + private List mAllEnabledSubtypesOfCurrentInputMethod; + private InputMethodSubtypeCompatWrapper mCurrentSubtype; private Locale mSystemLocale; private Locale mInputLocale; private String mInputLocaleStr; @@ -100,7 +101,7 @@ public class SubtypeSwitcher { mService = service; mPrefs = prefs; mResources = service.getResources(); - mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); + mImm = InputMethodManagerCompatWrapper.getInstance(service); mConnectivityManager = (ConnectivityManager) service.getSystemService( Context.CONNECTIVITY_SERVICE); mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); @@ -148,7 +149,7 @@ public class SubtypeSwitcher { null, true); mEnabledLanguagesOfCurrentInputMethod.clear(); mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); - for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) { + for (InputMethodSubtypeCompatWrapper ims: mAllEnabledSubtypesOfCurrentInputMethod) { final String locale = ims.getLocale(); final String mode = ims.getMode(); mLocaleSplitter.setString(locale); @@ -182,10 +183,10 @@ public class SubtypeSwitcher { + ", " + mShortcutSubtype.getMode()))); } // TODO: Update an icon for shortcut IME - Map> shortcuts = + Map> shortcuts = mImm.getShortcutInputMethodsAndSubtypes(); for (InputMethodInfo imi: shortcuts.keySet()) { - List subtypes = shortcuts.get(imi); + List subtypes = shortcuts.get(imi); // TODO: Returns the first found IMI for now. Should handle all shortcuts as // appropriate. mShortcutInputMethodInfo = imi; @@ -204,7 +205,7 @@ public class SubtypeSwitcher { } // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. - public void updateSubtype(InputMethodSubtype newSubtype) { + public void updateSubtype(InputMethodSubtypeCompatWrapper newSubtype) { final String newLocale; final String newMode; final String oldMode = getCurrentSubtypeMode(); @@ -306,7 +307,7 @@ public class SubtypeSwitcher { return; } final String imiId = mShortcutInputMethodInfo.getId(); - final InputMethodSubtype subtype = mShortcutSubtype; + final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype; new Thread("SwitchToShortcutIME") { @Override public void run() { @@ -319,7 +320,7 @@ public class SubtypeSwitcher { return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype); } - private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) { + private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtypeCompatWrapper subtype) { final PackageManager pm = mService.getPackageManager(); if (imi != null) { final String imiPackageName = imi.getPackageName(); @@ -359,8 +360,9 @@ public class SubtypeSwitcher { if (mShortcutSubtype == null) return true; final boolean allowsImplicitlySelectedSubtypes = true; - for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList( - mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) { + for (final InputMethodSubtypeCompatWrapper enabledSubtype : + mImm.getEnabledInputMethodSubtypeList( + mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) { if (enabledSubtype.equals(mShortcutSubtype)) return true; } diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index e176f76b8..f48e1184b 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.keyboard.KeyboardId; import android.content.res.Resources; @@ -29,7 +30,6 @@ import android.text.format.DateUtils; import android.util.Log; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; import java.io.BufferedReader; import java.io.File; @@ -101,14 +101,14 @@ public class Utils { } } - public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm) { + public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) { return imm.getEnabledInputMethodList().size() > 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; } - public static String getInputMethodId(InputMethodManager imm, String packageName) { + public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) { for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { if (imi.getPackageName().equals(packageName)) return imi.getId(); -- cgit v1.2.3-83-g751a From 10dd34de0ffcde0104f7d2dae3a3c9fd66abffcc Mon Sep 17 00:00:00 2001 From: satok Date: Fri, 18 Mar 2011 21:22:28 -0700 Subject: Add InputMethodServiceCompatWrapper for moving the callback from LatinIME. Change-Id: If6b0180c63738e5e78a20fe5b938ef50d62865d6 --- .../android/inputmethod/compat/CompatUtils.java | 2 +- .../compat/InputMethodManagerCompatWrapper.java | 4 +- .../compat/InputMethodServiceCompatWrapper.java | 65 ++++++++++++++++++++++ .../inputmethod/deprecated/VoiceConnector.java | 15 ++++- .../com/android/inputmethod/latin/LatinIME.java | 11 +--- .../android/inputmethod/latin/SubtypeSwitcher.java | 19 ++++++- 6 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java index 157446654..fea15fda5 100644 --- a/java/src/com/android/inputmethod/compat/CompatUtils.java +++ b/java/src/com/android/inputmethod/compat/CompatUtils.java @@ -94,7 +94,7 @@ public class CompatUtils { } } - public static List copyInputMethodSubtypeListToWrappler( + public static List copyInputMethodSubtypeListToWrapper( Object listObject) { if (!(listObject instanceof List)) return null; final List subtypes = diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java index 648b1892b..81cf02c35 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java @@ -71,7 +71,7 @@ public class InputMethodManagerCompatWrapper { InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList, imi, allowsImplicitlySelectedSubtypes); - return CompatUtils.copyInputMethodSubtypeListToWrappler((List)retval); + return CompatUtils.copyInputMethodSubtypeListToWrapper((List)retval); } public Map> @@ -86,7 +86,7 @@ public class InputMethodManagerCompatWrapper { Log.e(TAG, "Class type error."); return null; } - shortcutMap.put((InputMethodInfo)key, CompatUtils.copyInputMethodSubtypeListToWrappler( + shortcutMap.put((InputMethodInfo)key, CompatUtils.copyInputMethodSubtypeListToWrapper( retvalMap.get(key))); } return shortcutMap; diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java new file mode 100644 index 000000000..72f3ca0d0 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java @@ -0,0 +1,65 @@ +/* + * 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.compat; + +import com.android.inputmethod.latin.SubtypeSwitcher; + +import android.inputmethodservice.InputMethodService; +import android.view.inputmethod.InputMethodSubtype; + +public class InputMethodServiceCompatWrapper extends InputMethodService { + // CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED needs to be false if the API level is 10 + // or previous. Note that InputMethodSubtype was added in the API level 11. + // For the API level 11 or later, LatinIME should override onCurrentInputMethodSubtypeChanged(). + // For the API level 10 or previous, we handle the "subtype changed" events by ourselves + // without having support from framework -- onCurrentInputMethodSubtypeChanged(). + private static final boolean CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED = true; + + private InputMethodManagerCompatWrapper mImm; + + @Override + public void onCreate() { + super.onCreate(); + mImm = InputMethodManagerCompatWrapper.getInstance(this); + } + + // When the API level is 10 or previous, notifyOnCurrentInputMethodSubtypeChanged should + // handle the event the current subtype was changed. LatinIME calls + // notifyOnCurrentInputMethodSubtypeChanged every time LatinIME + // changes the current subtype. + // This call is required to let LatinIME itself know a subtype changed + // event when the API level is 10 or previous. + @SuppressWarnings("unused") + public void notifyOnCurrentInputMethodSubtypeChanged(InputMethodSubtypeCompatWrapper subtype) { + // Do nothing when the API level is 11 or later + if (CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) return; + if (subtype == null) { + subtype = mImm.getCurrentInputMethodSubtype(); + } + if (subtype != null) { + SubtypeSwitcher.getInstance().updateSubtype(subtype); + } + } + + @Override + public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { + // Do nothing when the API level is 10 or previous + if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) return; + SubtypeSwitcher.getInstance().updateSubtype( + new InputMethodSubtypeCompatWrapper(subtype)); + } +} diff --git a/java/src/com/android/inputmethod/deprecated/VoiceConnector.java b/java/src/com/android/inputmethod/deprecated/VoiceConnector.java index 5c78e9d24..a5c307c5b 100644 --- a/java/src/com/android/inputmethod/deprecated/VoiceConnector.java +++ b/java/src/com/android/inputmethod/deprecated/VoiceConnector.java @@ -566,14 +566,24 @@ public class VoiceConnector implements VoiceInput.UiListener { @Override protected void onPostExecute(Boolean result) { + // Calls in this method need to be done in the same thread as the thread which + // called switchToLastInputMethod() if (!result) { if (DEBUG) { Log.d(TAG, "Couldn't switch back to last IME."); } - // Needs to reset here because LatinIME failed to back to any IME and - // the same voice subtype will be triggered in the next time. + // Because the current IME and subtype failed to switch to any other IME and + // subtype by switchToLastInputMethod, the current IME and subtype should keep + // being LatinIME and voice subtype in the next time. And for re-showing voice + // mode, the state of voice input should be reset and the voice view should be + // hidden. mVoiceInput.reset(); mService.requestHideSelf(0); + } else { + // Notify an event that the current subtype was changed. This event will be + // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented + // when the API level is 10 or previous. + mService.notifyOnCurrentInputMethodSubtypeChanged(null); } } }.execute(); @@ -630,6 +640,7 @@ public class VoiceConnector implements VoiceInput.UiListener { } private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { + @SuppressWarnings("deprecation") final boolean noMic = Utils.inPrivateImeOptions(null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute) || Utils.inPrivateImeOptions(mService.getPackageName(), diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index b34d4575b..b89fcbfcf 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -18,7 +18,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.CompatUtils; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; -import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; +import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; import com.android.inputmethod.deprecated.VoiceConnector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; @@ -69,7 +69,6 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodSubtype; import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; @@ -83,7 +82,7 @@ import java.util.Locale; /** * Input method implementation for Qwerty'ish keyboard. */ -public class LatinIME extends InputMethodService implements KeyboardActionListener { +public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean PERF_DEBUG = false; private static final boolean TRACE = false; @@ -96,6 +95,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed. */ + @SuppressWarnings("dep-ann") public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm"; /** @@ -2339,9 +2339,4 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); } - - @Override - public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { - SubtypeSwitcher.getInstance().updateSubtype(new InputMethodSubtypeCompatWrapper(subtype)); - } } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 4bdd01556..d4db7d0f7 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -31,6 +31,7 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.os.AsyncTask; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; @@ -308,12 +309,24 @@ public class SubtypeSwitcher { } final String imiId = mShortcutInputMethodInfo.getId(); final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype; - new Thread("SwitchToShortcutIME") { + new AsyncTask() { @Override - public void run() { + protected Void doInBackground(Void... params) { mImm.setInputMethodAndSubtype(token, imiId, subtype); + return null; } - }.start(); + + @Override + protected void onPostExecute(Void result) { + // Calls in this method need to be done in the same thread as the thread which + // called switchToShortcutIME(). + + // Notify an event that the current subtype was changed. This event will be + // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented + // when the API level is 10 or previous. + mService.notifyOnCurrentInputMethodSubtypeChanged(subtype); + } + }.execute(); } public Drawable getShortcutIcon() { -- cgit v1.2.3-83-g751a From 4fc18095f57333aa6589274afa06e23d17545c76 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 23 Mar 2011 11:17:18 -0700 Subject: Remove a useless variable. Change-Id: Ifec283fcd397f7f42933220f19a807879ef6195e --- .../inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java | 1 - 1 file changed, 1 deletion(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java index 7a3bcd8f7..c7864621d 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java +++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java @@ -56,7 +56,6 @@ public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver { if (null == providers) return; // No providers : it is not a dictionary. // Search for some dictionary pack in the just-installed package. If found, reread. - boolean found = false; for (ProviderInfo info : providers) { if (BinaryDictionary.DICTIONARY_PACK_AUTHORITY.equals(info.authority)) { mService.resetSuggestMainDict(); -- cgit v1.2.3-83-g751a From 6dd7778d220d464755d35d9769fd6d3b634ee204 Mon Sep 17 00:00:00 2001 From: satok Date: Wed, 23 Mar 2011 12:59:58 -0700 Subject: Fix a bug that Voice IME is not triggered properly. Background: Both SubtypeSwitcher and VoiceConnector have an instance of VoiceInput. And VoiceConnector has a responsivity for setting VoiceInput to SubtypeSwitcher. But in case that VoiceInputConnector already has an instance of VoiceInput VoiceInputConnector doesn't set VoiceInput to SubtypeSwitcher. Change-Id: I42d0220e7d84a08e03f143213cc6eff87e7e79a6 --- java/src/com/android/inputmethod/deprecated/VoiceConnector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/deprecated/VoiceConnector.java b/java/src/com/android/inputmethod/deprecated/VoiceConnector.java index a5c307c5b..4ea2d1af4 100644 --- a/java/src/com/android/inputmethod/deprecated/VoiceConnector.java +++ b/java/src/com/android/inputmethod/deprecated/VoiceConnector.java @@ -793,8 +793,8 @@ public class VoiceConnector implements VoiceInput.UiListener { public void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) { if (mVoiceInput == null && voiceInput != null) { mVoiceInput = voiceInput; - switcher.setVoiceInputConnector(this); } + switcher.setVoiceInputConnector(this); } private VoiceInputConnector() { -- cgit v1.2.3-83-g751a From 2e36fb68d014838a24d1daa1bcbda04254d69be1 Mon Sep 17 00:00:00 2001 From: satok Date: Wed, 23 Mar 2011 14:01:05 -0700 Subject: Added InputMethodInfoCompatWrapper Change-Id: I5f2970e986afbe3b0b24ccf819f57f524e83ef3f --- .../compat/InputMethodInfoCompatWrapper.java | 59 ++++++++++++++++++++++ .../compat/InputMethodManagerCompatWrapper.java | 25 +++++---- .../android/inputmethod/latin/SubtypeSwitcher.java | 11 ++-- java/src/com/android/inputmethod/latin/Utils.java | 4 +- 4 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java new file mode 100644 index 000000000..8e22bbc79 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputMethodInfoCompatWrapper.java @@ -0,0 +1,59 @@ +/* + * 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.compat; + +import android.content.pm.ServiceInfo; +import android.view.inputmethod.InputMethodInfo; + +import java.lang.reflect.Method; + +public class InputMethodInfoCompatWrapper { + private final InputMethodInfo mImi; + private static final Method METHOD_getSubtypeAt = CompatUtils.getMethod( + InputMethodInfo.class, "getSubtypeAt", int.class); + private static final Method METHOD_getSubtypeCount = CompatUtils.getMethod( + InputMethodInfo.class, "getSubtypeCount"); + + public InputMethodInfoCompatWrapper(InputMethodInfo imi) { + mImi = imi; + } + + public InputMethodInfo getInputMethodInfo() { + return mImi; + } + + public String getId() { + return mImi.getId(); + } + + public String getPackageName() { + return mImi.getPackageName(); + } + + public ServiceInfo getServiceInfo() { + return mImi.getServiceInfo(); + } + + public int getSubtypeCount() { + return (Integer) CompatUtils.invoke(mImi, 0, METHOD_getSubtypeCount); + } + + public InputMethodSubtypeCompatWrapper getSubtypeAt(int index) { + return new InputMethodSubtypeCompatWrapper(CompatUtils.invoke(mImi, null, + METHOD_getSubtypeAt, index)); + } +} diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java index 81cf02c35..d1747ba5f 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java @@ -23,6 +23,7 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -68,26 +69,26 @@ public class InputMethodManagerCompatWrapper { } public List getEnabledInputMethodSubtypeList( - InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { + InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) { Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList, - imi, allowsImplicitlySelectedSubtypes); + (imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes); return CompatUtils.copyInputMethodSubtypeListToWrapper((List)retval); } - public Map> + public Map> getShortcutInputMethodsAndSubtypes() { Object retval = CompatUtils.invoke(mImm, null, METHOD_getShortcutInputMethodsAndSubtypes); if (!(retval instanceof Map)) return null; - Map> shortcutMap = - new HashMap>(); + Map> shortcutMap = + new HashMap>(); final Map retvalMap = (Map)retval; - for (Object key: retvalMap.keySet()) { + for (Object key : retvalMap.keySet()) { if (!(key instanceof InputMethodInfo)) { Log.e(TAG, "Class type error."); return null; } - shortcutMap.put((InputMethodInfo)key, CompatUtils.copyInputMethodSubtypeListToWrapper( - retvalMap.get(key))); + shortcutMap.put(new InputMethodInfoCompatWrapper((InputMethodInfo)key), + CompatUtils.copyInputMethodSubtypeListToWrapper(retvalMap.get(key))); } return shortcutMap; } @@ -103,9 +104,13 @@ public class InputMethodManagerCompatWrapper { return mImm.switchToLastInputMethod(token); } - public List getEnabledInputMethodList() { + public List getEnabledInputMethodList() { if (mImm == null) return null; - return mImm.getEnabledInputMethodList(); + List imis = new ArrayList(); + for (InputMethodInfo imi : mImm.getEnabledInputMethodList()) { + imis.add(new InputMethodInfoCompatWrapper(imi)); + } + return imis; } public void showInputMethodPicker() { diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index d4db7d0f7..9bc96995f 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; import com.android.inputmethod.deprecated.VoiceConnector; @@ -35,7 +36,6 @@ import android.os.AsyncTask; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; -import android.view.inputmethod.InputMethodInfo; import java.util.ArrayList; import java.util.Arrays; @@ -71,7 +71,7 @@ public class SubtypeSwitcher { // Variants which should be changed only by reload functions. private boolean mNeedsToDisplayLanguage; private boolean mIsSystemLanguageSameAsInputLanguage; - private InputMethodInfo mShortcutInputMethodInfo; + private InputMethodInfoCompatWrapper mShortcutInputMethodInfo; private InputMethodSubtypeCompatWrapper mShortcutSubtype; private List mAllEnabledSubtypesOfCurrentInputMethod; private InputMethodSubtypeCompatWrapper mCurrentSubtype; @@ -184,9 +184,9 @@ public class SubtypeSwitcher { + ", " + mShortcutSubtype.getMode()))); } // TODO: Update an icon for shortcut IME - Map> shortcuts = + final Map> shortcuts = mImm.getShortcutInputMethodsAndSubtypes(); - for (InputMethodInfo imi: shortcuts.keySet()) { + for (InputMethodInfoCompatWrapper imi : shortcuts.keySet()) { List subtypes = shortcuts.get(imi); // TODO: Returns the first found IMI for now. Should handle all shortcuts as // appropriate. @@ -333,7 +333,8 @@ public class SubtypeSwitcher { return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype); } - private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtypeCompatWrapper subtype) { + private Drawable getSubtypeIcon( + InputMethodInfoCompatWrapper imi, InputMethodSubtypeCompatWrapper subtype) { final PackageManager pm = mService.getPackageManager(); if (imi != null) { final String imiPackageName = imi.getPackageName(); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index f48e1184b..2c21de8c0 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.keyboard.KeyboardId; @@ -29,7 +30,6 @@ import android.text.InputType; import android.text.format.DateUtils; import android.util.Log; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodInfo; import java.io.BufferedReader; import java.io.File; @@ -109,7 +109,7 @@ public class Utils { } public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) { - for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { + for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) { if (imi.getPackageName().equals(packageName)) return imi.getId(); } -- cgit v1.2.3-83-g751a From 24119dfd414f948b2da214c021cc76e06b3dd41c Mon Sep 17 00:00:00 2001 From: satok Date: Wed, 23 Mar 2011 16:15:07 -0700 Subject: Moved a functionality of setting touchableRegion to InputMethodServiceCompatWrapper. Change-Id: I8b9fe507885ef5ae7cb09db93a977e23b1bbd0db --- .../android/inputmethod/compat/CompatUtils.java | 35 ++++++++++++++++++++++ .../compat/InputMethodServiceCompatWrapper.java | 6 ++++ .../com/android/inputmethod/latin/LatinIME.java | 3 +- 3 files changed, 42 insertions(+), 2 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java index fea15fda5..8bf59091f 100644 --- a/java/src/com/android/inputmethod/compat/CompatUtils.java +++ b/java/src/com/android/inputmethod/compat/CompatUtils.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.text.TextUtils; import android.util.Log; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -77,6 +78,18 @@ public class CompatUtils { } } + public static Field getField(Class targetClass, String name) { + try { + return targetClass.getField(name); + } catch (SecurityException e) { + // ignore + return null; + } catch (NoSuchFieldException e) { + // ignore + return null; + } + } + public static Object invoke( Object receiver, Object defaultValue, Method method, Object... args) { if (receiver == null || method == null) return defaultValue; @@ -94,6 +107,28 @@ public class CompatUtils { } } + public static Object getFieldValue(Object receiver, Object defaultValue, Field field) { + if (receiver == null || field == null) return defaultValue; + try { + return field.get(receiver); + } catch (IllegalArgumentException e) { + return defaultValue; + } catch (IllegalAccessException e) { + return defaultValue; + } + } + + public static void setFieldValue(Object receiver, Field field, Object value) { + if (receiver == null || field == null) return; + try { + field.set(receiver, value); + } catch (IllegalArgumentException e) { + // ignore + } catch (IllegalAccessException e) { + // ignore + } + } + public static List copyInputMethodSubtypeListToWrapper( Object listObject) { if (!(listObject instanceof List)) return null; diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java index 72f3ca0d0..606322af7 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java @@ -62,4 +62,10 @@ public class InputMethodServiceCompatWrapper extends InputMethodService { SubtypeSwitcher.getInstance().updateSubtype( new InputMethodSubtypeCompatWrapper(subtype)); } + + protected static void setTouchableRegionCompat(InputMethodService.Insets outInsets, + int x, int y, int width, int height) { + outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; + outInsets.touchableRegion.set(x, y, width, height); + } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index b89fcbfcf..6ae6bd60f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -914,8 +914,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (DEBUG) { Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height); } - outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; - outInsets.touchableRegion.set(x, y, width, height); + setTouchableRegionCompat(outInsets, x, y, width, height); } } -- cgit v1.2.3-83-g751a From b86905943c0f1cadb2b3df9f2a58e7af84f6b27f Mon Sep 17 00:00:00 2001 From: satok Date: Thu, 24 Mar 2011 17:42:09 -0700 Subject: Renamed Proxy classes - VoiceConnector to VoiceProxy - VoiceInputConnector to VoiceInputWrapper - VoiceLoggerConnector to VoiceLoggerWrapper Change-Id: I908ddc590c8846cf5cbd80b9c0257cb65c638bde --- .../inputmethod/deprecated/VoiceConnector.java | 832 --------------------- .../android/inputmethod/deprecated/VoiceProxy.java | 832 +++++++++++++++++++++ .../inputmethod/keyboard/LatinKeyboardView.java | 4 +- .../com/android/inputmethod/latin/LatinIME.java | 58 +- .../com/android/inputmethod/latin/Settings.java | 8 +- .../android/inputmethod/latin/SubtypeSwitcher.java | 30 +- 6 files changed, 882 insertions(+), 882 deletions(-) delete mode 100644 java/src/com/android/inputmethod/deprecated/VoiceConnector.java create mode 100644 java/src/com/android/inputmethod/deprecated/VoiceProxy.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/deprecated/VoiceConnector.java b/java/src/com/android/inputmethod/deprecated/VoiceConnector.java deleted file mode 100644 index 4ea2d1af4..000000000 --- a/java/src/com/android/inputmethod/deprecated/VoiceConnector.java +++ /dev/null @@ -1,832 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.deprecated; - -import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; -import com.android.inputmethod.deprecated.voice.FieldContext; -import com.android.inputmethod.deprecated.voice.Hints; -import com.android.inputmethod.deprecated.voice.SettingsUtil; -import com.android.inputmethod.deprecated.voice.VoiceInput; -import com.android.inputmethod.deprecated.voice.VoiceInputLogger; -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.latin.EditingUtils; -import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.LatinIME.UIHandler; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SharedPreferencesCompat; -import com.android.inputmethod.latin.SubtypeSwitcher; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.Utils; - -import android.app.AlertDialog; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.provider.Browser; -import android.speech.SpeechRecognizer; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.text.style.URLSpan; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.Window; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class VoiceConnector implements VoiceInput.UiListener { - private static final VoiceConnector sInstance = new VoiceConnector(); - - public static final boolean VOICE_INSTALLED = true; - private static final boolean ENABLE_VOICE_BUTTON = true; - private static final String PREF_VOICE_MODE = "voice_mode"; - // Whether or not the user has used voice input before (and thus, whether to show the - // first-run warning dialog or not). - private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input"; - // Whether or not the user has used voice input from an unsupported locale UI before. - // For example, the user has a Chinese UI but activates voice input. - private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = - "has_used_voice_input_unsupported_locale"; - private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6; - - private static final String TAG = VoiceConnector.class.getSimpleName(); - private static final boolean DEBUG = LatinImeLogger.sDBG; - - private boolean mAfterVoiceInput; - private boolean mHasUsedVoiceInput; - private boolean mHasUsedVoiceInputUnsupportedLocale; - private boolean mImmediatelyAfterVoiceInput; - private boolean mIsShowingHint; - private boolean mLocaleSupportedForVoiceInput; - private boolean mPasswordText; - private boolean mRecognizing; - private boolean mShowingVoiceSuggestions; - private boolean mVoiceButtonEnabled; - private boolean mVoiceButtonOnPrimary; - private boolean mVoiceInputHighlighted; - - private InputMethodManagerCompatWrapper mImm; - private LatinIME mService; - private AlertDialog mVoiceWarningDialog; - private VoiceInput mVoiceInput; - private final VoiceResults mVoiceResults = new VoiceResults(); - private Hints mHints; - private UIHandler mHandler; - private SubtypeSwitcher mSubtypeSwitcher; - // For each word, a list of potential replacements, usually from voice. - private final Map> mWordToSuggestions = - new HashMap>(); - - public static VoiceConnector init(LatinIME context, SharedPreferences prefs, UIHandler h) { - sInstance.initInternal(context, prefs, h); - return sInstance; - } - - public static VoiceConnector getInstance() { - return sInstance; - } - - private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) { - mService = service; - mHandler = h; - mImm = InputMethodManagerCompatWrapper.getInstance(service); - mSubtypeSwitcher = SubtypeSwitcher.getInstance(); - if (VOICE_INSTALLED) { - mVoiceInput = new VoiceInput(service, this); - mHints = new Hints(service, prefs, new Hints.Display() { - @Override - public void showHint(int viewResource) { - View view = LayoutInflater.from(mService).inflate(viewResource, null); - mService.setCandidatesView(view); - mService.setCandidatesViewShown(true); - mIsShowingHint = true; - } - }); - } - } - - private VoiceConnector() { - // Intentional empty constructor for singleton. - } - - public void resetVoiceStates(boolean isPasswordText) { - mAfterVoiceInput = false; - mImmediatelyAfterVoiceInput = false; - mShowingVoiceSuggestions = false; - mVoiceInputHighlighted = false; - mPasswordText = isPasswordText; - } - - public void flushVoiceInputLogs(boolean configurationChanged) { - if (VOICE_INSTALLED && !configurationChanged) { - if (mAfterVoiceInput) { - mVoiceInput.flushAllTextModificationCounters(); - mVoiceInput.logInputEnded(); - } - mVoiceInput.flushLogs(); - mVoiceInput.cancel(); - } - } - - public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion, - String wordSeparators) { - if (mAfterVoiceInput && mShowingVoiceSuggestions) { - mVoiceInput.flushAllTextModificationCounters(); - // send this intent AFTER logging any prior aggregated edits. - mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index, - wordSeparators, mService.getCurrentInputConnection()); - } - } - - private void showVoiceWarningDialog(final boolean swipe, IBinder token) { - if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { - return; - } - AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService); - builder.setCancelable(true); - builder.setIcon(R.drawable.ic_mic_dialog); - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - mVoiceInput.logKeyboardWarningDialogOk(); - reallyStartListening(swipe); - } - }); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - mVoiceInput.logKeyboardWarningDialogCancel(); - switchToLastInputMethod(); - } - }); - // When the dialog is dismissed by user's cancellation, switch back to the last input method - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface arg0) { - mVoiceInput.logKeyboardWarningDialogCancel(); - switchToLastInputMethod(); - } - }); - - final CharSequence message; - if (mLocaleSupportedForVoiceInput) { - message = TextUtils.concat( - mService.getText(R.string.voice_warning_may_not_understand), "\n\n", - mService.getText(R.string.voice_warning_how_to_turn_off)); - } else { - message = TextUtils.concat( - mService.getText(R.string.voice_warning_locale_not_supported), "\n\n", - mService.getText(R.string.voice_warning_may_not_understand), "\n\n", - mService.getText(R.string.voice_warning_how_to_turn_off)); - } - builder.setMessage(message); - builder.setTitle(R.string.voice_warning_title); - mVoiceWarningDialog = builder.create(); - final Window window = mVoiceWarningDialog.getWindow(); - final WindowManager.LayoutParams lp = window.getAttributes(); - lp.token = token; - lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; - window.setAttributes(lp); - window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - mVoiceInput.logKeyboardWarningDialogShown(); - mVoiceWarningDialog.show(); - } - - private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder { - private AlertDialog mAlertDialog; - - public UrlLinkAlertDialogBuilder(Context context) { - super(context); - } - - @Override - public AlertDialog.Builder setMessage(CharSequence message) { - return super.setMessage(replaceURLSpan(message)); - } - - private Spanned replaceURLSpan(CharSequence message) { - // Replace all spans with the custom span - final SpannableStringBuilder ssb = new SpannableStringBuilder(message); - for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) { - int spanStart = ssb.getSpanStart(span); - int spanEnd = ssb.getSpanEnd(span); - int spanFlags = ssb.getSpanFlags(span); - ssb.removeSpan(span); - ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags); - } - return ssb; - } - - @Override - public AlertDialog create() { - final AlertDialog dialog = super.create(); - - dialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialogInterface) { - // Make URL in the dialog message click-able. - TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message); - if (textView != null) { - textView.setMovementMethod(LinkMovementMethod.getInstance()); - } - } - }); - mAlertDialog = dialog; - return dialog; - } - - class ClickableSpan extends URLSpan { - public ClickableSpan(String url) { - super(url); - } - - @Override - public void onClick(View widget) { - Uri uri = Uri.parse(getURL()); - Context context = widget.getContext(); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - // Add this flag to start an activity from service - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); - // Dismiss the warning dialog and go back to the previous IME. - // TODO: If we can find a way to bring the new activity to front while keeping - // the warning dialog, we don't need to dismiss it here. - mAlertDialog.cancel(); - context.startActivity(intent); - } - } - } - - public void showPunctuationHintIfNecessary() { - InputConnection ic = mService.getCurrentInputConnection(); - if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) { - if (mHints.showPunctuationHintIfNecessary(ic)) { - mVoiceInput.logPunctuationHintDisplayed(); - } - } - mImmediatelyAfterVoiceInput = false; - } - - public void hideVoiceWindow(boolean configurationChanging) { - if (!configurationChanging) { - if (mAfterVoiceInput) - mVoiceInput.logInputEnded(); - if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { - mVoiceInput.logKeyboardWarningDialogDismissed(); - mVoiceWarningDialog.dismiss(); - mVoiceWarningDialog = null; - } - if (VOICE_INSTALLED & mRecognizing) { - mVoiceInput.cancel(); - } - } - mWordToSuggestions.clear(); - } - - public void setCursorAndSelection(int newSelEnd, int newSelStart) { - if (mAfterVoiceInput) { - mVoiceInput.setCursorPos(newSelEnd); - mVoiceInput.setSelectionSpan(newSelEnd - newSelStart); - } - } - - public void setVoiceInputHighlighted(boolean b) { - mVoiceInputHighlighted = b; - } - - public void setShowingVoiceSuggestions(boolean b) { - mShowingVoiceSuggestions = b; - } - - public boolean isVoiceButtonEnabled() { - return mVoiceButtonEnabled; - } - - public boolean isVoiceButtonOnPrimary() { - return mVoiceButtonOnPrimary; - } - - public boolean isVoiceInputHighlighted() { - return mVoiceInputHighlighted; - } - - public boolean isRecognizing() { - return mRecognizing; - } - - public boolean needsToShowWarningDialog() { - return !mHasUsedVoiceInput - || (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale); - } - - public boolean getAndResetIsShowingHint() { - boolean ret = mIsShowingHint; - mIsShowingHint = false; - return ret; - } - - private void revertVoiceInput() { - InputConnection ic = mService.getCurrentInputConnection(); - if (ic != null) ic.commitText("", 1); - mService.updateSuggestions(); - mVoiceInputHighlighted = false; - } - - public void commitVoiceInput() { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - InputConnection ic = mService.getCurrentInputConnection(); - if (ic != null) ic.finishComposingText(); - mService.updateSuggestions(); - mVoiceInputHighlighted = false; - } - } - - public boolean logAndRevertVoiceInput() { - if (VOICE_INSTALLED && mVoiceInputHighlighted) { - mVoiceInput.incrementTextModificationDeleteCount( - mVoiceResults.candidates.get(0).toString().length()); - revertVoiceInput(); - return true; - } else { - return false; - } - } - - public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) { - if (mShowingVoiceSuggestions) { - // Retain the replaced word in the alternatives array. - String wordToBeReplaced = EditingUtils.getWordAtCursor( - mService.getCurrentInputConnection(), wordSeparators); - if (!mWordToSuggestions.containsKey(wordToBeReplaced)) { - wordToBeReplaced = wordToBeReplaced.toLowerCase(); - } - if (mWordToSuggestions.containsKey(wordToBeReplaced)) { - List suggestions = mWordToSuggestions.get(wordToBeReplaced); - if (suggestions.contains(suggestion)) { - suggestions.remove(suggestion); - } - suggestions.add(wordToBeReplaced); - mWordToSuggestions.remove(wordToBeReplaced); - mWordToSuggestions.put(suggestion.toString(), suggestions); - } - } - } - - /** - * Tries to apply any voice alternatives for the word if this was a spoken word and - * there are voice alternatives. - * @param touching The word that the cursor is touching, with position information - * @return true if an alternative was found, false otherwise. - */ - public boolean applyVoiceAlternatives(EditingUtils.SelectedWord touching) { - // Search for result in spoken word alternatives - String selectedWord = touching.mWord.toString().trim(); - if (!mWordToSuggestions.containsKey(selectedWord)) { - selectedWord = selectedWord.toLowerCase(); - } - if (mWordToSuggestions.containsKey(selectedWord)) { - mShowingVoiceSuggestions = true; - List suggestions = mWordToSuggestions.get(selectedWord); - SuggestedWords.Builder builder = new SuggestedWords.Builder(); - // If the first letter of touching is capitalized, make all the suggestions - // start with a capital letter. - if (Character.isUpperCase(touching.mWord.charAt(0))) { - for (CharSequence word : suggestions) { - String str = word.toString(); - word = Character.toUpperCase(str.charAt(0)) + str.substring(1); - builder.addWord(word); - } - } else { - builder.addWords(suggestions, null); - } - builder.setTypedWordValid(true).setHasMinimalSuggestion(true); - mService.setSuggestions(builder.build()); - mService.setCandidatesViewShown(true); - return true; - } - return false; - } - - public void handleBackspace() { - if (mAfterVoiceInput) { - // Don't log delete if the user is pressing delete at - // the beginning of the text box (hence not deleting anything) - if (mVoiceInput.getCursorPos() > 0) { - // If anything was selected before the delete was pressed, increment the - // delete count by the length of the selection - int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? - mVoiceInput.getSelectionSpan() : 1; - mVoiceInput.incrementTextModificationDeleteCount(deleteLen); - } - } - } - - public void handleCharacter() { - commitVoiceInput(); - if (mAfterVoiceInput) { - // Assume input length is 1. This assumption fails for smiley face insertions. - mVoiceInput.incrementTextModificationInsertCount(1); - } - } - - public void handleSeparator() { - commitVoiceInput(); - if (mAfterVoiceInput){ - // Assume input length is 1. This assumption fails for smiley face insertions. - mVoiceInput.incrementTextModificationInsertPunctuationCount(1); - } - } - - public void handleClose() { - if (VOICE_INSTALLED & mRecognizing) { - mVoiceInput.cancel(); - } - } - - - public void handleVoiceResults(boolean capitalizeFirstWord) { - mAfterVoiceInput = true; - mImmediatelyAfterVoiceInput = true; - - InputConnection ic = mService.getCurrentInputConnection(); - if (!mService.isFullscreenMode()) { - // Start listening for updates to the text from typing, etc. - if (ic != null) { - ExtractedTextRequest req = new ExtractedTextRequest(); - ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); - } - } - mService.vibrate(); - - final List nBest = new ArrayList(); - for (String c : mVoiceResults.candidates) { - if (capitalizeFirstWord) { - c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length()); - } - nBest.add(c); - } - if (nBest.size() == 0) { - return; - } - String bestResult = nBest.get(0).toString(); - mVoiceInput.logVoiceInputDelivered(bestResult.length()); - mHints.registerVoiceResult(bestResult); - - if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text - mService.commitTyped(ic); - EditingUtils.appendText(ic, bestResult); - if (ic != null) ic.endBatchEdit(); - - mVoiceInputHighlighted = true; - mWordToSuggestions.putAll(mVoiceResults.alternatives); - onCancelVoice(); - } - - public void switchToRecognitionStatusView(final Configuration configuration) { - mHandler.post(new Runnable() { - @Override - public void run() { - mService.setCandidatesViewShown(false); - mRecognizing = true; - mVoiceInput.newView(); - View v = mVoiceInput.getView(); - - ViewParent p = v.getParent(); - if (p != null && p instanceof ViewGroup) { - ((ViewGroup) p).removeView(v); - } - - View keyboardView = KeyboardSwitcher.getInstance().getInputView(); - - // The full height of the keyboard is difficult to calculate - // as the dimension is expressed in "mm" and not in "pixel" - // As we add mm, we don't know how the rounding is going to work - // thus we may end up with few pixels extra (or less). - if (keyboardView != null) { - View popupLayout = v.findViewById(R.id.popup_layout); - final int displayHeight = - mService.getResources().getDisplayMetrics().heightPixels; - final int currentHeight = popupLayout.getLayoutParams().height; - final int keyboardHeight = keyboardView.getHeight(); - if (keyboardHeight > currentHeight || keyboardHeight - > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) { - popupLayout.getLayoutParams().height = keyboardHeight; - } - } - mService.setInputView(v); - mService.updateInputViewShown(); - - if (configuration != null) { - mVoiceInput.onConfigurationChanged(configuration); - } - }}); - } - - private void switchToLastInputMethod() { - final IBinder token = mService.getWindow().getWindow().getAttributes().token; - new AsyncTask() { - @Override - protected Boolean doInBackground(Void... params) { - return mImm.switchToLastInputMethod(token); - } - - @Override - protected void onPostExecute(Boolean result) { - // Calls in this method need to be done in the same thread as the thread which - // called switchToLastInputMethod() - if (!result) { - if (DEBUG) { - Log.d(TAG, "Couldn't switch back to last IME."); - } - // Because the current IME and subtype failed to switch to any other IME and - // subtype by switchToLastInputMethod, the current IME and subtype should keep - // being LatinIME and voice subtype in the next time. And for re-showing voice - // mode, the state of voice input should be reset and the voice view should be - // hidden. - mVoiceInput.reset(); - mService.requestHideSelf(0); - } else { - // Notify an event that the current subtype was changed. This event will be - // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented - // when the API level is 10 or previous. - mService.notifyOnCurrentInputMethodSubtypeChanged(null); - } - } - }.execute(); - } - - private void reallyStartListening(boolean swipe) { - if (!VOICE_INSTALLED) { - return; - } - if (!mHasUsedVoiceInput) { - // The user has started a voice input, so remember that in the - // future (so we don't show the warning dialog after the first run). - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences(mService).edit(); - editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true); - SharedPreferencesCompat.apply(editor); - mHasUsedVoiceInput = true; - } - - if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) { - // The user has started a voice input from an unsupported locale, so remember that - // in the future (so we don't show the warning dialog the next time they do this). - SharedPreferences.Editor editor = - PreferenceManager.getDefaultSharedPreferences(mService).edit(); - editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true); - SharedPreferencesCompat.apply(editor); - mHasUsedVoiceInputUnsupportedLocale = true; - } - - // Clear N-best suggestions - mService.clearSuggestions(); - - FieldContext context = makeFieldContext(); - mVoiceInput.startListening(context, swipe); - switchToRecognitionStatusView(null); - } - - public void startListening(final boolean swipe, IBinder token) { - // TODO: remove swipe which is no longer used. - if (VOICE_INSTALLED) { - if (needsToShowWarningDialog()) { - // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. - showVoiceWarningDialog(swipe, token); - } else { - reallyStartListening(swipe); - } - } - } - - private boolean fieldCanDoVoice(FieldContext fieldContext) { - return !mPasswordText - && mVoiceInput != null - && !mVoiceInput.isBlacklistedField(fieldContext); - } - - private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { - @SuppressWarnings("deprecation") - final boolean noMic = Utils.inPrivateImeOptions(null, - LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute) - || Utils.inPrivateImeOptions(mService.getPackageName(), - LatinIME.IME_OPTION_NO_MICROPHONE, attribute); - return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic - && SpeechRecognizer.isRecognitionAvailable(mService); - } - - public void loadSettings(EditorInfo attribute, SharedPreferences sp) { - mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false); - mHasUsedVoiceInputUnsupportedLocale = - sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false); - - mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported( - SubtypeSwitcher.getInstance().getInputLocaleStr()); - - if (VOICE_INSTALLED) { - final String voiceMode = sp.getString(PREF_VOICE_MODE, - mService.getString(R.string.voice_mode_main)); - mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off)) - && shouldShowVoiceButton(makeFieldContext(), attribute); - mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main)); - } - } - - public void destroy() { - if (VOICE_INSTALLED && mVoiceInput != null) { - mVoiceInput.destroy(); - } - } - - public void onStartInputView(IBinder keyboardViewToken) { - // If keyboardViewToken is null, keyboardView is not attached but voiceView is attached. - IBinder windowToken = keyboardViewToken != null ? keyboardViewToken - : mVoiceInput.getView().getWindowToken(); - // If IME is in voice mode, but still needs to show the voice warning dialog, - // keep showing the warning. - if (mSubtypeSwitcher.isVoiceMode() && windowToken != null) { - // Close keyboard view if it is been shown. - if (KeyboardSwitcher.getInstance().isInputViewShown()) - KeyboardSwitcher.getInstance().getInputView().purgeKeyboardAndClosing(); - startListening(false, windowToken); - } - // If we have no token, onAttachedToWindow will take care of showing dialog and start - // listening. - } - - public void onAttachedToWindow() { - // After onAttachedToWindow, we can show the voice warning dialog. See startListening() - // above. - VoiceInputConnector.getInstance().setVoiceInput(mVoiceInput, mSubtypeSwitcher); - } - - public void onConfigurationChanged(Configuration configuration) { - if (mRecognizing) { - switchToRecognitionStatusView(configuration); - } - } - - @Override - public void onCancelVoice() { - if (mRecognizing) { - if (mSubtypeSwitcher.isVoiceMode()) { - // If voice mode is being canceled within LatinIME (i.e. time-out or user - // cancellation etc.), onCancelVoice() will be called first. LatinIME thinks it's - // still in voice mode. LatinIME needs to call switchToLastInputMethod(). - // Note that onCancelVoice() will be called again from SubtypeSwitcher. - switchToLastInputMethod(); - } else if (mSubtypeSwitcher.isKeyboardMode()) { - // If voice mode is being canceled out of LatinIME (i.e. by user's IME switching or - // as a result of switchToLastInputMethod() etc.), - // onCurrentInputMethodSubtypeChanged() will be called first. LatinIME will know - // that it's in keyboard mode and SubtypeSwitcher will call onCancelVoice(). - mRecognizing = false; - mService.switchToKeyboardView(); - } - } - } - - @Override - public void onVoiceResults(List candidates, - Map> alternatives) { - if (!mRecognizing) { - return; - } - mVoiceResults.candidates = candidates; - mVoiceResults.alternatives = alternatives; - mHandler.updateVoiceResults(); - } - - private FieldContext makeFieldContext() { - SubtypeSwitcher switcher = SubtypeSwitcher.getInstance(); - return new FieldContext(mService.getCurrentInputConnection(), - mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(), - switcher.getEnabledLanguages()); - } - - private class VoiceResults { - List candidates; - Map> alternatives; - } - - public static class VoiceLoggerConnector { - private static final VoiceLoggerConnector sInstance = new VoiceLoggerConnector(); - private VoiceInputLogger mLogger; - - public static VoiceLoggerConnector getInstance(Context context) { - if (sInstance.mLogger == null) { - // Not thread safe, but it's ok. - sInstance.mLogger = VoiceInputLogger.getLogger(context); - } - return sInstance; - } - - // private for the singleton - private VoiceLoggerConnector() { - } - - public void settingsWarningDialogCancel() { - mLogger.settingsWarningDialogCancel(); - } - - public void settingsWarningDialogOk() { - mLogger.settingsWarningDialogOk(); - } - - public void settingsWarningDialogShown() { - mLogger.settingsWarningDialogShown(); - } - - public void settingsWarningDialogDismissed() { - mLogger.settingsWarningDialogDismissed(); - } - - public void voiceInputSettingEnabled(boolean enabled) { - if (enabled) { - mLogger.voiceInputSettingEnabled(); - } else { - mLogger.voiceInputSettingDisabled(); - } - } - } - - public static class VoiceInputConnector { - private static final VoiceInputConnector sInstance = new VoiceInputConnector(); - private VoiceInput mVoiceInput; - public static VoiceInputConnector getInstance() { - return sInstance; - } - public void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) { - if (mVoiceInput == null && voiceInput != null) { - mVoiceInput = voiceInput; - } - switcher.setVoiceInputConnector(this); - } - - private VoiceInputConnector() { - } - - public void cancel() { - if (mVoiceInput != null) mVoiceInput.cancel(); - } - - public void reset() { - if (mVoiceInput != null) mVoiceInput.reset(); - } - } - - // A list of locales which are supported by default for voice input, unless we get a - // different list from Gservices. - private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = - "en " + - "en_US " + - "en_GB " + - "en_AU " + - "en_CA " + - "en_IE " + - "en_IN " + - "en_NZ " + - "en_SG " + - "en_ZA "; - - public static String getSupportedLocalesString (ContentResolver resolver) { - return SettingsUtil.getSettingsString( - resolver, - SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, - DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); - } -} diff --git a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java new file mode 100644 index 000000000..5fba29d90 --- /dev/null +++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java @@ -0,0 +1,832 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * 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.deprecated; + +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.deprecated.voice.FieldContext; +import com.android.inputmethod.deprecated.voice.Hints; +import com.android.inputmethod.deprecated.voice.SettingsUtil; +import com.android.inputmethod.deprecated.voice.VoiceInput; +import com.android.inputmethod.deprecated.voice.VoiceInputLogger; +import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.latin.EditingUtils; +import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.LatinIME.UIHandler; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.SharedPreferencesCompat; +import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.Utils; + +import android.app.AlertDialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.provider.Browser; +import android.speech.SpeechRecognizer; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.URLSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VoiceProxy implements VoiceInput.UiListener { + private static final VoiceProxy sInstance = new VoiceProxy(); + + public static final boolean VOICE_INSTALLED = true; + private static final boolean ENABLE_VOICE_BUTTON = true; + private static final String PREF_VOICE_MODE = "voice_mode"; + // Whether or not the user has used voice input before (and thus, whether to show the + // first-run warning dialog or not). + private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input"; + // Whether or not the user has used voice input from an unsupported locale UI before. + // For example, the user has a Chinese UI but activates voice input. + private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = + "has_used_voice_input_unsupported_locale"; + private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6; + + private static final String TAG = VoiceProxy.class.getSimpleName(); + private static final boolean DEBUG = LatinImeLogger.sDBG; + + private boolean mAfterVoiceInput; + private boolean mHasUsedVoiceInput; + private boolean mHasUsedVoiceInputUnsupportedLocale; + private boolean mImmediatelyAfterVoiceInput; + private boolean mIsShowingHint; + private boolean mLocaleSupportedForVoiceInput; + private boolean mPasswordText; + private boolean mRecognizing; + private boolean mShowingVoiceSuggestions; + private boolean mVoiceButtonEnabled; + private boolean mVoiceButtonOnPrimary; + private boolean mVoiceInputHighlighted; + + private InputMethodManagerCompatWrapper mImm; + private LatinIME mService; + private AlertDialog mVoiceWarningDialog; + private VoiceInput mVoiceInput; + private final VoiceResults mVoiceResults = new VoiceResults(); + private Hints mHints; + private UIHandler mHandler; + private SubtypeSwitcher mSubtypeSwitcher; + // For each word, a list of potential replacements, usually from voice. + private final Map> mWordToSuggestions = + new HashMap>(); + + public static VoiceProxy init(LatinIME context, SharedPreferences prefs, UIHandler h) { + sInstance.initInternal(context, prefs, h); + return sInstance; + } + + public static VoiceProxy getInstance() { + return sInstance; + } + + private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) { + mService = service; + mHandler = h; + mImm = InputMethodManagerCompatWrapper.getInstance(service); + mSubtypeSwitcher = SubtypeSwitcher.getInstance(); + if (VOICE_INSTALLED) { + mVoiceInput = new VoiceInput(service, this); + mHints = new Hints(service, prefs, new Hints.Display() { + @Override + public void showHint(int viewResource) { + View view = LayoutInflater.from(mService).inflate(viewResource, null); + mService.setCandidatesView(view); + mService.setCandidatesViewShown(true); + mIsShowingHint = true; + } + }); + } + } + + private VoiceProxy() { + // Intentional empty constructor for singleton. + } + + public void resetVoiceStates(boolean isPasswordText) { + mAfterVoiceInput = false; + mImmediatelyAfterVoiceInput = false; + mShowingVoiceSuggestions = false; + mVoiceInputHighlighted = false; + mPasswordText = isPasswordText; + } + + public void flushVoiceInputLogs(boolean configurationChanged) { + if (VOICE_INSTALLED && !configurationChanged) { + if (mAfterVoiceInput) { + mVoiceInput.flushAllTextModificationCounters(); + mVoiceInput.logInputEnded(); + } + mVoiceInput.flushLogs(); + mVoiceInput.cancel(); + } + } + + public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion, + String wordSeparators) { + if (mAfterVoiceInput && mShowingVoiceSuggestions) { + mVoiceInput.flushAllTextModificationCounters(); + // send this intent AFTER logging any prior aggregated edits. + mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index, + wordSeparators, mService.getCurrentInputConnection()); + } + } + + private void showVoiceWarningDialog(final boolean swipe, IBinder token) { + if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { + return; + } + AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService); + builder.setCancelable(true); + builder.setIcon(R.drawable.ic_mic_dialog); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + mVoiceInput.logKeyboardWarningDialogOk(); + reallyStartListening(swipe); + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + mVoiceInput.logKeyboardWarningDialogCancel(); + switchToLastInputMethod(); + } + }); + // When the dialog is dismissed by user's cancellation, switch back to the last input method + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface arg0) { + mVoiceInput.logKeyboardWarningDialogCancel(); + switchToLastInputMethod(); + } + }); + + final CharSequence message; + if (mLocaleSupportedForVoiceInput) { + message = TextUtils.concat( + mService.getText(R.string.voice_warning_may_not_understand), "\n\n", + mService.getText(R.string.voice_warning_how_to_turn_off)); + } else { + message = TextUtils.concat( + mService.getText(R.string.voice_warning_locale_not_supported), "\n\n", + mService.getText(R.string.voice_warning_may_not_understand), "\n\n", + mService.getText(R.string.voice_warning_how_to_turn_off)); + } + builder.setMessage(message); + builder.setTitle(R.string.voice_warning_title); + mVoiceWarningDialog = builder.create(); + final Window window = mVoiceWarningDialog.getWindow(); + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.token = token; + lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + window.setAttributes(lp); + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + mVoiceInput.logKeyboardWarningDialogShown(); + mVoiceWarningDialog.show(); + } + + private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder { + private AlertDialog mAlertDialog; + + public UrlLinkAlertDialogBuilder(Context context) { + super(context); + } + + @Override + public AlertDialog.Builder setMessage(CharSequence message) { + return super.setMessage(replaceURLSpan(message)); + } + + private Spanned replaceURLSpan(CharSequence message) { + // Replace all spans with the custom span + final SpannableStringBuilder ssb = new SpannableStringBuilder(message); + for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) { + int spanStart = ssb.getSpanStart(span); + int spanEnd = ssb.getSpanEnd(span); + int spanFlags = ssb.getSpanFlags(span); + ssb.removeSpan(span); + ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags); + } + return ssb; + } + + @Override + public AlertDialog create() { + final AlertDialog dialog = super.create(); + + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogInterface) { + // Make URL in the dialog message click-able. + TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message); + if (textView != null) { + textView.setMovementMethod(LinkMovementMethod.getInstance()); + } + } + }); + mAlertDialog = dialog; + return dialog; + } + + class ClickableSpan extends URLSpan { + public ClickableSpan(String url) { + super(url); + } + + @Override + public void onClick(View widget) { + Uri uri = Uri.parse(getURL()); + Context context = widget.getContext(); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + // Add this flag to start an activity from service + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + // Dismiss the warning dialog and go back to the previous IME. + // TODO: If we can find a way to bring the new activity to front while keeping + // the warning dialog, we don't need to dismiss it here. + mAlertDialog.cancel(); + context.startActivity(intent); + } + } + } + + public void showPunctuationHintIfNecessary() { + InputConnection ic = mService.getCurrentInputConnection(); + if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) { + if (mHints.showPunctuationHintIfNecessary(ic)) { + mVoiceInput.logPunctuationHintDisplayed(); + } + } + mImmediatelyAfterVoiceInput = false; + } + + public void hideVoiceWindow(boolean configurationChanging) { + if (!configurationChanging) { + if (mAfterVoiceInput) + mVoiceInput.logInputEnded(); + if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { + mVoiceInput.logKeyboardWarningDialogDismissed(); + mVoiceWarningDialog.dismiss(); + mVoiceWarningDialog = null; + } + if (VOICE_INSTALLED & mRecognizing) { + mVoiceInput.cancel(); + } + } + mWordToSuggestions.clear(); + } + + public void setCursorAndSelection(int newSelEnd, int newSelStart) { + if (mAfterVoiceInput) { + mVoiceInput.setCursorPos(newSelEnd); + mVoiceInput.setSelectionSpan(newSelEnd - newSelStart); + } + } + + public void setVoiceInputHighlighted(boolean b) { + mVoiceInputHighlighted = b; + } + + public void setShowingVoiceSuggestions(boolean b) { + mShowingVoiceSuggestions = b; + } + + public boolean isVoiceButtonEnabled() { + return mVoiceButtonEnabled; + } + + public boolean isVoiceButtonOnPrimary() { + return mVoiceButtonOnPrimary; + } + + public boolean isVoiceInputHighlighted() { + return mVoiceInputHighlighted; + } + + public boolean isRecognizing() { + return mRecognizing; + } + + public boolean needsToShowWarningDialog() { + return !mHasUsedVoiceInput + || (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale); + } + + public boolean getAndResetIsShowingHint() { + boolean ret = mIsShowingHint; + mIsShowingHint = false; + return ret; + } + + private void revertVoiceInput() { + InputConnection ic = mService.getCurrentInputConnection(); + if (ic != null) ic.commitText("", 1); + mService.updateSuggestions(); + mVoiceInputHighlighted = false; + } + + public void commitVoiceInput() { + if (VOICE_INSTALLED && mVoiceInputHighlighted) { + InputConnection ic = mService.getCurrentInputConnection(); + if (ic != null) ic.finishComposingText(); + mService.updateSuggestions(); + mVoiceInputHighlighted = false; + } + } + + public boolean logAndRevertVoiceInput() { + if (VOICE_INSTALLED && mVoiceInputHighlighted) { + mVoiceInput.incrementTextModificationDeleteCount( + mVoiceResults.candidates.get(0).toString().length()); + revertVoiceInput(); + return true; + } else { + return false; + } + } + + public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) { + if (mShowingVoiceSuggestions) { + // Retain the replaced word in the alternatives array. + String wordToBeReplaced = EditingUtils.getWordAtCursor( + mService.getCurrentInputConnection(), wordSeparators); + if (!mWordToSuggestions.containsKey(wordToBeReplaced)) { + wordToBeReplaced = wordToBeReplaced.toLowerCase(); + } + if (mWordToSuggestions.containsKey(wordToBeReplaced)) { + List suggestions = mWordToSuggestions.get(wordToBeReplaced); + if (suggestions.contains(suggestion)) { + suggestions.remove(suggestion); + } + suggestions.add(wordToBeReplaced); + mWordToSuggestions.remove(wordToBeReplaced); + mWordToSuggestions.put(suggestion.toString(), suggestions); + } + } + } + + /** + * Tries to apply any voice alternatives for the word if this was a spoken word and + * there are voice alternatives. + * @param touching The word that the cursor is touching, with position information + * @return true if an alternative was found, false otherwise. + */ + public boolean applyVoiceAlternatives(EditingUtils.SelectedWord touching) { + // Search for result in spoken word alternatives + String selectedWord = touching.mWord.toString().trim(); + if (!mWordToSuggestions.containsKey(selectedWord)) { + selectedWord = selectedWord.toLowerCase(); + } + if (mWordToSuggestions.containsKey(selectedWord)) { + mShowingVoiceSuggestions = true; + List suggestions = mWordToSuggestions.get(selectedWord); + SuggestedWords.Builder builder = new SuggestedWords.Builder(); + // If the first letter of touching is capitalized, make all the suggestions + // start with a capital letter. + if (Character.isUpperCase(touching.mWord.charAt(0))) { + for (CharSequence word : suggestions) { + String str = word.toString(); + word = Character.toUpperCase(str.charAt(0)) + str.substring(1); + builder.addWord(word); + } + } else { + builder.addWords(suggestions, null); + } + builder.setTypedWordValid(true).setHasMinimalSuggestion(true); + mService.setSuggestions(builder.build()); + mService.setCandidatesViewShown(true); + return true; + } + return false; + } + + public void handleBackspace() { + if (mAfterVoiceInput) { + // Don't log delete if the user is pressing delete at + // the beginning of the text box (hence not deleting anything) + if (mVoiceInput.getCursorPos() > 0) { + // If anything was selected before the delete was pressed, increment the + // delete count by the length of the selection + int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? + mVoiceInput.getSelectionSpan() : 1; + mVoiceInput.incrementTextModificationDeleteCount(deleteLen); + } + } + } + + public void handleCharacter() { + commitVoiceInput(); + if (mAfterVoiceInput) { + // Assume input length is 1. This assumption fails for smiley face insertions. + mVoiceInput.incrementTextModificationInsertCount(1); + } + } + + public void handleSeparator() { + commitVoiceInput(); + if (mAfterVoiceInput){ + // Assume input length is 1. This assumption fails for smiley face insertions. + mVoiceInput.incrementTextModificationInsertPunctuationCount(1); + } + } + + public void handleClose() { + if (VOICE_INSTALLED & mRecognizing) { + mVoiceInput.cancel(); + } + } + + + public void handleVoiceResults(boolean capitalizeFirstWord) { + mAfterVoiceInput = true; + mImmediatelyAfterVoiceInput = true; + + InputConnection ic = mService.getCurrentInputConnection(); + if (!mService.isFullscreenMode()) { + // Start listening for updates to the text from typing, etc. + if (ic != null) { + ExtractedTextRequest req = new ExtractedTextRequest(); + ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR); + } + } + mService.vibrate(); + + final List nBest = new ArrayList(); + for (String c : mVoiceResults.candidates) { + if (capitalizeFirstWord) { + c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length()); + } + nBest.add(c); + } + if (nBest.size() == 0) { + return; + } + String bestResult = nBest.get(0).toString(); + mVoiceInput.logVoiceInputDelivered(bestResult.length()); + mHints.registerVoiceResult(bestResult); + + if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text + mService.commitTyped(ic); + EditingUtils.appendText(ic, bestResult); + if (ic != null) ic.endBatchEdit(); + + mVoiceInputHighlighted = true; + mWordToSuggestions.putAll(mVoiceResults.alternatives); + onCancelVoice(); + } + + public void switchToRecognitionStatusView(final Configuration configuration) { + mHandler.post(new Runnable() { + @Override + public void run() { + mService.setCandidatesViewShown(false); + mRecognizing = true; + mVoiceInput.newView(); + View v = mVoiceInput.getView(); + + ViewParent p = v.getParent(); + if (p != null && p instanceof ViewGroup) { + ((ViewGroup) p).removeView(v); + } + + View keyboardView = KeyboardSwitcher.getInstance().getInputView(); + + // The full height of the keyboard is difficult to calculate + // as the dimension is expressed in "mm" and not in "pixel" + // As we add mm, we don't know how the rounding is going to work + // thus we may end up with few pixels extra (or less). + if (keyboardView != null) { + View popupLayout = v.findViewById(R.id.popup_layout); + final int displayHeight = + mService.getResources().getDisplayMetrics().heightPixels; + final int currentHeight = popupLayout.getLayoutParams().height; + final int keyboardHeight = keyboardView.getHeight(); + if (keyboardHeight > currentHeight || keyboardHeight + > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) { + popupLayout.getLayoutParams().height = keyboardHeight; + } + } + mService.setInputView(v); + mService.updateInputViewShown(); + + if (configuration != null) { + mVoiceInput.onConfigurationChanged(configuration); + } + }}); + } + + private void switchToLastInputMethod() { + final IBinder token = mService.getWindow().getWindow().getAttributes().token; + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + return mImm.switchToLastInputMethod(token); + } + + @Override + protected void onPostExecute(Boolean result) { + // Calls in this method need to be done in the same thread as the thread which + // called switchToLastInputMethod() + if (!result) { + if (DEBUG) { + Log.d(TAG, "Couldn't switch back to last IME."); + } + // Because the current IME and subtype failed to switch to any other IME and + // subtype by switchToLastInputMethod, the current IME and subtype should keep + // being LatinIME and voice subtype in the next time. And for re-showing voice + // mode, the state of voice input should be reset and the voice view should be + // hidden. + mVoiceInput.reset(); + mService.requestHideSelf(0); + } else { + // Notify an event that the current subtype was changed. This event will be + // handled if "onCurrentInputMethodSubtypeChanged" can't be implemented + // when the API level is 10 or previous. + mService.notifyOnCurrentInputMethodSubtypeChanged(null); + } + } + }.execute(); + } + + private void reallyStartListening(boolean swipe) { + if (!VOICE_INSTALLED) { + return; + } + if (!mHasUsedVoiceInput) { + // The user has started a voice input, so remember that in the + // future (so we don't show the warning dialog after the first run). + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(mService).edit(); + editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true); + SharedPreferencesCompat.apply(editor); + mHasUsedVoiceInput = true; + } + + if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) { + // The user has started a voice input from an unsupported locale, so remember that + // in the future (so we don't show the warning dialog the next time they do this). + SharedPreferences.Editor editor = + PreferenceManager.getDefaultSharedPreferences(mService).edit(); + editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true); + SharedPreferencesCompat.apply(editor); + mHasUsedVoiceInputUnsupportedLocale = true; + } + + // Clear N-best suggestions + mService.clearSuggestions(); + + FieldContext context = makeFieldContext(); + mVoiceInput.startListening(context, swipe); + switchToRecognitionStatusView(null); + } + + public void startListening(final boolean swipe, IBinder token) { + // TODO: remove swipe which is no longer used. + if (VOICE_INSTALLED) { + if (needsToShowWarningDialog()) { + // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. + showVoiceWarningDialog(swipe, token); + } else { + reallyStartListening(swipe); + } + } + } + + private boolean fieldCanDoVoice(FieldContext fieldContext) { + return !mPasswordText + && mVoiceInput != null + && !mVoiceInput.isBlacklistedField(fieldContext); + } + + private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { + @SuppressWarnings("deprecation") + final boolean noMic = Utils.inPrivateImeOptions(null, + LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute) + || Utils.inPrivateImeOptions(mService.getPackageName(), + LatinIME.IME_OPTION_NO_MICROPHONE, attribute); + return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic + && SpeechRecognizer.isRecognitionAvailable(mService); + } + + public void loadSettings(EditorInfo attribute, SharedPreferences sp) { + mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false); + mHasUsedVoiceInputUnsupportedLocale = + sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false); + + mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported( + SubtypeSwitcher.getInstance().getInputLocaleStr()); + + if (VOICE_INSTALLED) { + final String voiceMode = sp.getString(PREF_VOICE_MODE, + mService.getString(R.string.voice_mode_main)); + mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off)) + && shouldShowVoiceButton(makeFieldContext(), attribute); + mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main)); + } + } + + public void destroy() { + if (VOICE_INSTALLED && mVoiceInput != null) { + mVoiceInput.destroy(); + } + } + + public void onStartInputView(IBinder keyboardViewToken) { + // If keyboardViewToken is null, keyboardView is not attached but voiceView is attached. + IBinder windowToken = keyboardViewToken != null ? keyboardViewToken + : mVoiceInput.getView().getWindowToken(); + // If IME is in voice mode, but still needs to show the voice warning dialog, + // keep showing the warning. + if (mSubtypeSwitcher.isVoiceMode() && windowToken != null) { + // Close keyboard view if it is been shown. + if (KeyboardSwitcher.getInstance().isInputViewShown()) + KeyboardSwitcher.getInstance().getInputView().purgeKeyboardAndClosing(); + startListening(false, windowToken); + } + // If we have no token, onAttachedToWindow will take care of showing dialog and start + // listening. + } + + public void onAttachedToWindow() { + // After onAttachedToWindow, we can show the voice warning dialog. See startListening() + // above. + VoiceInputWrapper.getInstance().setVoiceInput(mVoiceInput, mSubtypeSwitcher); + } + + public void onConfigurationChanged(Configuration configuration) { + if (mRecognizing) { + switchToRecognitionStatusView(configuration); + } + } + + @Override + public void onCancelVoice() { + if (mRecognizing) { + if (mSubtypeSwitcher.isVoiceMode()) { + // If voice mode is being canceled within LatinIME (i.e. time-out or user + // cancellation etc.), onCancelVoice() will be called first. LatinIME thinks it's + // still in voice mode. LatinIME needs to call switchToLastInputMethod(). + // Note that onCancelVoice() will be called again from SubtypeSwitcher. + switchToLastInputMethod(); + } else if (mSubtypeSwitcher.isKeyboardMode()) { + // If voice mode is being canceled out of LatinIME (i.e. by user's IME switching or + // as a result of switchToLastInputMethod() etc.), + // onCurrentInputMethodSubtypeChanged() will be called first. LatinIME will know + // that it's in keyboard mode and SubtypeSwitcher will call onCancelVoice(). + mRecognizing = false; + mService.switchToKeyboardView(); + } + } + } + + @Override + public void onVoiceResults(List candidates, + Map> alternatives) { + if (!mRecognizing) { + return; + } + mVoiceResults.candidates = candidates; + mVoiceResults.alternatives = alternatives; + mHandler.updateVoiceResults(); + } + + private FieldContext makeFieldContext() { + SubtypeSwitcher switcher = SubtypeSwitcher.getInstance(); + return new FieldContext(mService.getCurrentInputConnection(), + mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(), + switcher.getEnabledLanguages()); + } + + private class VoiceResults { + List candidates; + Map> alternatives; + } + + public static class VoiceLoggerWrapper { + private static final VoiceLoggerWrapper sInstance = new VoiceLoggerWrapper(); + private VoiceInputLogger mLogger; + + public static VoiceLoggerWrapper getInstance(Context context) { + if (sInstance.mLogger == null) { + // Not thread safe, but it's ok. + sInstance.mLogger = VoiceInputLogger.getLogger(context); + } + return sInstance; + } + + // private for the singleton + private VoiceLoggerWrapper() { + } + + public void settingsWarningDialogCancel() { + mLogger.settingsWarningDialogCancel(); + } + + public void settingsWarningDialogOk() { + mLogger.settingsWarningDialogOk(); + } + + public void settingsWarningDialogShown() { + mLogger.settingsWarningDialogShown(); + } + + public void settingsWarningDialogDismissed() { + mLogger.settingsWarningDialogDismissed(); + } + + public void voiceInputSettingEnabled(boolean enabled) { + if (enabled) { + mLogger.voiceInputSettingEnabled(); + } else { + mLogger.voiceInputSettingDisabled(); + } + } + } + + public static class VoiceInputWrapper { + private static final VoiceInputWrapper sInstance = new VoiceInputWrapper(); + private VoiceInput mVoiceInput; + public static VoiceInputWrapper getInstance() { + return sInstance; + } + public void setVoiceInput(VoiceInput voiceInput, SubtypeSwitcher switcher) { + if (mVoiceInput == null && voiceInput != null) { + mVoiceInput = voiceInput; + } + switcher.setVoiceInputWrapper(this); + } + + private VoiceInputWrapper() { + } + + public void cancel() { + if (mVoiceInput != null) mVoiceInput.cancel(); + } + + public void reset() { + if (mVoiceInput != null) mVoiceInput.reset(); + } + } + + // A list of locales which are supported by default for voice input, unless we get a + // different list from Gservices. + private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = + "en " + + "en_US " + + "en_GB " + + "en_AU " + + "en_CA " + + "en_IE " + + "en_IN " + + "en_NZ " + + "en_SG " + + "en_ZA "; + + public static String getSupportedLocalesString (ContentResolver resolver) { + return SettingsUtil.getSettingsString( + resolver, + SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, + DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index bba3e0dc3..d6c3723fd 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -16,7 +16,7 @@ package com.android.inputmethod.keyboard; -import com.android.inputmethod.deprecated.VoiceConnector; +import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.Utils; @@ -264,6 +264,6 @@ public class LatinKeyboardView extends KeyboardView { @Override protected void onAttachedToWindow() { // Token is available from here. - VoiceConnector.getInstance().onAttachedToWindow(); + VoiceProxy.getInstance().onAttachedToWindow(); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index b89fcbfcf..79ef03a8b 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -19,7 +19,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.CompatUtils; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; -import com.android.inputmethod.deprecated.VoiceConnector; +import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardSwitcher; @@ -154,7 +154,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private String mInputMethodId; private KeyboardSwitcher mKeyboardSwitcher; private SubtypeSwitcher mSubtypeSwitcher; - private VoiceConnector mVoiceConnector; + private VoiceProxy mVoiceProxy; private UserDictionary mUserDictionary; private UserBigramDictionary mUserBigramDictionary; @@ -212,7 +212,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar /* package */ String mWordSeparators; private String mSentenceSeparators; private String mSuggestPuncs; - // TODO: Move this flag to VoiceIMEConnector + // TODO: Move this flag to VoiceProxy private boolean mConfigurationChanging; // Object for reacting to adding/removing a dictionary pack. @@ -282,7 +282,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar switcher.updateShiftState(); break; case MSG_VOICE_RESULTS: - mVoiceConnector.handleVoiceResults(preferCapitalization() + mVoiceProxy.handleVoiceResults(preferCapitalization() || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked())); break; case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR: @@ -432,7 +432,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mReceiver, filter); - mVoiceConnector = VoiceConnector.init(this, prefs, mHandler); + mVoiceProxy = VoiceProxy.init(this, prefs, mHandler); final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); @@ -492,7 +492,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } unregisterReceiver(mReceiver); unregisterReceiver(mDictionaryPackInstallReceiver); - mVoiceConnector.destroy(); + mVoiceProxy.destroy(); LatinImeLogger.commit(); LatinImeLogger.onDestroy(); super.onDestroy(); @@ -513,7 +513,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mConfigurationChanging = true; super.onConfigurationChanged(conf); - mVoiceConnector.onConfigurationChanged(conf); + mVoiceProxy.onConfigurationChanged(conf); mConfigurationChanging = false; } @@ -559,7 +559,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to // know now whether this is a password text field, because we need to know now whether we // want to enable the voice button. - final VoiceConnector voiceIme = mVoiceConnector; + final VoiceProxy voiceIme = mVoiceProxy; voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) || Utils.isVisiblePasswordInputType(attribute.inputType)); @@ -691,7 +691,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar LatinImeLogger.commit(); mKeyboardSwitcher.onAutoCorrectionStateChanged(false); - mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging); + mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging); KeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) inputView.closing(); @@ -712,7 +712,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void onUpdateExtractedText(int token, ExtractedText text) { super.onUpdateExtractedText(token, text); - mVoiceConnector.showPunctuationHintIfNecessary(); + mVoiceProxy.showPunctuationHintIfNecessary(); } @Override @@ -733,7 +733,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar + ", ce=" + candidatesEnd); } - mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart); + mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart); // If the current selection in the text view changes, we should // clear whatever candidate text we have. @@ -741,7 +741,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart; final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1; if (((mComposing.length() > 0 && mHasValidSuggestions) - || mVoiceConnector.isVoiceInputHighlighted()) + || mVoiceProxy.isVoiceInputHighlighted()) && (selectionChanged || candidatesCleared)) { if (candidatesCleared) { // If the composing span has been cleared, save the typed word in the history for @@ -757,7 +757,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (ic != null) { ic.finishComposingText(); } - mVoiceConnector.setVoiceInputHighlighted(false); + mVoiceProxy.setVoiceInputHighlighted(false); } else if (!mHasValidSuggestions && !mJustAccepted) { if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) { if (TextEntryState.isAcceptedDefault()) @@ -837,7 +837,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mOptionsDialog.dismiss(); mOptionsDialog = null; } - mVoiceConnector.hideVoiceWindow(mConfigurationChanging); + mVoiceProxy.hideVoiceWindow(mConfigurationChanging); mWordHistory.clear(); super.hideWindow(); } @@ -1188,7 +1188,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void onTextInput(CharSequence text) { - mVoiceConnector.commitVoiceInput(); + mVoiceProxy.commitVoiceInput(); InputConnection ic = getCurrentInputConnection(); if (ic == null) return; abortRecorrection(false); @@ -1210,13 +1210,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } private void handleBackspace() { - if (mVoiceConnector.logAndRevertVoiceInput()) return; + if (mVoiceProxy.logAndRevertVoiceInput()) return; final InputConnection ic = getCurrentInputConnection(); if (ic == null) return; ic.beginBatchEdit(); - mVoiceConnector.handleBackspace(); + mVoiceProxy.handleBackspace(); boolean deleteChar = false; if (mHasValidSuggestions) { @@ -1300,7 +1300,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) { - mVoiceConnector.handleCharacter(); + mVoiceProxy.handleCharacter(); if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) { abortRecorrection(false); @@ -1360,7 +1360,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } private void handleSeparator(int primaryCode) { - mVoiceConnector.handleSeparator(); + mVoiceProxy.handleSeparator(); // Should dismiss the "Touch again to save" message when handling separator if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { @@ -1430,7 +1430,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private void handleClose() { commitTyped(getCurrentInputConnection()); - mVoiceConnector.handleClose(); + mVoiceProxy.handleClose(); requestHideSelf(0); LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) @@ -1503,7 +1503,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } public void setSuggestions(SuggestedWords words) { - if (mVoiceConnector.getAndResetIsShowingHint()) { + if (mVoiceProxy.getAndResetIsShowingHint()) { setCandidatesView(mCandidateViewContainer); } @@ -1519,7 +1519,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar public void updateSuggestions() { // Check if we have a suggestion engine attached. if ((mSuggest == null || !isSuggestionsRequested()) - && !mVoiceConnector.isVoiceInputHighlighted()) { + && !mVoiceProxy.isVoiceInputHighlighted()) { return; } @@ -1614,7 +1614,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar public void pickSuggestionManually(int index, CharSequence suggestion) { SuggestedWords suggestions = mCandidateView.getSuggestions(); - mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators); + mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators); final boolean recorrecting = TextEntryState.isRecorrecting(); InputConnection ic = getCurrentInputConnection(); @@ -1718,7 +1718,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar return; InputConnection ic = getCurrentInputConnection(); if (ic != null) { - mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators); + mVoiceProxy.rememberReplacedWord(suggestion, mWordSeparators); ic.commitText(suggestion, 1); } saveWordInHistory(suggestion); @@ -1773,7 +1773,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } private void setOldSuggestions() { - mVoiceConnector.setShowingVoiceSuggestions(false); + mVoiceProxy.setShowingVoiceSuggestions(false); if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) { return; } @@ -1787,7 +1787,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (touching != null && touching.mWord.length() > 1) { ic.beginBatchEdit(); - if (!mVoiceConnector.applyVoiceAlternatives(touching) + if (!mVoiceProxy.applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) { abortRecorrection(true); } else { @@ -1945,8 +1945,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } // Reload keyboard because the current language has been changed. mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), - mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceConnector.isVoiceButtonEnabled(), - mVoiceConnector.isVoiceButtonOnPrimary()); + mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(), + mVoiceProxy.isVoiceButtonOnPrimary()); initSuggest(); mKeyboardSwitcher.updateShiftState(); } @@ -2132,7 +2132,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs); loadAndSetAutoCorrectionThreshold(prefs); - mVoiceConnector.loadSettings(attribute, prefs); + mVoiceProxy.loadSettings(attribute, prefs); updateCorrectionMode(); updateAutoTextEnabled(); diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 7bb1745fb..aedb0ff75 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -18,7 +18,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.CompatUtils; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; -import com.android.inputmethod.deprecated.VoiceConnector; +import com.android.inputmethod.deprecated.VoiceProxy; import android.app.AlertDialog; import android.app.Dialog; @@ -82,7 +82,7 @@ public class Settings extends PreferenceActivity private AlertDialog mDialog; - private VoiceConnector.VoiceLoggerConnector mVoiceLogger; + private VoiceProxy.VoiceLoggerWrapper mVoiceLogger; private boolean mOkClicked = false; private String mVoiceModeOff; @@ -111,7 +111,7 @@ public class Settings extends PreferenceActivity mVoiceModeOff = getString(R.string.voice_mode_off); mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff) .equals(mVoiceModeOff)); - mVoiceLogger = VoiceConnector.VoiceLoggerConnector.getInstance(this); + mVoiceLogger = VoiceProxy.VoiceLoggerWrapper.getInstance(this); mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD); mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS); @@ -184,7 +184,7 @@ public class Settings extends PreferenceActivity ((PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY)) .removePreference(mQuickFixes); } - if (!VoiceConnector.VOICE_INSTALLED + if (!VoiceProxy.VOICE_INSTALLED || !SpeechRecognizer.isRecognitionAvailable(this)) { getPreferenceScreen().removePreference(mVoicePreference); } else { diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index d4db7d0f7..17bf5bc18 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -18,7 +18,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; -import com.android.inputmethod.deprecated.VoiceConnector; +import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.LatinKeyboard; @@ -78,7 +78,7 @@ public class SubtypeSwitcher { private Locale mSystemLocale; private Locale mInputLocale; private String mInputLocaleStr; - private VoiceConnector.VoiceInputConnector mVoiceInputConnector; + private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper; /*-----------------------------------------------------------*/ private boolean mIsNetworkConnected; @@ -113,7 +113,7 @@ public class SubtypeSwitcher { mCurrentSubtype = null; mAllEnabledSubtypesOfCurrentInputMethod = null; // TODO: Voice input should be created here - mVoiceInputConnector = null; + mVoiceInputWrapper = null; mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean( R.bool.config_use_spacebar_language_switcher); if (mConfigUseSpacebarLanguageSwitcher) @@ -243,30 +243,30 @@ public class SubtypeSwitcher { // We cancel its status when we change mode, while we reset otherwise. if (isKeyboardMode()) { if (modeChanged) { - if (VOICE_MODE.equals(oldMode) && mVoiceInputConnector != null) { - mVoiceInputConnector.cancel(); + if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) { + mVoiceInputWrapper.cancel(); } } if (modeChanged || languageChanged) { updateShortcutIME(); mService.onRefreshKeyboard(); } - } else if (isVoiceMode() && mVoiceInputConnector != null) { + } else if (isVoiceMode() && mVoiceInputWrapper != null) { if (VOICE_MODE.equals(oldMode)) { - mVoiceInputConnector.reset(); + mVoiceInputWrapper.reset(); } // If needsToShowWarningDialog is true, voice input need to show warning before // show recognition view. if (languageChanged || modeChanged - || VoiceConnector.getInstance().needsToShowWarningDialog()) { + || VoiceProxy.getInstance().needsToShowWarningDialog()) { triggerVoiceIME(); } } else { Log.w(TAG, "Unknown subtype mode: " + newMode); - if (VOICE_MODE.equals(oldMode) && mVoiceInputConnector != null) { + if (VOICE_MODE.equals(oldMode) && mVoiceInputWrapper != null) { // We need to reset the voice input to release the resources and to reset its status // as it is not the current input mode. - mVoiceInputConnector.reset(); + mVoiceInputWrapper.reset(); } } } @@ -520,9 +520,9 @@ public class SubtypeSwitcher { // Voice Input functions // /////////////////////////// - public boolean setVoiceInputConnector(VoiceConnector.VoiceInputConnector vi) { - if (mVoiceInputConnector == null && vi != null) { - mVoiceInputConnector = vi; + public boolean setVoiceInputWrapper(VoiceProxy.VoiceInputWrapper vi) { + if (mVoiceInputWrapper == null && vi != null) { + mVoiceInputWrapper = vi; if (isVoiceMode()) { if (DBG) { Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr()); @@ -540,7 +540,7 @@ public class SubtypeSwitcher { private void triggerVoiceIME() { if (!mService.isInputViewShown()) return; - VoiceConnector.getInstance().startListening(false, + VoiceProxy.getInstance().startListening(false, KeyboardSwitcher.getInstance().getInputView().getWindowToken()); } @@ -631,7 +631,7 @@ public class SubtypeSwitcher { // input. Because this method is called by onStartInputView, this should mean that as // long as the locale doesn't change while the user is keeping the IME open, the // value should never be stale. - String supportedLocalesString = VoiceConnector.getSupportedLocalesString( + String supportedLocalesString = VoiceProxy.getSupportedLocalesString( mService.getContentResolver()); List voiceInputSupportedLocales = Arrays.asList( supportedLocalesString.split("\\s+")); -- cgit v1.2.3-83-g751a From 699e429f1913197650cc2fc2d8cb6e506be66842 Mon Sep 17 00:00:00 2001 From: satok Date: Wed, 23 Mar 2011 14:36:37 -0700 Subject: Added VibratorCompatWrapper Change-Id: I7640f051873f42aa15337edc718ac95ec1c51698 --- .../inputmethod/compat/VibratorCompatWrapper.java | 47 ++++++++++++++++++++++ .../com/android/inputmethod/latin/LatinIME.java | 7 ++-- .../com/android/inputmethod/latin/Settings.java | 5 +-- 3 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java new file mode 100644 index 000000000..8e2a2e0b8 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/VibratorCompatWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.compat; + +import android.content.Context; +import android.os.Vibrator; + +import java.lang.reflect.Method; + +public class VibratorCompatWrapper { + private static final Method METHOD_hasVibrator = CompatUtils.getMethod(Vibrator.class, + "hasVibrator", int.class); + + private static final VibratorCompatWrapper sInstance = new VibratorCompatWrapper(); + private Vibrator mVibrator; + + private VibratorCompatWrapper() { + } + + public static VibratorCompatWrapper getInstance(Context context) { + if (sInstance.mVibrator == null) { + sInstance.mVibrator = + (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + } + return sInstance; + } + + public boolean hasVibrator() { + if (mVibrator == null) + return false; + return (Boolean) CompatUtils.invoke(mVibrator, true, METHOD_hasVibrator); + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 79ef03a8b..64656c381 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -20,6 +20,7 @@ import com.android.inputmethod.compat.CompatUtils; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; import com.android.inputmethod.deprecated.VoiceProxy; +import com.android.inputmethod.compat.VibratorCompatWrapper; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardSwitcher; @@ -45,7 +46,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.SystemClock; -import android.os.Vibrator; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; import android.text.InputType; @@ -2118,9 +2118,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private void loadSettings(EditorInfo attribute) { // Get the settings preferences final SharedPreferences prefs = mPrefs; - Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); - mVibrateOn = vibrator != null && vibrator.hasVibrator() - && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); + final boolean hasVibrator = VibratorCompatWrapper.getInstance(this).hasVibrator(); + mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, mResources.getBoolean(R.bool.config_default_sound_enabled)); diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index aedb0ff75..488ab09c3 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.CompatUtils; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.deprecated.VoiceProxy; +import com.android.inputmethod.compat.VibratorCompatWrapper; import android.app.AlertDialog; import android.app.Dialog; @@ -26,7 +27,6 @@ import android.app.backup.BackupManager; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; -import android.os.Vibrator; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; @@ -134,8 +134,7 @@ public class Settings extends PreferenceActivity generalSettings.removePreference(mVoicePreference); } - Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); - if (vibrator == null || !vibrator.hasVibrator()) { + if (!VibratorCompatWrapper.getInstance(this).hasVibrator()) { generalSettings.removePreference(findPreference(PREF_VIBRATE_ON)); } -- cgit v1.2.3-83-g751a From a3ef6cde2158b4a76c70070d790959b03ef0e9d1 Mon Sep 17 00:00:00 2001 From: satok Date: Thu, 24 Mar 2011 21:37:21 -0700 Subject: Moved a functionality of setOverScrollMode to InputMethodServiceCompatWrapper Change-Id: Ie473c9fced9f0a8da5643390b44aa4b86128396d --- .../com/android/inputmethod/compat/CompatUtils.java | 8 +++++--- .../compat/InputMethodServiceCompatWrapper.java | 21 +++++++++++++++++++++ .../src/com/android/inputmethod/latin/LatinIME.java | 2 +- 3 files changed, 27 insertions(+), 4 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java index 8bf59091f..07db4a1bb 100644 --- a/java/src/com/android/inputmethod/compat/CompatUtils.java +++ b/java/src/com/android/inputmethod/compat/CompatUtils.java @@ -96,7 +96,7 @@ public class CompatUtils { try { return method.invoke(receiver, args); } catch (IllegalArgumentException e) { - Log.e(TAG, "Exception in invoke: IllegalArgmentException"); + Log.e(TAG, "Exception in invoke: IllegalArgumentException"); return defaultValue; } catch (IllegalAccessException e) { Log.e(TAG, "Exception in invoke: IllegalAccessException"); @@ -112,8 +112,10 @@ public class CompatUtils { try { return field.get(receiver); } catch (IllegalArgumentException e) { + Log.e(TAG, "Exception in getFieldValue: IllegalArgumentException"); return defaultValue; } catch (IllegalAccessException e) { + Log.e(TAG, "Exception in getFieldValue: IllegalAccessException"); return defaultValue; } } @@ -123,9 +125,9 @@ public class CompatUtils { try { field.set(receiver, value); } catch (IllegalArgumentException e) { - // ignore + Log.e(TAG, "Exception in setFieldValue: IllegalArgumentException"); } catch (IllegalAccessException e) { - // ignore + Log.e(TAG, "Exception in setFieldValue: IllegalAccessException"); } } diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java index 606322af7..e02aac704 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java @@ -19,9 +19,20 @@ package com.android.inputmethod.compat; import com.android.inputmethod.latin.SubtypeSwitcher; import android.inputmethodservice.InputMethodService; +import android.view.View; import android.view.inputmethod.InputMethodSubtype; +import android.widget.HorizontalScrollView; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; public class InputMethodServiceCompatWrapper extends InputMethodService { + private static final Method METHOD_HorizontalScrollView_setOverScrollMode = + CompatUtils.getMethod(HorizontalScrollView.class, "setOverScrollMode", int.class); + private static final Field FIELD_View_OVER_SCROLL_NEVER = + CompatUtils.getField(View.class, "OVER_SCROLL_NEVER"); + private static final Integer View_OVER_SCROLL_NEVER = + (Integer)CompatUtils.getFieldValue(null, null, FIELD_View_OVER_SCROLL_NEVER); // CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED needs to be false if the API level is 10 // or previous. Note that InputMethodSubtype was added in the API level 11. // For the API level 11 or later, LatinIME should override onCurrentInputMethodSubtypeChanged(). @@ -55,6 +66,16 @@ public class InputMethodServiceCompatWrapper extends InputMethodService { } } + protected static void setOverScrollModeNever(HorizontalScrollView scrollView) { + if (View_OVER_SCROLL_NEVER != null) { + CompatUtils.invoke(scrollView, null, METHOD_HorizontalScrollView_setOverScrollMode, + View_OVER_SCROLL_NEVER); + } + } + + ////////////////////////////////////// + // Functions using API v11 or later // + ////////////////////////////////////// @Override public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { // Do nothing when the API level is 10 or previous diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 09a4ead22..4b38e2707 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -530,7 +530,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (container.getPaddingRight() != 0) { HorizontalScrollView scrollView = (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view); - scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); + setOverScrollModeNever(scrollView); container.setGravity(Gravity.CENTER_HORIZONTAL); } mCandidateView = (CandidateView) container.findViewById(R.id.candidates); -- cgit v1.2.3-83-g751a From b2707856aba4fc9b063f26305f1fb603b19c1701 Mon Sep 17 00:00:00 2001 From: satok Date: Wed, 23 Mar 2011 23:18:49 -0700 Subject: Added InputConnectionCompatUtils and EditorInfoCompatUtils Change-Id: Ie69d5e90c4ee978d3ecb40b68e6466082569ca7a --- .../android/inputmethod/compat/CompatUtils.java | 14 +++++ .../inputmethod/compat/EditorInfoCompatUtils.java | 59 ++++++++++++++++++++++ .../compat/InputConnectionCompatUtils.java | 58 +++++++++++++++++++++ .../com/android/inputmethod/latin/LatinIME.java | 22 ++++---- 4 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java create mode 100644 java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java index 07db4a1bb..a8086919c 100644 --- a/java/src/com/android/inputmethod/compat/CompatUtils.java +++ b/java/src/com/android/inputmethod/compat/CompatUtils.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.text.TextUtils; import android.util.Log; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -90,6 +91,19 @@ public class CompatUtils { } } + public static Constructor getConstructor(Class targetClass, Class[] types) { + if (targetClass == null || types == null) return null; + try { + return targetClass.getConstructor(types); + } catch (SecurityException e) { + // ignore + return null; + } catch (NoSuchMethodException e) { + // ignore + return null; + } + } + public static Object invoke( Object receiver, Object defaultValue, Method method, Object... args) { if (receiver == null || method == null) return defaultValue; diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java new file mode 100644 index 000000000..9d83b5463 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java @@ -0,0 +1,59 @@ +/* + * 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.compat; + +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import java.lang.reflect.Field; + +public class EditorInfoCompatUtils { + private static final Field FIELD_IME_FLAG_NAVIGATE_NEXT = CompatUtils.getField( + EditorInfo.class, "IME_FLAG_NAVIGATE_NEXT"); + private static final Field FIELD_IME_FLAG_NAVIGATE_PREVIOUS = CompatUtils.getField( + EditorInfo.class, "IME_FLAG_NAVIGATE_PREVIOUS"); + private static final Field FIELD_IME_ACTION_PREVIOUS = CompatUtils.getField( + EditorInfo.class, "IME_FLAG_ACTION_PREVIOUS"); + private static final Integer OBJ_IME_FLAG_NAVIGATE_NEXT = (Integer) CompatUtils + .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_NEXT); + private static final Integer OBJ_IME_FLAG_NAVIGATE_PREVIOUS = (Integer) CompatUtils + .getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_PREVIOUS); + private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils + .getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS); + + public static boolean hasFlagNavigateNext(int imeOptions) { + if (OBJ_IME_FLAG_NAVIGATE_NEXT == null) + return false; + return (imeOptions & OBJ_IME_FLAG_NAVIGATE_NEXT) != 0; + } + + public static boolean hasFlagNavigatePrevious(int imeOptions) { + if (OBJ_IME_FLAG_NAVIGATE_PREVIOUS == null) + return false; + return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0; + } + + public static void performEditorActionNext(InputConnection ic) { + ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); + } + + public static void performEditorActionPrevious(InputConnection ic) { + if (OBJ_IME_ACTION_PREVIOUS == null) + return; + ic.performEditorAction(OBJ_IME_ACTION_PREVIOUS); + } +} diff --git a/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java new file mode 100644 index 000000000..b4fe32765 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputConnectionCompatUtils.java @@ -0,0 +1,58 @@ +/* + * 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.compat; + +import android.util.Log; +import android.view.inputmethod.InputConnection; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class InputConnectionCompatUtils { + private static final String TAG = InputConnectionCompatUtils.class.getSimpleName(); + private static final Class CLASS_CorrectionInfo = CompatUtils + .getClass("android.view.inputmethod.CorrectionInfo"); + private static final Class[] INPUT_TYPE_CorrectionInfo = new Class[] { int.class, + CharSequence.class, CharSequence.class }; + private static final Constructor CONSTRUCTOR_CorrectionInfo = CompatUtils + .getConstructor(CLASS_CorrectionInfo, INPUT_TYPE_CorrectionInfo); + private static final Method METHOD_InputConnection_commitCorrection = CompatUtils + .getMethod(InputConnection.class, "commitCorrection", CLASS_CorrectionInfo); + + public static void commitCorrection(InputConnection ic, int offset, CharSequence oldText, + CharSequence newText) { + if (ic == null || CONSTRUCTOR_CorrectionInfo == null + || METHOD_InputConnection_commitCorrection == null) { + return; + } + Object[] args = { offset, oldText, newText }; + try { + Object correctionInfo = CONSTRUCTOR_CorrectionInfo.newInstance(args); + CompatUtils.invoke(ic, null, METHOD_InputConnection_commitCorrection, + correctionInfo); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Error in commitCorrection: IllegalArgumentException"); + } catch (InstantiationException e) { + Log.e(TAG, "Error in commitCorrection: InstantiationException"); + } catch (IllegalAccessException e) { + Log.e(TAG, "Error in commitCorrection: IllegalAccessException"); + } catch (InvocationTargetException e) { + Log.e(TAG, "Error in commitCorrection: InvocationTargetException"); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 05169dd9b..c050c834e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -17,6 +17,8 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.CompatUtils; +import com.android.inputmethod.compat.EditorInfoCompatUtils; +import com.android.inputmethod.compat.InputConnectionCompatUtils; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; import com.android.inputmethod.deprecated.VoiceProxy; @@ -64,7 +66,6 @@ import android.view.ViewParent; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -1267,9 +1268,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private void handleTab() { final int imeOptions = getCurrentInputEditorInfo().imeOptions; - final int navigationFlags = - EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; - if ((imeOptions & navigationFlags) == 0) { + if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) + && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) { sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB); return; } @@ -1280,12 +1280,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // True if keyboard is in either chording shift or manual temporary upper case mode. final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase(); - if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0 + if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) && !isManualTemporaryUpperCase) { + EditorInfoCompatUtils.performEditorActionNext(ic); ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); - } else if ((imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0 + } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions) && isManualTemporaryUpperCase) { - ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); + EditorInfoCompatUtils.performEditorActionPrevious(ic); } } @@ -1411,11 +1412,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar CharSequence typedWord = mWord.getTypedWord(); TextEntryState.backToAcceptedDefault(typedWord); if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) { - if (ic != null) { - CorrectionInfo correctionInfo = new CorrectionInfo( - mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); - ic.commitCorrection(correctionInfo); - } + InputConnectionCompatUtils.commitCorrection( + ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); if (mCandidateView != null) mCandidateView.onAutoCorrectionInverted(mBestWord); } -- cgit v1.2.3-83-g751a From e9957752bcaad048746c7a57bbd2c0a59e1918a0 Mon Sep 17 00:00:00 2001 From: satok Date: Thu, 24 Mar 2011 23:28:29 -0700 Subject: Added InputTypeCompatUtils Also Moved utilities related to InputType to InputTypeCompatUtils Change-Id: Iab8ff5389f624f2abe627ece2acb156b0e1285ef --- .../inputmethod/compat/InputTypeCompatUtils.java | 95 ++++++++++++++++++++++ .../android/inputmethod/keyboard/KeyboardId.java | 6 +- .../com/android/inputmethod/latin/LatinIME.java | 15 ++-- java/src/com/android/inputmethod/latin/Utils.java | 28 +------ 4 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java new file mode 100644 index 000000000..d85174188 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/InputTypeCompatUtils.java @@ -0,0 +1,95 @@ +/* + * 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.compat; + +import android.text.InputType; + +import java.lang.reflect.Field; + +public class InputTypeCompatUtils { + private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = + CompatUtils.getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS"); + private static final Field FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD = CompatUtils + .getField(InputType.class, "TYPE_TEXT_VARIATION_WEB_PASSWORD"); + private static final Field FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD = CompatUtils + .getField(InputType.class, "TYPE_NUMBER_VARIATION_PASSWORD"); + private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = + (Integer) CompatUtils.getFieldValue(null, null, + FIELD_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS); + private static final Integer OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD = + (Integer) CompatUtils.getFieldValue(null, null, + FIELD_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD); + private static final Integer OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD = + (Integer) CompatUtils.getFieldValue(null, null, + FIELD_InputType_TYPE_NUMBER_VARIATION_PASSWORD); + private static final int WEB_TEXT_PASSWORD_INPUT_TYPE; + private static final int NUMBER_PASSWORD_INPUT_TYPE; + private static final int TEXT_PASSWORD_INPUT_TYPE = + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; + private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE = + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + + static { + WEB_TEXT_PASSWORD_INPUT_TYPE = + OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD != null + ? InputType.TYPE_CLASS_TEXT | OBJ_InputType_TYPE_TEXT_VARIATION_WEB_PASSWORD + : 0; + NUMBER_PASSWORD_INPUT_TYPE = + OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD != null + ? InputType.TYPE_CLASS_NUMBER | OBJ_InputType_TYPE_NUMBER_VARIATION_PASSWORD + : 0; + } + + private static boolean isWebPasswordInputType(int inputType) { + return WEB_TEXT_PASSWORD_INPUT_TYPE != 0 + && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE; + } + + private static boolean isNumberPasswordInputType(int inputType) { + return NUMBER_PASSWORD_INPUT_TYPE != 0 + && inputType == NUMBER_PASSWORD_INPUT_TYPE; + } + + private static boolean isTextPasswordInputType(int inputType) { + return inputType == TEXT_PASSWORD_INPUT_TYPE; + } + + private static boolean isWebEmailAddressVariation(int variation) { + return OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != null + && variation == OBJ_InputType_TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; + } + + public static boolean isEmailVariation(int variation) { + return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + || isWebEmailAddressVariation(variation); + } + + // Please refer to TextView.isPasswordInputType + public static boolean isPasswordInputType(int inputType) { + final int maskedInputType = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType) + || isNumberPasswordInputType(maskedInputType); + } + + // Please refer to TextView.isVisiblePasswordInputType + public static boolean isVisiblePasswordInputType(int inputType) { + final int maskedInputType = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index d09f6786e..5d8cf7cff 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -16,8 +16,8 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.Utils; import android.view.inputmethod.EditorInfo; @@ -62,8 +62,8 @@ public class KeyboardId { this.mMode = mode; this.mXmlId = xmlId; this.mColorScheme = colorScheme; - this.mPasswordInput = Utils.isPasswordInputType(inputType) - || Utils.isVisiblePasswordInputType(inputType); + this.mPasswordInput = InputTypeCompatUtils.isPasswordInputType(inputType) + || InputTypeCompatUtils.isVisiblePasswordInputType(inputType); this.mHasSettingsKey = hasSettingsKey; this.mVoiceKeyEnabled = voiceKeyEnabled; this.mHasVoiceKey = hasVoiceKey; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index c050c834e..ae2315437 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -21,8 +21,9 @@ import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.compat.InputConnectionCompatUtils; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; -import com.android.inputmethod.deprecated.VoiceProxy; +import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.compat.VibratorCompatWrapper; +import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardSwitcher; @@ -561,8 +562,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // know now whether this is a password text field, because we need to know now whether we // want to enable the voice button. final VoiceProxy voiceIme = mVoiceProxy; - voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) - || Utils.isVisiblePasswordInputType(attribute.inputType)); + voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(attribute.inputType) + || InputTypeCompatUtils.isVisiblePasswordInputType(attribute.inputType)); initializeInputAttributes(attribute); @@ -616,17 +617,17 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { mIsSettingsSuggestionStripOn = true; // Make sure that passwords are not displayed in candidate view - if (Utils.isPasswordInputType(inputType) - || Utils.isVisiblePasswordInputType(inputType)) { + if (InputTypeCompatUtils.isPasswordInputType(inputType) + || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) { mIsSettingsSuggestionStripOn = false; } - if (Utils.isEmailVariation(variation) + if (InputTypeCompatUtils.isEmailVariation(variation) || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { mAutoSpace = false; } else { mAutoSpace = true; } - if (Utils.isEmailVariation(variation)) { + if (InputTypeCompatUtils.isEmailVariation(variation)) { mIsSettingsSuggestionStripOn = false; } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { mIsSettingsSuggestionStripOn = false; diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 2c21de8c0..35b2b123c 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.keyboard.KeyboardId; import android.content.res.Resources; @@ -485,7 +486,7 @@ public class Utils { case InputType.TYPE_CLASS_PHONE: return KeyboardId.MODE_PHONE; case InputType.TYPE_CLASS_TEXT: - if (Utils.isEmailVariation(variation)) { + if (InputTypeCompatUtils.isEmailVariation(variation)) { return KeyboardId.MODE_EMAIL; } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { return KeyboardId.MODE_URL; @@ -503,31 +504,6 @@ public class Utils { } } - public static boolean isEmailVariation(int variation) { - return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; - } - - // Please refer to TextView.isPasswordInputType - public static boolean isPasswordInputType(int inputType) { - final int variation = - inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); - return (variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) - || (variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) - || (variation - == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); - } - - // Please refer to TextView.isVisiblePasswordInputType - public static boolean isVisiblePasswordInputType(int inputType) { - final int variation = - inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); - return variation - == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - } - public static boolean containsInCsv(String key, String csv) { if (csv == null) return false; -- cgit v1.2.3-83-g751a From f1a81f5eb37df4170de2cf6327c860e3d64dc2f8 Mon Sep 17 00:00:00 2001 From: satok Date: Fri, 25 Mar 2011 12:40:57 -0700 Subject: Move a debug utility function to EditorInfoCompatUtils - imeOptionName Change-Id: I7a7ea9bee410cedc0ef9b3d2aa4df8260d7f6a35 --- .../inputmethod/compat/EditorInfoCompatUtils.java | 40 ++++++++++++++++++++++ .../android/inputmethod/keyboard/KeyboardId.java | 25 ++------------ .../inputmethod/keyboard/KeyboardParser.java | 3 +- 3 files changed, 44 insertions(+), 24 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java index 9d83b5463..f6f4f7a59 100644 --- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java @@ -56,4 +56,44 @@ public class EditorInfoCompatUtils { return; ic.performEditorAction(OBJ_IME_ACTION_PREVIOUS); } + + public static String imeOptionsName(int imeOptions) { + if (imeOptions == -1) + return null; + final int actionId = imeOptions & EditorInfo.IME_MASK_ACTION; + final String action; + switch (actionId) { + case EditorInfo.IME_ACTION_UNSPECIFIED: + action = "actionUnspecified"; + break; + case EditorInfo.IME_ACTION_NONE: + action = "actionNone"; + break; + case EditorInfo.IME_ACTION_GO: + action = "actionGo"; + break; + case EditorInfo.IME_ACTION_SEARCH: + action = "actionSearch"; + break; + case EditorInfo.IME_ACTION_SEND: + action = "actionSend"; + break; + case EditorInfo.IME_ACTION_DONE: + action = "actionDone"; + break; + default: { + if (OBJ_IME_ACTION_PREVIOUS != null && actionId == OBJ_IME_ACTION_PREVIOUS) { + action = "actionPrevious"; + } else { + action = "actionUnknown(" + actionId + ")"; + } + break; + } + } + if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { + return "flagNoEnterAction|" + action; + } else { + return action; + } + } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index 5d8cf7cff..f68b68f1d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -16,6 +16,7 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.latin.R; @@ -140,7 +141,7 @@ public class KeyboardId { mLocale, (mOrientation == 1 ? "port" : "land"), modeName(mMode), - imeOptionsName(mImeAction), + EditorInfoCompatUtils.imeOptionsName(mImeAction), (mPasswordInput ? " passwordInput" : ""), (mHasSettingsKey ? " hasSettingsKey" : ""), (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""), @@ -170,26 +171,4 @@ public class KeyboardId { } return null; } - - public static String imeOptionsName(int imeOptions) { - if (imeOptions == -1) return null; - final int actionNo = imeOptions & EditorInfo.IME_MASK_ACTION; - final String action; - switch (actionNo) { - case EditorInfo.IME_ACTION_UNSPECIFIED: action = "actionUnspecified"; break; - case EditorInfo.IME_ACTION_NONE: action = "actionNone"; break; - case EditorInfo.IME_ACTION_GO: action = "actionGo"; break; - case EditorInfo.IME_ACTION_SEARCH: action = "actionSearch"; break; - case EditorInfo.IME_ACTION_SEND: action = "actionSend"; break; - case EditorInfo.IME_ACTION_DONE: action = "actionDone"; break; - case EditorInfo.IME_ACTION_PREVIOUS: action = "actionPrevious"; break; - default: action = "actionUnknown(" + actionNo + ")"; break; - } - if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { - return "flagNoEnterAction|" + action; - } else { - return action; - } - } } - diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java index feb56ab3a..62e6f302d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -16,6 +16,7 @@ package com.android.inputmethod.keyboard; +import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.latin.R; import org.xmlpull.v1.XmlPullParser; @@ -453,7 +454,7 @@ public class KeyboardParser { booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"), booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"), booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), - textAttr(KeyboardId.imeOptionsName( + textAttr(EditorInfoCompatUtils.imeOptionsName( a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"), textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"), textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), "countryCode"), -- cgit v1.2.3-83-g751a