aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/latin/BaseKeyboard.java92
-rw-r--r--java/src/com/android/inputmethod/latin/BaseKeyboardParser.java57
-rw-r--r--java/src/com/android/inputmethod/latin/KeyStyles.java237
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java510
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIMESettings.java3
-rw-r--r--java/src/com/android/inputmethod/latin/LatinKeyboard.java8
-rw-r--r--java/src/com/android/inputmethod/voice/VoiceIMEConnector.java537
7 files changed, 919 insertions, 525 deletions
diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboard.java b/java/src/com/android/inputmethod/latin/BaseKeyboard.java
index cb0ac216c..485cc3153 100644
--- a/java/src/com/android/inputmethod/latin/BaseKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/BaseKeyboard.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.BaseKeyboardParser.ParseException;
+import com.android.inputmethod.latin.KeyStyles.KeyStyle;
import com.android.inputmethod.latin.KeyboardSwitcher.KeyboardId;
import org.xmlpull.v1.XmlPullParserException;
@@ -26,14 +28,11 @@ import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
-import android.util.Log;
-import android.util.TypedValue;
import android.util.Xml;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import java.util.StringTokenizer;
/**
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -284,7 +283,8 @@ public class BaseKeyboard {
* @param y the y coordinate of the top-left
* @param parser the XML parser containing the attributes for this key
*/
- public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) {
+ public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser,
+ KeyStyles keyStyles) {
this(parent);
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
@@ -299,44 +299,47 @@ public class BaseKeyboard {
R.styleable.BaseKeyboard_keyWidth,
keyboard.mDisplayWidth, parent.defaultWidth) - gap;
a.recycle();
+
a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.BaseKeyboard_Key);
+ final KeyStyle style;
+ if (a.hasValue(R.styleable.BaseKeyboard_Key_keyStyle)) {
+ String styleName = a.getString(R.styleable.BaseKeyboard_Key_keyStyle);
+ style = keyStyles.getKeyStyle(styleName);
+ if (style == null)
+ throw new ParseException("Unknown key style: " + styleName, parser);
+ } else {
+ style = keyStyles.getEmptyKeyStyle();
+ }
+
// Horizontal gap is divided equally to both sides of the key.
this.x = x + gap / 2;
this.y = y;
- TypedValue codesValue = new TypedValue();
- a.getValue(R.styleable.BaseKeyboard_Key_codes, codesValue);
- if (codesValue.type == TypedValue.TYPE_INT_DEC
- || codesValue.type == TypedValue.TYPE_INT_HEX) {
- codes = new int[] { codesValue.data };
- } else if (codesValue.type == TypedValue.TYPE_STRING) {
- codes = parseCSV(codesValue.string.toString());
- }
-
- iconPreview = a.getDrawable(R.styleable.BaseKeyboard_Key_iconPreview);
+ codes = style.getIntArray(a, R.styleable.BaseKeyboard_Key_codes);
+ iconPreview = style.getDrawable(a, R.styleable.BaseKeyboard_Key_iconPreview);
setDefaultBounds(iconPreview);
- popupCharacters = a.getText(R.styleable.BaseKeyboard_Key_popupCharacters);
- popupResId = a.getResourceId(R.styleable.BaseKeyboard_Key_popupKeyboard, 0);
- repeatable = a.getBoolean(R.styleable.BaseKeyboard_Key_isRepeatable, false);
- modifier = a.getBoolean(R.styleable.BaseKeyboard_Key_isModifier, false);
- sticky = a.getBoolean(R.styleable.BaseKeyboard_Key_isSticky, false);
- edgeFlags = a.getInt(R.styleable.BaseKeyboard_Key_keyEdgeFlags, 0);
+ popupCharacters = style.getText(a, R.styleable.BaseKeyboard_Key_popupCharacters);
+ popupResId = style.getResourceId(a, R.styleable.BaseKeyboard_Key_popupKeyboard, 0);
+ repeatable = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isRepeatable, false);
+ modifier = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isModifier, false);
+ sticky = style.getBoolean(a, R.styleable.BaseKeyboard_Key_isSticky, false);
+ edgeFlags = style.getFlag(a, R.styleable.BaseKeyboard_Key_keyEdgeFlags, 0);
edgeFlags |= parent.rowEdgeFlags;
- icon = a.getDrawable(R.styleable.BaseKeyboard_Key_keyIcon);
+ icon = style.getDrawable(a, R.styleable.BaseKeyboard_Key_keyIcon);
setDefaultBounds(icon);
- hintIcon = a.getDrawable(R.styleable.BaseKeyboard_Key_keyHintIcon);
+ hintIcon = style.getDrawable(a, R.styleable.BaseKeyboard_Key_keyHintIcon);
setDefaultBounds(hintIcon);
- manualTemporaryUpperCaseHintIcon = a.getDrawable(
+ manualTemporaryUpperCaseHintIcon = style.getDrawable(a,
R.styleable.BaseKeyboard_Key_manualTemporaryUpperCaseHintIcon);
setDefaultBounds(manualTemporaryUpperCaseHintIcon);
- label = a.getText(R.styleable.BaseKeyboard_Key_keyLabel);
- labelOption = a.getInt(R.styleable.BaseKeyboard_Key_keyLabelOption, 0);
- manualTemporaryUpperCaseCode = a.getInt(
+ label = style.getText(a, R.styleable.BaseKeyboard_Key_keyLabel);
+ labelOption = style.getFlag(a, R.styleable.BaseKeyboard_Key_keyLabelOption, 0);
+ manualTemporaryUpperCaseCode = style.getInt(a,
R.styleable.BaseKeyboard_Key_manualTemporaryUpperCaseCode, 0);
- text = a.getText(R.styleable.BaseKeyboard_Key_keyOutputText);
+ text = style.getText(a, R.styleable.BaseKeyboard_Key_keyOutputText);
if (codes == null && !TextUtils.isEmpty(label)) {
codes = new int[] { label.charAt(0) };
@@ -366,28 +369,6 @@ public class BaseKeyboard {
}
}
- private int[] parseCSV(String value) {
- int count = 0;
- int lastIndex = 0;
- if (value.length() > 0) {
- count++;
- while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
- count++;
- }
- }
- int[] values = new int[count];
- count = 0;
- StringTokenizer st = new StringTokenizer(value, ",");
- while (st.hasMoreTokens()) {
- try {
- values[count++] = Integer.parseInt(st.nextToken());
- } catch (NumberFormatException nfe) {
- Log.e(TAG, "Error parsing keycodes " + value);
- }
- }
- return values;
- }
-
/**
* Detects if a point falls inside this key.
* @param x the x-coordinate of the point
@@ -560,6 +541,10 @@ public class BaseKeyboard {
mTotalHeight = y + mDefaultHeight;
}
+ public KeyboardId getKeyboardId() {
+ return mId;
+ }
+
public List<Key> getKeys() {
return mKeys;
}
@@ -688,14 +673,15 @@ public class BaseKeyboard {
// TODO should be private
protected BaseKeyboard.Key createKeyFromXml(Resources res, Row parent, int x, int y,
- XmlResourceParser parser) {
- return new BaseKeyboard.Key(res, parent, x, y, parser);
+ XmlResourceParser parser, KeyStyles keyStyles) {
+ return new BaseKeyboard.Key(res, parent, x, y, parser, keyStyles);
}
private void loadKeyboard(Context context, int xmlLayoutResId) {
try {
- BaseKeyboardParser parser = new BaseKeyboardParser(this, context.getResources());
- parser.parseKeyboard(context.getResources().getXml(xmlLayoutResId));
+ final Resources res = context.getResources();
+ BaseKeyboardParser parser = new BaseKeyboardParser(this, res);
+ parser.parseKeyboard(res.getXml(xmlLayoutResId));
// mTotalWidth is the width of this keyboard which is maximum width of row.
mTotalWidth = parser.getMaxRowWidth();
mTotalHeight = parser.getTotalHeight();
diff --git a/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java b/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java
index 1aee2fcef..ea209c515 100644
--- a/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java
+++ b/java/src/com/android/inputmethod/latin/BaseKeyboardParser.java
@@ -83,27 +83,25 @@ import java.util.List;
* &gt;/default&lt;
* &gt;/switch&lt;
* </pre>
- *
- * TODO: These are some random ideas to improve this parser.
- * - can specify keyWidth attribute by multiplication of default keyWidth
- * for example: keyWidth="200%b" ("b" stands for "base")
- * - can declare style and specify styles within tags.
- * for example:
+ * You can declare Key style and specify styles within Key tags.
+ * <pre>
* &gt;switch&lt;
* &gt;case colorScheme="white"&lt;
- * &gt;declare-style name="shift-key" parentStyle="modifier-key"&lt;
- * &gt;item name="keyIcon"&lt;@drawable/sym_keyboard_shift"&gt;/item&lt;
- * &gt;/declare-style&lt;
+ * &gt;key-style styleName="shift-key" parentStyle="modifier-key"
+ * keyIcon="@drawable/sym_keyboard_shift"
+ * /&lt;
* &gt;/case&lt;
* &gt;case colorScheme="black"&lt;
- * &gt;declare-style name="shift-key" parentStyle="modifier-key"&lt;
- * &gt;item name="keyIcon"&lt;@drawable/sym_bkeyboard_shift"&gt;/item&lt;
- * &gt;/declare-style&lt;
+ * &gt;key-style styleName="shift-key" parentStyle="modifier-key"
+ * keyIcon="@drawable/sym_bkeyboard_shift"
+ * /&lt;
* &gt;/case&lt;
* &gt;/switch&lt;
* ...
- * &gt;Key include-style="shift-key" ... /&lt;
+ * &gt;Key keyStyle="shift-key" ... /&lt;
+ * </pre>
*/
+
public class BaseKeyboardParser {
private static final String TAG = "BaseKeyboardParser";
private static final boolean DEBUG_TAG = false;
@@ -118,6 +116,7 @@ public class BaseKeyboardParser {
private static final String TAG_SWITCH = "switch";
private static final String TAG_CASE = "case";
private static final String TAG_DEFAULT = "default";
+ private static final String TAG_KEY_STYLE = "key-style";
private final BaseKeyboard mKeyboard;
private final Resources mResources;
@@ -127,6 +126,7 @@ public class BaseKeyboardParser {
private int mMaxRowWidth = 0;
private int mTotalHeight = 0;
private Row mCurrentRow = null;
+ private final KeyStyles mKeyStyles = new KeyStyles();
public BaseKeyboardParser(BaseKeyboard keyboard, Resources res) {
mKeyboard = keyboard;
@@ -192,6 +192,8 @@ public class BaseKeyboardParser {
parseIncludeKeyboardContent(parser, keys);
} else if (TAG_SWITCH.equals(tag)) {
parseSwitchKeyboardContent(parser, keys);
+ } else if (TAG_KEY_STYLE.equals(tag)) {
+ parseKeyStyle(parser, keys);
} else {
throw new IllegalStartTag(parser, TAG_ROW);
}
@@ -205,6 +207,8 @@ public class BaseKeyboardParser {
break;
} else if (TAG_MERGE.equals(tag)) {
break;
+ } else if (TAG_KEY_STYLE.equals(tag)) {
+ continue;
} else {
throw new IllegalEndTag(parser, TAG_ROW);
}
@@ -227,6 +231,8 @@ public class BaseKeyboardParser {
parseIncludeRowContent(parser, row, keys);
} else if (TAG_SWITCH.equals(tag)) {
parseSwitchRowContent(parser, row, keys);
+ } else if (TAG_KEY_STYLE.equals(tag)) {
+ parseKeyStyle(parser, keys);
} else {
throw new IllegalStartTag(parser, TAG_KEY);
}
@@ -241,6 +247,8 @@ public class BaseKeyboardParser {
break;
} else if (TAG_MERGE.equals(tag)) {
break;
+ } else if (TAG_KEY_STYLE.equals(tag)) {
+ continue;
} else {
throw new IllegalEndTag(parser, TAG_KEY);
}
@@ -253,7 +261,8 @@ public class BaseKeyboardParser {
if (keys == null) {
checkEndTag(TAG_KEY, parser);
} else {
- Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser);
+ Key key = mKeyboard.createKeyFromXml(mResources, row, mCurrentX, mCurrentY, parser,
+ mKeyStyles);
checkEndTag(TAG_KEY, parser);
keys.add(key);
if (key.codes[0] == BaseKeyboard.KEYCODE_SHIFT)
@@ -439,6 +448,24 @@ public class BaseKeyboardParser {
return true;
}
+ private void parseKeyStyle(XmlResourceParser parser, List<Key> keys)
+ throws XmlPullParserException, IOException {
+ TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.BaseKeyboard_KeyStyle);
+ TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.BaseKeyboard_Key);
+ try {
+ if (!a.hasValue(R.styleable.BaseKeyboard_KeyStyle_styleName))
+ throw new ParseException("<" + TAG_KEY_STYLE
+ + "/> needs styleName attribute", parser);
+ if (keys != null)
+ mKeyStyles.parseKeyStyleAttributes(a, keyAttrs, parser);
+ } finally {
+ a.recycle();
+ keyAttrs.recycle();
+ }
+ }
+
private static void checkEndTag(String tag, XmlResourceParser parser)
throws XmlPullParserException, IOException {
if (parser.next() == XmlResourceParser.END_TAG && tag.equals(parser.getName()))
@@ -486,7 +513,7 @@ public class BaseKeyboardParser {
}
@SuppressWarnings("serial")
- private static class ParseException extends InflateException {
+ public static class ParseException extends InflateException {
public ParseException(String msg, XmlResourceParser parser) {
super(msg + " at line " + parser.getLineNumber());
}
diff --git a/java/src/com/android/inputmethod/latin/KeyStyles.java b/java/src/com/android/inputmethod/latin/KeyStyles.java
new file mode 100644
index 000000000..e53e351a3
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/KeyStyles.java
@@ -0,0 +1,237 @@
+/*
+ * 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.latin;
+
+import com.android.inputmethod.latin.BaseKeyboardParser.ParseException;
+
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+public class KeyStyles {
+ private static final String TAG = "KeyStyles";
+
+ private final HashMap<String, DeclaredKeyStyle> mStyles =
+ new HashMap<String, DeclaredKeyStyle>();
+ private static final KeyStyle EMPTY_KEY_STYLE = new EmptyKeyStyle();
+
+ public interface KeyStyle {
+ public int[] getIntArray(TypedArray a, int index);
+ public Drawable getDrawable(TypedArray a, int index);
+ public CharSequence getText(TypedArray a, int index);
+ public int getResourceId(TypedArray a, int index, int defaultValue);
+ public int getInt(TypedArray a, int index, int defaultValue);
+ public int getFlag(TypedArray a, int index, int defaultValue);
+ public boolean getBoolean(TypedArray a, int index, boolean defaultValue);
+ }
+
+ public static class EmptyKeyStyle implements KeyStyle {
+ private EmptyKeyStyle() {
+ }
+
+ public int[] getIntArray(TypedArray a, int index) {
+ return parseIntArray(a, index);
+ }
+
+ public Drawable getDrawable(TypedArray a, int index) {
+ return a.getDrawable(index);
+ }
+
+ public CharSequence getText(TypedArray a, int index) {
+ return a.getText(index);
+ }
+
+ public int getResourceId(TypedArray a, int index, int defaultValue) {
+ return a.getResourceId(index, defaultValue);
+ }
+
+ public int getInt(TypedArray a, int index, int defaultValue) {
+ return a.getInt(index, defaultValue);
+ }
+
+ public int getFlag(TypedArray a, int index, int defaultValue) {
+ return a.getInt(index, defaultValue);
+ }
+
+ public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
+ return a.getBoolean(index, defaultValue);
+ }
+
+ protected static int[] parseIntArray(TypedArray a, int index) {
+ TypedValue v = new TypedValue();
+ a.getValue(index, v);
+ if (v.type == TypedValue.TYPE_INT_DEC || v.type == TypedValue.TYPE_INT_HEX) {
+ return new int[] { v.data };
+ } else if (v.type == TypedValue.TYPE_STRING) {
+ return parseCSV(v.string.toString());
+ } else {
+ return null;
+ }
+ }
+
+ private static int[] parseCSV(String value) {
+ int count = 0;
+ int lastIndex = 0;
+ if (value.length() > 0) {
+ count++;
+ while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
+ count++;
+ }
+ }
+ int[] values = new int[count];
+ count = 0;
+ StringTokenizer st = new StringTokenizer(value, ",");
+ while (st.hasMoreTokens()) {
+ try {
+ values[count++] = Integer.parseInt(st.nextToken());
+ } catch (NumberFormatException nfe) {
+ Log.e(TAG, "Error parsing integer CSV " + value);
+ }
+ }
+ return values;
+ }
+ }
+
+ public static class DeclaredKeyStyle extends EmptyKeyStyle {
+ private final HashMap<Integer, Object> mAttributes = new HashMap<Integer, Object>();
+
+ @Override
+ public int[] getIntArray(TypedArray a, int index) {
+ return a.hasValue(index)
+ ? super.getIntArray(a, index) : (int[])mAttributes.get(index);
+ }
+
+ @Override
+ public Drawable getDrawable(TypedArray a, int index) {
+ return a.hasValue(index)
+ ? super.getDrawable(a, index) : (Drawable)mAttributes.get(index);
+ }
+
+ @Override
+ public CharSequence getText(TypedArray a, int index) {
+ return a.hasValue(index)
+ ? super.getText(a, index) : (CharSequence)mAttributes.get(index);
+ }
+
+ @Override
+ public int getResourceId(TypedArray a, int index, int defaultValue) {
+ final Integer value = (Integer)mAttributes.get(index);
+ return super.getResourceId(a, index, (value != null) ? value : defaultValue);
+ }
+
+ @Override
+ public int getFlag(TypedArray a, int index, int defaultValue) {
+ final Integer value = (Integer)mAttributes.get(index);
+ return super.getFlag(a, index, defaultValue) | (value != null ? value : 0);
+ }
+
+ @Override
+ public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
+ final Boolean value = (Boolean)mAttributes.get(index);
+ return super.getBoolean(a, index, (value != null) ? value : defaultValue);
+ }
+
+ private DeclaredKeyStyle() {
+ super();
+ }
+
+ private void parseKeyStyleAttributes(TypedArray a) {
+ // TODO: Currently not all Key attributes can be declared as style.
+ readIntArray(a, R.styleable.BaseKeyboard_Key_codes);
+ readText(a, R.styleable.BaseKeyboard_Key_keyLabel);
+ readFlag(a, R.styleable.BaseKeyboard_Key_keyLabelOption);
+ readText(a, R.styleable.BaseKeyboard_Key_keyOutputText);
+ readDrawable(a, R.styleable.BaseKeyboard_Key_keyIcon);
+ readDrawable(a, R.styleable.BaseKeyboard_Key_iconPreview);
+ readDrawable(a, R.styleable.BaseKeyboard_Key_keyHintIcon);
+ readResourceId(a, R.styleable.BaseKeyboard_Key_popupKeyboard);
+ readBoolean(a, R.styleable.BaseKeyboard_Key_isModifier);
+ readBoolean(a, R.styleable.BaseKeyboard_Key_isSticky);
+ readBoolean(a, R.styleable.BaseKeyboard_Key_isRepeatable);
+ }
+
+ private void readDrawable(TypedArray a, int index) {
+ if (a.hasValue(index))
+ mAttributes.put(index, a.getDrawable(index));
+ }
+
+ private void readText(TypedArray a, int index) {
+ if (a.hasValue(index))
+ mAttributes.put(index, a.getText(index));
+ }
+
+ private void readResourceId(TypedArray a, int index) {
+ if (a.hasValue(index))
+ mAttributes.put(index, a.getResourceId(index, 0));
+ }
+
+ private void readFlag(TypedArray a, int index) {
+ final Integer value = (Integer)mAttributes.get(index);
+ if (a.hasValue(index))
+ mAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
+ }
+
+ private void readBoolean(TypedArray a, int index) {
+ if (a.hasValue(index))
+ mAttributes.put(index, a.getBoolean(index, false));
+ }
+
+ private void readIntArray(TypedArray a, int index) {
+ if (a.hasValue(index)) {
+ final int[] value = parseIntArray(a, index);
+ if (value != null)
+ mAttributes.put(index, value);
+ }
+ }
+
+ private void addParent(DeclaredKeyStyle parentStyle) {
+ mAttributes.putAll(parentStyle.mAttributes);
+ }
+ }
+
+ public void parseKeyStyleAttributes(TypedArray a, TypedArray keyAttrs,
+ XmlResourceParser parser) {
+ String styleName = a.getString(R.styleable.BaseKeyboard_KeyStyle_styleName);
+ if (mStyles.containsKey(styleName))
+ throw new ParseException("duplicate key style declared: " + styleName, parser);
+
+ final DeclaredKeyStyle style = new DeclaredKeyStyle();
+ if (a.hasValue(R.styleable.BaseKeyboard_KeyStyle_parentStyle)) {
+ String parentStyle = a.getString(
+ R.styleable.BaseKeyboard_KeyStyle_parentStyle);
+ final DeclaredKeyStyle parent = mStyles.get(parentStyle);
+ if (parent == null)
+ throw new ParseException("Unknown parentStyle " + parent, parser);
+ style.addParent(parent);
+ }
+ style.parseKeyStyleAttributes(keyAttrs);
+ mStyles.put(styleName, style);
+ }
+
+ public KeyStyle getKeyStyle(String styleName) {
+ return mStyles.get(styleName);
+ }
+
+ public KeyStyle getEmptyKeyStyle() {
+ return EMPTY_KEY_STYLE;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9c7af3557..d624ae444 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -17,8 +17,7 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.latin.LatinIMEUtil.RingCharBuffer;
-import com.android.inputmethod.voice.FieldContext;
-import com.android.inputmethod.voice.VoiceInput;
+import com.android.inputmethod.voice.VoiceIMEConnector;
import org.xmlpull.v1.XmlPullParserException;
@@ -41,8 +40,6 @@ import android.os.SystemClock;
import android.os.Vibrator;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
-import android.speech.SpeechRecognizer;
-import android.text.ClipboardManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -50,10 +47,7 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
-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.CompletionInfo;
@@ -70,25 +64,20 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
/**
* Input method implementation for Qwerty'ish keyboard.
*/
public class LatinIME extends InputMethodService
implements BaseKeyboardView.OnKeyboardActionListener,
- VoiceInput.UiListener,
SharedPreferences.OnSharedPreferenceChangeListener,
Tutorial.TutorialListener {
private static final String TAG = "LatinIME";
private static final boolean PERF_DEBUG = false;
- static final boolean DEBUG = false;
- static final boolean TRACE = false;
- static final boolean VOICE_INSTALLED = true;
- static final boolean ENABLE_VOICE_BUTTON = true;
+ private static final boolean DEBUG = false;
+ private static final boolean TRACE = false;
private static final String PREF_SOUND_ON = "sound_on";
private static final String PREF_POPUP_ON = "popup_on";
@@ -97,21 +86,6 @@ public class LatinIME extends InputMethodService
private static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold";
private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
- 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";
-
- // 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";
public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
public static final String PREF_INPUT_LANGUAGE = "input_language";
@@ -156,29 +130,22 @@ public class LatinIME extends InputMethodService
private CompletionInfo[] mCompletions;
private AlertDialog mOptionsDialog;
- private AlertDialog mVoiceWarningDialog;
private KeyboardSwitcher mKeyboardSwitcher;
private SubtypeSwitcher mSubtypeSwitcher;
+ private VoiceIMEConnector mVoiceConnector;
private UserDictionary mUserDictionary;
private UserBigramDictionary mUserBigramDictionary;
private ContactsDictionary mContactsDictionary;
private AutoDictionary mAutoDictionary;
- private Hints mHints;
-
private Resources mResources;
private final StringBuilder mComposing = new StringBuilder();
private WordComposer mWord = new WordComposer();
private int mCommittedLength;
private boolean mPredicting;
- private boolean mRecognizing;
- private boolean mAfterVoiceInput;
- private boolean mImmediatelyAfterVoiceInput;
- private boolean mShowingVoiceSuggestions;
- private boolean mVoiceInputHighlighted;
private CharSequence mBestWord;
private boolean mPredictionOn;
private boolean mCompletionOn;
@@ -189,19 +156,13 @@ public class LatinIME extends InputMethodService
private boolean mReCorrectionEnabled;
private boolean mBigramSuggestionEnabled;
private boolean mAutoCorrectOn;
- private boolean mPasswordText;
private boolean mVibrateOn;
private boolean mSoundOn;
private boolean mPopupOn;
private boolean mAutoCap;
private boolean mQuickFixes;
- private boolean mHasUsedVoiceInput;
- private boolean mHasUsedVoiceInputUnsupportedLocale;
- private boolean mLocaleSupportedForVoiceInput;
- private boolean mIsShowingHint;
private int mCorrectionMode;
- private boolean mVoiceKeyEnabled;
- private boolean mVoiceButtonOnPrimary;
+
private int mOrientation;
private List<CharSequence> mSuggestPuncList;
// Keep track of the last selection range to decide if we need to show word alternatives
@@ -227,25 +188,15 @@ public class LatinIME extends InputMethodService
/* package */ String mWordSeparators;
private String mSentenceSeparators;
private String mSuggestPuncs;
- private VoiceInput mVoiceInput;
- private final VoiceResults mVoiceResults = new VoiceResults();
+ // TODO: Move this flag to VoiceIMEConnector
private boolean mConfigurationChanging;
// Keeps track of most recently inserted text (multi-character key) for reverting
private CharSequence mEnteredText;
private boolean mRefreshKeyboardRequired;
- // For each word, a list of potential replacements, usually from voice.
- private final Map<String, List<CharSequence>> mWordToSuggestions =
- new HashMap<String, List<CharSequence>>();
-
private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
- private class VoiceResults {
- List<String> candidates;
- Map<String, List<CharSequence>> alternatives;
- }
-
public abstract static class WordAlternatives {
protected CharSequence mChosenWord;
@@ -294,9 +245,9 @@ public class LatinIME extends InputMethodService
}
}
- /* package */ UIHandler mHandler = new UIHandler();
+ /* package */ final UIHandler mHandler = new UIHandler();
- /* package */ class UIHandler extends Handler {
+ public class UIHandler extends Handler {
private static final int MSG_UPDATE_SUGGESTIONS = 0;
private static final int MSG_UPDATE_OLD_SUGGESTIONS = 1;
private static final int MSG_UPDATE_SHIFT_STATE = 2;
@@ -316,7 +267,9 @@ public class LatinIME extends InputMethodService
mKeyboardSwitcher.updateShiftState();
break;
case MSG_VOICE_RESULTS:
- handleVoiceResults();
+ mVoiceConnector.handleVoiceResults(mKeyboardSwitcher, preferCapitalization()
+ || (mKeyboardSwitcher.isAlphabetMode()
+ && mKeyboardSwitcher.isShiftedOrShiftLocked()));
break;
case MSG_START_TUTORIAL:
if (mTutorial == null) {
@@ -405,19 +358,7 @@ public class LatinIME extends InputMethodService
// register to receive ringer mode changes for silent mode
IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
registerReceiver(mReceiver, filter);
- if (VOICE_INSTALLED) {
- mVoiceInput = new VoiceInput(this, this);
- mHints = new Hints(this, new Hints.Display() {
- public void showHint(int viewResource) {
- LayoutInflater inflater = (LayoutInflater) getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(viewResource, null);
- setCandidatesView(view);
- setCandidatesViewShown(true);
- mIsShowingHint = true;
- }
- });
- }
+ mVoiceConnector = VoiceIMEConnector.init(this, mHandler);
prefs.registerOnSharedPreferenceChangeListener(this);
}
@@ -508,9 +449,7 @@ public class LatinIME extends InputMethodService
mContactsDictionary.close();
}
unregisterReceiver(mReceiver);
- if (VOICE_INSTALLED && mVoiceInput != null) {
- mVoiceInput.destroy();
- }
+ mVoiceConnector.destroy();
LatinImeLogger.commit();
LatinImeLogger.onDestroy();
super.onDestroy();
@@ -531,15 +470,14 @@ public class LatinIME extends InputMethodService
final int mode = mKeyboardSwitcher.getKeyboardMode();
final EditorInfo attribute = getCurrentInputEditorInfo();
final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
- mKeyboardSwitcher.loadKeyboard(mode, imeOptions, mVoiceKeyEnabled,
- mVoiceButtonOnPrimary);
+ mKeyboardSwitcher.loadKeyboard(mode, imeOptions,
+ mVoiceConnector.isVoiceButtonEnabled(),
+ mVoiceConnector.isVoiceButtonOnPrimary());
}
mConfigurationChanging = true;
super.onConfigurationChanged(conf);
- if (mRecognizing) {
- switchToRecognitionStatusView();
- }
+ mVoiceConnector.onConfigurationChanged(mConfigurationChanging);
mConfigurationChanging = false;
}
@@ -591,16 +529,8 @@ public class LatinIME extends InputMethodService
// Most such things we decide below in the switch statement, but we need to know
// now whether this is a password text field, because we need to know now (before
// the switch statement) whether we want to enable the voice button.
- mPasswordText = false;
int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
- if (isPasswordVariation(variation)) {
- mPasswordText = true;
- }
-
- mAfterVoiceInput = false;
- mImmediatelyAfterVoiceInput = false;
- mShowingVoiceSuggestions = false;
- mVoiceInputHighlighted = false;
+ mVoiceConnector.resetVoiceStates(isPasswordVariation(variation));
mInputTypeNoAutoCorrect = false;
mPredictionOn = false;
mCompletionOn = false;
@@ -679,7 +609,8 @@ public class LatinIME extends InputMethodService
mJustAddedAutoSpace = false;
loadSettings(attribute);
- switcher.loadKeyboard(mode, attribute.imeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary);
+ switcher.loadKeyboard(mode, attribute.imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
+ mVoiceConnector.isVoiceButtonOnPrimary());
switcher.updateShiftState();
setCandidatesViewShownInternal(isCandidateStripVisible(),
@@ -731,14 +662,8 @@ public class LatinIME extends InputMethodService
LatinImeLogger.commit();
onAutoCompletionStateChanged(false);
- if (VOICE_INSTALLED && !mConfigurationChanging) {
- if (mAfterVoiceInput) {
- mVoiceInput.flushAllTextModificationCounters();
- mVoiceInput.logInputEnded();
- }
- mVoiceInput.flushLogs();
- mVoiceInput.cancel();
- }
+ mVoiceConnector.flushVoiceInputLogs(mConfigurationChanging);
+
BaseKeyboardView inputView = mKeyboardSwitcher.getInputView();
if (inputView != null)
inputView.closing();
@@ -760,13 +685,7 @@ public class LatinIME extends InputMethodService
@Override
public void onUpdateExtractedText(int token, ExtractedText text) {
super.onUpdateExtractedText(token, text);
- InputConnection ic = getCurrentInputConnection();
- if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
- if (mHints.showPunctuationHintIfNecessary(ic)) {
- mVoiceInput.logPunctuationHintDisplayed();
- }
- }
- mImmediatelyAfterVoiceInput = false;
+ mVoiceConnector.showPunctuationHintIfNecessary();
}
@Override
@@ -785,14 +704,12 @@ public class LatinIME extends InputMethodService
+ ", ce=" + candidatesEnd);
}
- if (mAfterVoiceInput) {
- mVoiceInput.setCursorPos(newSelEnd);
- mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
- }
+ mVoiceConnector.setCursorAndSelection(newSelEnd, newSelStart);
// If the current selection in the text view changes, we should
// clear whatever candidate text we have.
- if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
+ if ((((mComposing.length() > 0 && mPredicting)
+ || mVoiceConnector.isVoiceInputHighlighted())
&& (newSelStart != candidatesEnd
|| newSelEnd != candidatesEnd)
&& mLastSelectionStart != newSelStart)) {
@@ -804,7 +721,7 @@ public class LatinIME extends InputMethodService
if (ic != null) {
ic.finishComposingText();
}
- mVoiceInputHighlighted = false;
+ mVoiceConnector.setVoiceInputHighlighted(false);
} else if (!mPredicting && !mJustAccepted) {
switch (TextEntryState.getState()) {
case ACCEPTED_DEFAULT:
@@ -829,8 +746,7 @@ public class LatinIME extends InputMethodService
if (isPredictionOn() && !mJustReverted
&& (candidatesStart == candidatesEnd || newSelStart != oldSelStart
|| TextEntryState.isCorrecting())
- && (newSelStart < newSelEnd - 1 || (!mPredicting))
- && !mVoiceInputHighlighted) {
+ && (newSelStart < newSelEnd - 1 || (!mPredicting))) {
if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
mHandler.postUpdateOldSuggestions();
} else {
@@ -888,18 +804,7 @@ public class LatinIME extends InputMethodService
mOptionsDialog.dismiss();
mOptionsDialog = null;
}
- if (!mConfigurationChanging) {
- if (mAfterVoiceInput) mVoiceInput.logInputEnded();
- if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
- mVoiceInput.logKeyboardWarningDialogDismissed();
- mVoiceWarningDialog.dismiss();
- mVoiceWarningDialog = null;
- }
- if (VOICE_INSTALLED & mRecognizing) {
- mVoiceInput.cancel();
- }
- }
- mWordToSuggestions.clear();
+ mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
mWordHistory.clear();
super.hideWindow();
TextEntryState.endSession();
@@ -1019,21 +924,7 @@ public class LatinIME extends InputMethodService
return super.onKeyUp(keyCode, event);
}
- private void revertVoiceInput() {
- InputConnection ic = getCurrentInputConnection();
- if (ic != null) ic.commitText("", 1);
- updateSuggestions();
- mVoiceInputHighlighted = false;
- }
-
- private void commitVoiceInput() {
- InputConnection ic = getCurrentInputConnection();
- if (ic != null) ic.finishComposingText();
- updateSuggestions();
- mVoiceInputHighlighted = false;
- }
-
- private void commitTyped(InputConnection inputConnection) {
+ public void commitTyped(InputConnection inputConnection) {
if (mPredicting) {
mPredicting = false;
if (mComposing.length() > 0) {
@@ -1222,10 +1113,9 @@ public class LatinIME extends InputMethodService
case LatinKeyboardView.KEYCODE_CAPSLOCK:
switcher.toggleCapsLock();
break;
- case LatinKeyboardView.KEYCODE_VOICE:
- if (VOICE_INSTALLED) {
- startListening(false /* was a button press, was not a swipe */);
- }
+ case LatinKeyboardView.KEYCODE_VOICE: /* was a button press, was not a swipe */
+ mVoiceConnector.startListening(false,
+ mKeyboardSwitcher.getInputView().getWindowToken(), mConfigurationChanging);
break;
case KEYCODE_TAB:
handleTab();
@@ -1250,9 +1140,7 @@ public class LatinIME extends InputMethodService
}
public void onText(CharSequence text) {
- if (VOICE_INSTALLED && mVoiceInputHighlighted) {
- commitVoiceInput();
- }
+ mVoiceConnector.commitVoiceInput();
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
abortCorrection(false);
@@ -1274,29 +1162,14 @@ public class LatinIME extends InputMethodService
}
private void handleBackspace() {
- if (VOICE_INSTALLED && mVoiceInputHighlighted) {
- mVoiceInput.incrementTextModificationDeleteCount(
- mVoiceResults.candidates.get(0).toString().length());
- revertVoiceInput();
- return;
- }
+ if (mVoiceConnector.logAndRevertVoiceInput()) return;
boolean deleteChar = false;
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
ic.beginBatchEdit();
- 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);
- }
- }
+ mVoiceConnector.handleBackspace();
if (mPredicting) {
final int length = mComposing.length();
@@ -1377,14 +1250,8 @@ public class LatinIME extends InputMethodService
}
private void handleCharacter(int primaryCode, int[] keyCodes) {
- if (VOICE_INSTALLED && mVoiceInputHighlighted) {
- commitVoiceInput();
- }
+ mVoiceConnector.handleCharacter();
- if (mAfterVoiceInput) {
- // Assume input length is 1. This assumption fails for smiley face insertions.
- mVoiceInput.incrementTextModificationInsertCount(1);
- }
if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
abortCorrection(false);
}
@@ -1441,14 +1308,7 @@ public class LatinIME extends InputMethodService
}
private void handleSeparator(int primaryCode) {
- if (VOICE_INSTALLED && mVoiceInputHighlighted) {
- commitVoiceInput();
- }
-
- if (mAfterVoiceInput){
- // Assume input length is 1. This assumption fails for smiley face insertions.
- mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
- }
+ mVoiceConnector.handleSeparator();
// Should dismiss the "Touch again to save" message when handling separator
if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
@@ -1509,9 +1369,7 @@ public class LatinIME extends InputMethodService
private void handleClose() {
commitTyped(getCurrentInputConnection());
- if (VOICE_INSTALLED & mRecognizing) {
- mVoiceInput.cancel();
- }
+ mVoiceConnector.handleClose();
requestHideSelf(0);
LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
if (inputView != null)
@@ -1557,16 +1415,9 @@ public class LatinIME extends InputMethodService
&& (isPredictionOn() || mCompletionOn || isShowingPunctuationList()));
}
- public void onCancelVoice() {
- if (mRecognizing) {
- switchToKeyboardView();
- }
- }
-
- private void switchToKeyboardView() {
+ public void switchToKeyboardView() {
mHandler.post(new Runnable() {
public void run() {
- mRecognizing = false;
if (mKeyboardSwitcher.getInputView() != null) {
setInputView(mKeyboardSwitcher.getInputView());
}
@@ -1576,175 +1427,18 @@ public class LatinIME extends InputMethodService
}});
}
- private void switchToRecognitionStatusView() {
- final boolean configChanged = mConfigurationChanging;
- mHandler.post(new Runnable() {
- public void run() {
- setCandidatesViewShown(false);
- mRecognizing = true;
- View v = mVoiceInput.getView();
- ViewParent p = v.getParent();
- if (p != null && p instanceof ViewGroup) {
- ((ViewGroup)v.getParent()).removeView(v);
- }
- setInputView(v);
- updateInputViewShown();
- if (configChanged) {
- mVoiceInput.onConfigurationChanged();
- }
- }});
- }
-
- private void startListening(boolean swipe) {
- if (!mHasUsedVoiceInput ||
- (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
- // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
- showVoiceWarningDialog(swipe);
- } else {
- reallyStartListening(swipe);
- }
- }
-
- private void reallyStartListening(boolean swipe) {
- 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(this).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(this).edit();
- editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
- SharedPreferencesCompat.apply(editor);
- mHasUsedVoiceInputUnsupportedLocale = true;
- }
-
- // Clear N-best suggestions
- clearSuggestions();
-
- FieldContext context = makeFieldContext();
- mVoiceInput.startListening(context, swipe);
- switchToRecognitionStatusView();
- }
-
- private void showVoiceWarningDialog(final boolean swipe) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setCancelable(true);
- builder.setIcon(R.drawable.ic_mic_dialog);
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- mVoiceInput.logKeyboardWarningDialogOk();
- reallyStartListening(swipe);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- mVoiceInput.logKeyboardWarningDialogCancel();
- }
- });
-
- if (mLocaleSupportedForVoiceInput) {
- String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" +
- getString(R.string.voice_warning_how_to_turn_off);
- builder.setMessage(message);
- } else {
- String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" +
- getString(R.string.voice_warning_may_not_understand) + "\n\n" +
- getString(R.string.voice_warning_how_to_turn_off);
- builder.setMessage(message);
- }
-
- builder.setTitle(R.string.voice_warning_title);
- mVoiceWarningDialog = builder.create();
-
- Window window = mVoiceWarningDialog.getWindow();
- WindowManager.LayoutParams lp = window.getAttributes();
- lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
- lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
- window.setAttributes(lp);
- window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- mVoiceInput.logKeyboardWarningDialogShown();
- mVoiceWarningDialog.show();
- }
-
- public void onVoiceResults(List<String> candidates,
- Map<String, List<CharSequence>> alternatives) {
- if (!mRecognizing) {
- return;
- }
- mVoiceResults.candidates = candidates;
- mVoiceResults.alternatives = alternatives;
- mHandler.updateVoiceResults();
- }
-
- private void handleVoiceResults() {
- mAfterVoiceInput = true;
- mImmediatelyAfterVoiceInput = true;
-
- InputConnection ic = getCurrentInputConnection();
- if (!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);
- }
- }
-
- vibrate();
- switchToKeyboardView();
-
- final List<CharSequence> nBest = new ArrayList<CharSequence>();
- KeyboardSwitcher switcher = mKeyboardSwitcher;
- boolean capitalizeFirstWord = preferCapitalization()
- || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked());
- 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
-
- commitTyped(ic);
- EditingUtil.appendText(ic, bestResult);
-
- if (ic != null) ic.endBatchEdit();
-
- mVoiceInputHighlighted = true;
- mWordToSuggestions.putAll(mVoiceResults.alternatives);
- }
-
- private void clearSuggestions() {
+ public void clearSuggestions() {
setSuggestions(null, false, false, false);
}
- private void setSuggestions(
+ public void setSuggestions(
List<CharSequence> suggestions,
boolean completions,
boolean typedWordValid,
boolean haveMinimalSuggestion) {
- if (mIsShowingHint) {
+ if (mVoiceConnector.getAndResetIsShowingHint()) {
setCandidatesView(mCandidateViewContainer);
- mIsShowingHint = false;
}
if (mCandidateView != null) {
@@ -1753,11 +1447,12 @@ public class LatinIME extends InputMethodService
}
}
- private void updateSuggestions() {
+ public void updateSuggestions() {
mKeyboardSwitcher.setPreferredLetters(null);
// Check if we have a suggestion engine attached.
- if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
+ if ((mSuggest == null || !isPredictionOn())
+ && !mVoiceConnector.isVoiceInputHighlighted()) {
return;
}
@@ -1846,13 +1541,7 @@ public class LatinIME extends InputMethodService
public void pickSuggestionManually(int index, CharSequence suggestion) {
List<CharSequence> suggestions = mCandidateView.getSuggestions();
- if (mAfterVoiceInput && mShowingVoiceSuggestions) {
- mVoiceInput.flushAllTextModificationCounters();
- // send this intent AFTER logging any prior aggregated edits.
- mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
- mWordSeparators,
- getCurrentInputConnection());
- }
+ mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
final boolean correcting = TextEntryState.isCorrecting();
InputConnection ic = getCurrentInputConnection();
@@ -1932,27 +1621,6 @@ public class LatinIME extends InputMethodService
}
}
- private void rememberReplacedWord(CharSequence suggestion) {
- if (mShowingVoiceSuggestions) {
- // Retain the replaced word in the alternatives array.
- EditingUtil.Range range = new EditingUtil.Range();
- String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
- mWordSeparators, range);
- if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
- wordToBeReplaced = wordToBeReplaced.toLowerCase();
- }
- if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
- List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
- if (suggestions.contains(suggestion)) {
- suggestions.remove(suggestion);
- }
- suggestions.add(wordToBeReplaced);
- mWordToSuggestions.remove(wordToBeReplaced);
- mWordToSuggestions.put(suggestion.toString(), suggestions);
- }
- }
- }
-
/**
* Commits the chosen word to the text field and saves it for later
* retrieval.
@@ -1967,7 +1635,7 @@ public class LatinIME extends InputMethodService
return;
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
- rememberReplacedWord(suggestion);
+ mVoiceConnector.rememberReplacedWord(suggestion, mWordSeparators);
ic.commitText(suggestion, 1);
}
saveWordInHistory(suggestion);
@@ -1982,38 +1650,6 @@ public class LatinIME extends InputMethodService
}
/**
- * 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.
- */
- private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
- // Search for result in spoken word alternatives
- String selectedWord = touching.word.toString().trim();
- if (!mWordToSuggestions.containsKey(selectedWord)) {
- selectedWord = selectedWord.toLowerCase();
- }
- if (mWordToSuggestions.containsKey(selectedWord)) {
- mShowingVoiceSuggestions = true;
- List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
- // If the first letter of touching is capitalized, make all the suggestions
- // start with a capital letter.
- if (Character.isUpperCase(touching.word.charAt(0))) {
- for (int i = 0; i < suggestions.size(); i++) {
- String origSugg = (String) suggestions.get(i);
- String capsSugg = origSugg.toUpperCase().charAt(0)
- + origSugg.subSequence(1, origSugg.length()).toString();
- suggestions.set(i, capsSugg);
- }
- }
- setSuggestions(suggestions, false, true, true);
- setCandidatesViewShown(true);
- return true;
- }
- return false;
- }
-
- /**
* Tries to apply any typed alternatives for the word if we have any cached alternatives,
* otherwise tries to find new corrections and completions for the word.
* @param touching The word that the cursor is touching, with position information
@@ -2061,7 +1697,7 @@ public class LatinIME extends InputMethodService
}
private void setOldSuggestions() {
- mShowingVoiceSuggestions = false;
+ mVoiceConnector.setShowingVoiceSuggestions(false);
if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
return;
}
@@ -2075,7 +1711,8 @@ public class LatinIME extends InputMethodService
if (touching != null && touching.word.length() > 1) {
ic.beginBatchEdit();
- if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) {
+ if (!mVoiceConnector.applyVoiceAlternatives(touching)
+ && !applyTypedAlternatives(touching)) {
abortCorrection(true);
} else {
TextEntryState.selectedForCorrection();
@@ -2221,8 +1858,8 @@ public class LatinIME extends InputMethodService
final int mode = switcher.getKeyboardMode();
final EditorInfo attribute = getCurrentInputEditorInfo();
final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
- switcher.loadKeyboard(mode, imeOptions, mVoiceKeyEnabled,
- mVoiceButtonOnPrimary);
+ switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
+ mVoiceConnector.isVoiceButtonOnPrimary());
initSuggest();
switcher.updateShiftState();
}
@@ -2240,8 +1877,8 @@ public class LatinIME extends InputMethodService
public void swipeRight() {
if (LatinKeyboardView.DEBUG_AUTO_PLAY) {
- ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
- CharSequence text = cm.getText();
+ CharSequence text = ((android.text.ClipboardManager)getSystemService(
+ CLIPBOARD_SERVICE)).getText();
if (!TextUtils.isEmpty(text)) {
mKeyboardSwitcher.getInputView().startPlaying(text.toString());
}
@@ -2284,26 +1921,6 @@ public class LatinIME extends InputMethodService
}
}
- private FieldContext makeFieldContext() {
- return new FieldContext(
- getCurrentInputConnection(),
- getCurrentInputEditorInfo(),
- mSubtypeSwitcher.getInputLocaleStr(),
- mSubtypeSwitcher.getEnabledLanguages());
- }
-
- private boolean fieldCanDoVoice(FieldContext fieldContext) {
- return !mPasswordText
- && mVoiceInput != null
- && !mVoiceInput.isBlacklistedField(fieldContext);
- }
-
- private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
- return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
- && !(attribute != null
- && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions))
- && SpeechRecognizer.isRecognitionAvailable(this);
- }
// receive ringer mode changes to detect silent mode
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -2350,7 +1967,7 @@ public class LatinIME extends InputMethodService
}
}
- private void vibrate() {
+ public void vibrate() {
if (!mVibrateOn) {
return;
}
@@ -2453,24 +2070,13 @@ public class LatinIME extends InputMethodService
mResources.getBoolean(R.bool.default_popup_preview));
mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
- 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());
mAutoCorrectEnabled = isAutoCorrectEnabled(sp);
mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(sp);
loadAndSetAutoCompletionThreshold(sp);
- if (VOICE_INSTALLED) {
- final String voiceMode = sp.getString(PREF_VOICE_MODE,
- getString(R.string.voice_mode_main));
- mVoiceKeyEnabled = !voiceMode.equals(getString(R.string.voice_mode_off))
- && shouldShowVoiceButton(makeFieldContext(), attribute);
- mVoiceButtonOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main));
- }
+ mVoiceConnector.loadSettings(attribute, sp);
+
updateCorrectionMode();
updateAutoTextEnabled();
updateSuggestionVisibility(sp);
diff --git a/java/src/com/android/inputmethod/latin/LatinIMESettings.java b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
index 960c54d7a..8da9c223d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIMESettings.java
+++ b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
@@ -33,6 +33,7 @@ import android.speech.SpeechRecognizer;
import android.text.AutoText;
import android.util.Log;
+import com.android.inputmethod.voice.VoiceIMEConnector;
import com.android.inputmethod.voice.VoiceInputLogger;
public class LatinIMESettings extends PreferenceActivity
@@ -108,7 +109,7 @@ public class LatinIMESettings extends PreferenceActivity
((PreferenceGroup) findPreference(PREDICTION_SETTINGS_KEY))
.removePreference(mQuickFixes);
}
- if (!LatinIME.VOICE_INSTALLED
+ if (!VoiceIMEConnector.VOICE_INSTALLED
|| !SpeechRecognizer.isRecognitionAvailable(this)) {
getPreferenceScreen().removePreference(mVoicePreference);
} else {
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboard.java b/java/src/com/android/inputmethod/latin/LatinKeyboard.java
index 1242818d8..fc62053fe 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboard.java
@@ -127,8 +127,8 @@ public class LatinKeyboard extends BaseKeyboard {
@Override
protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
- XmlResourceParser parser) {
- Key key = new LatinKey(res, parent, x, y, parser);
+ XmlResourceParser parser, KeyStyles keyStyles) {
+ Key key = new LatinKey(res, parent, x, y, parser, keyStyles);
switch (key.codes[0]) {
case LatinIME.KEYCODE_ENTER:
mEnterKey = key;
@@ -619,8 +619,8 @@ public class LatinKeyboard extends BaseKeyboard {
private boolean mShiftLockEnabled;
public LatinKey(Resources res, BaseKeyboard.Row parent, int x, int y,
- XmlResourceParser parser) {
- super(res, parent, x, y, parser);
+ XmlResourceParser parser, KeyStyles keyStyles) {
+ super(res, parent, x, y, parser, keyStyles);
if (popupCharacters != null && popupCharacters.length() == 0) {
// If there is a keyboard with no keys specified in popupCharacters
popupResId = 0;
diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
new file mode 100644
index 000000000..23b6752ef
--- /dev/null
+++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
@@ -0,0 +1,537 @@
+/*
+ * 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.latin.EditingUtil;
+import com.android.inputmethod.latin.Hints;
+import com.android.inputmethod.latin.KeyboardSwitcher;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SharedPreferencesCompat;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.LatinIME.UIHandler;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.speech.SpeechRecognizer;
+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 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";
+ // 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 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 LatinIME mContext;
+ private AlertDialog mVoiceWarningDialog;
+ private VoiceInput mVoiceInput;
+ private final VoiceResults mVoiceResults = new VoiceResults();
+ private Hints mHints;
+ private UIHandler mHandler;
+ // For each word, a list of potential replacements, usually from voice.
+ private final Map<String, List<CharSequence>> mWordToSuggestions =
+ new HashMap<String, List<CharSequence>>();
+
+ public static VoiceIMEConnector init(LatinIME context, UIHandler h) {
+ sInstance.initInternal(context, h);
+ return sInstance;
+ }
+
+ private void initInternal(LatinIME context, UIHandler h) {
+ mContext = context;
+ mHandler = h;
+ if (VOICE_INSTALLED) {
+ mVoiceInput = new VoiceInput(context, this);
+ mHints = new Hints(context, new Hints.Display() {
+ public void showHint(int viewResource) {
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(viewResource, null);
+ mContext.setCandidatesView(view);
+ mContext.setCandidatesViewShown(true);
+ mIsShowingHint = true;
+ }
+ });
+ }
+ }
+
+ private VoiceIMEConnector() {
+ }
+
+ 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, mContext.getCurrentInputConnection());
+ }
+ }
+
+ private void showVoiceWarningDialog(final boolean swipe, IBinder token,
+ final boolean configurationChanging) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setCancelable(true);
+ builder.setIcon(R.drawable.ic_mic_dialog);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mVoiceInput.logKeyboardWarningDialogOk();
+ reallyStartListening(swipe, configurationChanging);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mVoiceInput.logKeyboardWarningDialogCancel();
+ }
+ });
+
+ if (mLocaleSupportedForVoiceInput) {
+ String message = mContext.getString(R.string.voice_warning_may_not_understand)
+ + "\n\n" + mContext.getString(R.string.voice_warning_how_to_turn_off);
+ builder.setMessage(message);
+ } else {
+ String message = mContext.getString(R.string.voice_warning_locale_not_supported)
+ + "\n\n" + mContext.getString(R.string.voice_warning_may_not_understand)
+ + "\n\n" + mContext.getString(R.string.voice_warning_how_to_turn_off);
+ builder.setMessage(message);
+ }
+
+ builder.setTitle(R.string.voice_warning_title);
+ mVoiceWarningDialog = builder.create();
+ Window window = mVoiceWarningDialog.getWindow();
+ 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();
+ }
+
+ public void showPunctuationHintIfNecessary() {
+ InputConnection ic = mContext.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 getAndResetIsShowingHint() {
+ boolean ret = mIsShowingHint;
+ mIsShowingHint = false;
+ return ret;
+ }
+
+ private void revertVoiceInput() {
+ InputConnection ic = mContext.getCurrentInputConnection();
+ if (ic != null) ic.commitText("", 1);
+ mContext.updateSuggestions();
+ mVoiceInputHighlighted = false;
+ }
+
+ public void commitVoiceInput() {
+ if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+ InputConnection ic = mContext.getCurrentInputConnection();
+ if (ic != null) ic.finishComposingText();
+ mContext.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.
+ EditingUtil.Range range = new EditingUtil.Range();
+ String wordToBeReplaced = EditingUtil.getWordAtCursor(
+ mContext.getCurrentInputConnection(), wordSeparators, range);
+ if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
+ wordToBeReplaced = wordToBeReplaced.toLowerCase();
+ }
+ if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
+ List<CharSequence> 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(EditingUtil.SelectedWord touching) {
+ // Search for result in spoken word alternatives
+ String selectedWord = touching.word.toString().trim();
+ if (!mWordToSuggestions.containsKey(selectedWord)) {
+ selectedWord = selectedWord.toLowerCase();
+ }
+ if (mWordToSuggestions.containsKey(selectedWord)) {
+ mShowingVoiceSuggestions = true;
+ List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
+ // If the first letter of touching is capitalized, make all the suggestions
+ // start with a capital letter.
+ if (Character.isUpperCase(touching.word.charAt(0))) {
+ for (int i = 0; i < suggestions.size(); i++) {
+ String origSugg = (String) suggestions.get(i);
+ String capsSugg = origSugg.toUpperCase().charAt(0)
+ + origSugg.subSequence(1, origSugg.length()).toString();
+ suggestions.set(i, capsSugg);
+ }
+ }
+ mContext.setSuggestions(suggestions, false, true, true);
+ mContext.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(KeyboardSwitcher switcher, boolean capitalizeFirstWord) {
+ mAfterVoiceInput = true;
+ mImmediatelyAfterVoiceInput = true;
+
+ InputConnection ic = mContext.getCurrentInputConnection();
+ if (!mContext.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);
+ }
+ }
+ mContext.vibrate();
+ mContext.switchToKeyboardView();
+
+ final List<CharSequence> nBest = new ArrayList<CharSequence>();
+ 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
+ mContext.commitTyped(ic);
+ EditingUtil.appendText(ic, bestResult);
+ if (ic != null) ic.endBatchEdit();
+
+ mVoiceInputHighlighted = true;
+ mWordToSuggestions.putAll(mVoiceResults.alternatives);
+ }
+
+ public void switchToRecognitionStatusView(final boolean configurationChanging) {
+ final boolean configChanged = configurationChanging;
+ mHandler.post(new Runnable() {
+ public void run() {
+ mContext.setCandidatesViewShown(false);
+ mRecognizing = true;
+ View v = mVoiceInput.getView();
+ ViewParent p = v.getParent();
+ if (p != null && p instanceof ViewGroup) {
+ ((ViewGroup)v.getParent()).removeView(v);
+ }
+ mContext.setInputView(v);
+ mContext.updateInputViewShown();
+ if (configChanged) {
+ mVoiceInput.onConfigurationChanged();
+ }
+ }});
+ }
+
+ private void reallyStartListening(boolean swipe, final boolean configurationChanging) {
+ 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(mContext).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(mContext).edit();
+ editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
+ SharedPreferencesCompat.apply(editor);
+ mHasUsedVoiceInputUnsupportedLocale = true;
+ }
+
+ // Clear N-best suggestions
+ mContext.clearSuggestions();
+
+ FieldContext context = makeFieldContext();
+ mVoiceInput.startListening(context, swipe);
+ switchToRecognitionStatusView(configurationChanging);
+ }
+
+ public void startListening(final boolean swipe, IBinder token,
+ final boolean configurationChanging) {
+ if (VOICE_INSTALLED) {
+ if (!mHasUsedVoiceInput ||
+ (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
+ // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
+ showVoiceWarningDialog(swipe, token, configurationChanging);
+ } else {
+ reallyStartListening(swipe, configurationChanging);
+ }
+ }
+ }
+
+
+ private boolean fieldCanDoVoice(FieldContext fieldContext) {
+ return !mPasswordText
+ && mVoiceInput != null
+ && !mVoiceInput.isBlacklistedField(fieldContext);
+ }
+
+ private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
+ return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
+ && !(attribute != null
+ && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions))
+ && SpeechRecognizer.isRecognitionAvailable(mContext);
+ }
+
+ 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,
+ mContext.getString(R.string.voice_mode_main));
+ mVoiceButtonEnabled = !voiceMode.equals(mContext.getString(R.string.voice_mode_off))
+ && shouldShowVoiceButton(makeFieldContext(), attribute);
+ mVoiceButtonOnPrimary = voiceMode.equals(mContext.getString(R.string.voice_mode_main));
+ }
+ }
+
+ public void destroy() {
+ if (VOICE_INSTALLED && mVoiceInput != null) {
+ mVoiceInput.destroy();
+ }
+ }
+
+ public void onConfigurationChanged(boolean configurationChanging) {
+ if (mRecognizing) {
+ switchToRecognitionStatusView(configurationChanging);
+ }
+ }
+ @Override
+ public void onCancelVoice() {
+ if (mRecognizing) {
+ mRecognizing = false;
+ mContext.switchToKeyboardView();
+ }
+ }
+
+ @Override
+ public void onVoiceResults(List<String> candidates,
+ Map<String, List<CharSequence>> alternatives) {
+ if (!mRecognizing) {
+ return;
+ }
+ mVoiceResults.candidates = candidates;
+ mVoiceResults.alternatives = alternatives;
+ mHandler.updateVoiceResults();
+ }
+
+ public FieldContext makeFieldContext() {
+ SubtypeSwitcher switcher = SubtypeSwitcher.getInstance();
+ return new FieldContext(mContext.getCurrentInputConnection(),
+ mContext.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(),
+ switcher.getEnabledLanguages());
+ }
+
+ private class VoiceResults {
+ List<String> candidates;
+ Map<String, List<CharSequence>> alternatives;
+ }
+}