diff options
Diffstat (limited to 'java/src')
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; * >/default< * >/switch< * </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> * >switch< * >case colorScheme="white"< - * >declare-style name="shift-key" parentStyle="modifier-key"< - * >item name="keyIcon"<@drawable/sym_keyboard_shift">/item< - * >/declare-style< + * >key-style styleName="shift-key" parentStyle="modifier-key" + * keyIcon="@drawable/sym_keyboard_shift" + * /< * >/case< * >case colorScheme="black"< - * >declare-style name="shift-key" parentStyle="modifier-key"< - * >item name="keyIcon"<@drawable/sym_bkeyboard_shift">/item< - * >/declare-style< + * >key-style styleName="shift-key" parentStyle="modifier-key" + * keyIcon="@drawable/sym_bkeyboard_shift" + * /< * >/case< * >/switch< * ... - * >Key include-style="shift-key" ... /< + * >Key keyStyle="shift-key" ... /< + * </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; + } +} |