/ime/latinime/src/com/googlecode/eyesfree/inputmethod/latin/LatinIME.java
http://eyes-free.googlecode.com/ · Java · 3197 lines · 2536 code · 347 blank · 314 comment · 755 complexity · d20e6071ec0049b14d3d7f255612dd4e MD5 · raw file
Large files are truncated click here to view the full file
- /*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
- package com.googlecode.eyesfree.inputmethod.latin;
- import com.google.android.marvin.aime.AccessibleInputConnection;
- import android.app.AlertDialog;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.DialogInterface;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.content.SharedPreferences;
- import android.content.res.Configuration;
- import android.content.res.Resources;
- import android.content.res.XmlResourceParser;
- import android.inputmethodservice.InputMethodService;
- import android.inputmethodservice.Keyboard;
- import android.inputmethodservice.Keyboard.Key;
- import android.media.AudioManager;
- import android.os.Build;
- import android.os.Debug;
- import android.os.Handler;
- import android.os.Message;
- import android.os.SystemClock;
- import android.preference.PreferenceActivity;
- import android.preference.PreferenceManager;
- import android.provider.Settings;
- import android.speech.SpeechRecognizer;
- import android.telephony.PhoneStateListener;
- import android.telephony.TelephonyManager;
- import android.text.TextUtils;
- import android.util.DisplayMetrics;
- import android.util.Log;
- import android.util.PrintWriterPrinter;
- import android.util.Printer;
- import android.view.HapticFeedbackConstants;
- import android.view.KeyCharacterMap;
- import android.view.KeyEvent;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewConfiguration;
- import android.view.ViewGroup;
- import android.view.ViewParent;
- import android.view.Window;
- import android.view.WindowManager;
- import android.view.inputmethod.CompletionInfo;
- import android.view.inputmethod.EditorInfo;
- import android.view.inputmethod.ExtractedText;
- import android.view.inputmethod.ExtractedTextRequest;
- import android.view.inputmethod.InputConnection;
- import android.view.inputmethod.InputMethodManager;
- import android.widget.LinearLayout;
- import com.googlecode.eyesfree.inputmethod.FeedbackUtil;
- import com.googlecode.eyesfree.inputmethod.PersistentInputMethodService;
- import com.googlecode.eyesfree.inputmethod.latin.LatinIMEUtil.RingCharBuffer;
- import com.googlecode.eyesfree.inputmethod.latin.tutorial.LatinTutorialDialog;
- import com.googlecode.eyesfree.inputmethod.voice.FieldContext;
- import com.googlecode.eyesfree.inputmethod.voice.SettingsUtil;
- import com.googlecode.eyesfree.inputmethod.voice.VoiceInput;
- import org.xmlpull.v1.XmlPullParserException;
- import java.io.FileDescriptor;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.util.ArrayList;
- import java.util.Collections;
- 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 PersistentInputMethodService implements VoiceInput.UiListener,
- SharedPreferences.OnSharedPreferenceChangeListener {
- 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 = supportsVoiceInput();
- static final boolean ENABLE_VOICE_BUTTON = supportsVoiceInput();
- /**
- * Permission to send {@link Intent} broadcast requests to LatinIME.
- */
- public static final String PERMISSION_REQUEST = "com.googlecode.eyesfree.inputmethod.latin.PERMISSION_REQUEST";
- // Mode change request broadcast constants
- public static final String REQUEST_KEYBOARD_MODE_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.REQUEST_KEYBOARD_MODE_CHANGE";
- public static final String REQUEST_KEYBOARD_MODE_UPDATE = "com.googlecode.eyesfree.inputmethod.latin.REQUEST_KEYBOARD_MODE_UPDATE";
- // Mode change broadcast constants
- public static final String BROADCAST_KEYBOARD_MODE_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.KEYBOARD_MODE_CHANGE";
- public static final String BROADCAST_KEYBOARD_MODE_UPDATE = "com.googlecode.eyesfree.inputmethod.latin.KEYBOARD_MODE_UPDATE";
- public static final String BROADCAST_LEFT_KEYBOARD_AREA = "com.googlecode.eyesfree.inputmethod.latin.LEFT_KEYBOARD_AREA";
- public static final String BROADCAST_ENTERED_KEYBOARD_AREA = "com.googlecode.eyesfree.inputmethod.latin.ENTERED_KEYBOARD_AREA";
- public static final String BROADCAST_UP_OUTSIDE_KEYBOARD = "com.googlecode.eyesfree.inputmethod.latin.TYPED_OUTSIDE_KEYBOARD";
- public static final String BROADCAST_GRANULARITY_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.GRANULARITY_CHANGE";
- public static final String EXTRA_MODE = "mode";
- public static final String EXTRA_GRANULARITY = "granularity";
- // Vibration pattern constants
- private static final long[] VIBRATE_PATTERN_SELECTED = new long[] {
- 0, 30
- };
- private static final long[] VIBRATE_PATTERN_SWIPE = new long[] {
- 0, 20, 0, 20
- };
- private static final long[] VIBRATE_PATTERN_TAP = new long[] {
- 0, 20
- };
- private static final long[] VIBRATE_PATTERN_DOUBLE_TAP = new long[] {
- 0, 20, 0, 20
- };
- private static final long[] VIBRATE_PATTERN_MODE_CHANGE = new long[] {
- 0, 50
- };
- private static final long[] VIBRATE_PATTERN_ENTERED = new long[] {
- 0, 20, 0, 50
- };
- private static final long[] VIBRATE_PATTERN_LEFT = new long[] {
- 0, 50, 0, 20
- };
- private static final long[] VIBRATE_PATTERN_LONG_PRESS = new long[] {
- 0, 20, 40, 20, 40, 20, 40, 20
- };
- // Sound resource constants
- private static final int SOUND_RESOURCE_SELECTED = R.raw.tick;
- private static final int SOUND_RESOURCE_SWIPE = R.raw.tick;
- private static final int SOUND_RESOURCE_TAP = R.raw.tick;
- private static final int SOUND_RESOURCE_DOUBLE_TAP = R.raw.tick;
- private static final int SOUND_RESOURCE_LONG_PRESS = R.raw.tick;
- private static final String PREF_AUTO_SWITCH = "auto_switch";
- private static final String PREF_VIBRATE_ON = "vibrate_on";
- private static final String PREF_SOUND_ON = "sound_on";
- private static final String PREF_POPUP_ON = "popup_on";
- private static final String PREF_AUTO_CAP = "auto_cap";
- private static final String PREF_QUICK_FIXES = "quick_fixes";
- private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
- private static final String PREF_AUTO_COMPLETE = "auto_complete";
- private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
- private static final String PREF_VOICE_MODE = "voice_mode";
- private static final String PREF_LINEAR_NAVIGATION = "linear_navigation";
- // 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";
- // A list of locales which are supported by default for voice input, unless
- // we get a different list from Gservices.
- public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = "en " + "en_US " + "en_GB "
- + "en_AU " + "en_CA " + "en_IE " + "en_IN " + "en_NZ " + "en_SG " + "en_ZA ";
- // 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_HAS_RUN_TUTORIAL = "has_run_tutorial";
- public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
- public static final String PREF_INPUT_LANGUAGE = "input_language";
- private static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
- private static final int MSG_UPDATE_SUGGESTIONS = 0;
- private static final int MSG_UPDATE_SHIFT_STATE = 1;
- private static final int MSG_VOICE_RESULTS = 2;
- private static final int MSG_UPDATE_OLD_SUGGESTIONS = 3;
- // How many continuous deletes at which to start deleting at a higher speed.
- private static final int DELETE_ACCELERATE_AT = 20;
- // Key events coming any faster than this are long-presses.
- private static final int QUICK_PRESS = 200;
- static final int KEYCODE_ENTER = '\n';
- static final int KEYCODE_SPACE = ' ';
- static final int KEYCODE_PERIOD = '.';
- // Contextual menu positions
- private static final int POS_METHOD = 0;
- private static final int POS_SETTINGS = 1;
- private static final int POS_ACCESSIBILITY_SETTINGS = 2;
- // Receive phone call status updates.
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- super.onCallStateChanged(state, incomingNumber);
- switch (state) {
- case TelephonyManager.CALL_STATE_IDLE:
- // This is a no-op if the cache has been invalidated.
- mInCallScreen = false;
- requestKeyboardMode(mCachedForcedMode);
- break;
- case TelephonyManager.CALL_STATE_RINGING:
- // Cache the current forced mode and switch to FORCE_HIDDEN.
- int currentMode = getKeyboardMode();
- requestKeyboardMode(FORCE_HIDDEN);
- mCachedForcedMode = currentMode;
- mInCallScreen = true;
- break;
- case TelephonyManager.CALL_STATE_OFFHOOK:
- // The user picked up the call. Do nothing.
- break;
- }
- }
- };
- // Receive ringer mode changes to detect silent mode and broadcast requests.
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
- updateRingerMode();
- } else if (REQUEST_KEYBOARD_MODE_CHANGE.equals(action)) {
- // Don't make changes unless accessibility is enabled.
- if (isAccessibilityEnabled()) {
- requestKeyboardMode(intent.getIntExtra(EXTRA_MODE, FORCE_DPAD));
- }
- } else if (REQUEST_KEYBOARD_MODE_UPDATE.equals(action)) {
- Intent broadcast = new Intent(BROADCAST_KEYBOARD_MODE_UPDATE);
- broadcast.putExtra(EXTRA_MODE, mForcedMode);
- sendBroadcast(broadcast, PERMISSION_REQUEST);
- }
- }
- };
- // private LatinKeyboardView mInputView;
- private LinearLayout mCandidateViewContainer;
- private CandidateView mCandidateView;
- private Suggest mSuggest;
- private CompletionInfo[] mCompletions;
- private AlertDialog mOptionsDialog;
- private AlertDialog mVoiceWarningDialog;
- /* package */KeyboardSwitcher mKeyboardSwitcher;
- private UserDictionary mUserDictionary;
- private UserBigramDictionary mUserBigramDictionary;
- private ContactsDictionary mContactsDictionary;
- private AutoDictionary mAutoDictionary;
- private Hints mHints;
- private Resources mResources;
- private TelephonyManager mTelephonyManager;
- private AccessibilityUtils mAccessibilityUtils;
- private FeedbackUtil mFeedbackUtil;
- public static final int FORCE_HIDDEN = 0;
- public static final int FORCE_DPAD = 1;
- public static final int FORCE_CACHED = 2;
- private static final int NUM_FORCE_MODES = 3;
- private static final int FORCE_NONE = -1;
- private int mForcedMode = FORCE_DPAD;
- private int mCachedMode = KeyboardSwitcher.MODE_NONE;
- private boolean mCachedPredictionOn = false;
- private int mCachedForcedMode = FORCE_NONE;
- private boolean mInCallScreen = false;
- private String mInputLocale;
- private String mSystemLocale;
- private LanguageSwitcher mLanguageSwitcher;
- private 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 boolean mEnableVoiceButton;
- private CharSequence mBestWord;
- private boolean mPredictionOn;
- private boolean mCompletionOn;
- private boolean mHasDictionary;
- private boolean mAutoSpace;
- private boolean mJustAddedAutoSpace;
- private boolean mAutoCorrectEnabled;
- private boolean mReCorrectionEnabled;
- private boolean mBigramSuggestionEnabled;
- private boolean mAutoCorrectOn;
- // TODO move this state variable outside LatinIME
- private boolean mCapsLock;
- private boolean mPasswordText;
- private boolean mAutoSwitch;
- 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 mShowSuggestions;
- private boolean mIsShowingHint;
- private int mCorrectionMode;
- private boolean mEnableVoice = true;
- private boolean mVoiceOnPrimary;
- private int mOrientation;
- private List<CharSequence> mSuggestPuncList;
- // Keep track of the last selection range to decide if we need to show word
- // alternatives
- private int mLastSelectionStart;
- private int mLastSelectionEnd;
- // Input type is such that we should not auto-correct
- private boolean mInputTypeNoAutoCorrect;
- // Indicates whether the suggestion strip is to be on in landscape
- private boolean mJustAccepted;
- private CharSequence mJustRevertedSeparator;
- private int mDeleteCount;
- private long mLastKeyTime;
- private int mPressedKey = LatinKeyboardView.KEYCODE_UNKNOWN;
- // Modifier keys state
- private ModifierKeyState mShiftKeyState = new ModifierKeyState();
- private ModifierKeyState mSymbolKeyState = new ModifierKeyState();
- private AudioManager mAudioManager;
- // Align sound effect volume on music volume
- private final float FX_VOLUME = -1.0f;
- private boolean mSilentMode;
- /* package */String mWordSeparators;
- private String mSentenceSeparators;
- private String mSuggestPuncs;
- private VoiceInput mVoiceInput;
- private VoiceResults mVoiceResults = new VoiceResults();
- 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 Map<String, List<CharSequence>> mWordToSuggestions = new HashMap<String, List<CharSequence>>();
- private 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;
- public WordAlternatives() {
- // Nothing
- }
- public WordAlternatives(CharSequence chosenWord) {
- mChosenWord = chosenWord;
- }
- @Override
- public int hashCode() {
- return mChosenWord.hashCode();
- }
- public abstract CharSequence getOriginalWord();
- public CharSequence getChosenWord() {
- return mChosenWord;
- }
- public abstract List<CharSequence> getAlternatives();
- }
- public class TypedWordAlternatives extends WordAlternatives {
- private WordComposer word;
- public TypedWordAlternatives() {
- // Nothing
- }
- public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
- super(chosenWord);
- word = wordComposer;
- }
- @Override
- public CharSequence getOriginalWord() {
- return word.getTypedWord();
- }
- @Override
- public List<CharSequence> getAlternatives() {
- return getTypedSuggestions(word);
- }
- }
- /**
- * This class sends a key event when it runs. This is useful for sending delayed key events,
- * such as an UP event for a simulated long-press.
- *
- * @author alanv@google.com (Alan Viverette)
- */
- private class KeyEventRunnable implements Runnable {
- private final int mKeyCode;
- private final int mMetaState;
- private final long mDownTime;
- public KeyEventRunnable(int keyCode, int metaState, long downTime) {
- mKeyCode = keyCode;
- mMetaState = metaState;
- mDownTime = downTime;
- }
- @Override
- public void run() {
- final KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), mDownTime,
- KeyEvent.ACTION_UP, mKeyCode, 0, mMetaState, KeyCharacterMap.BUILT_IN_KEYBOARD,
- 0, KeyEvent.FLAG_SOFT_KEYBOARD);
- // First attempt to send the key to the IME, then to the input
- // connection.
- if (!onKeyUp(mKeyCode, event)) {
- final InputConnection ic = getCurrentInputConnection();
- if (ic != null) {
- ic.sendKeyEvent(event);
- }
- }
- }
- }
- /* package */Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_SUGGESTIONS:
- updateSuggestions();
- break;
- case MSG_UPDATE_OLD_SUGGESTIONS:
- setOldSuggestions();
- break;
- case MSG_UPDATE_SHIFT_STATE:
- updateShiftKeyState(getCurrentInputEditorInfo());
- break;
- case MSG_VOICE_RESULTS:
- handleVoiceResults();
- break;
- }
- }
- };
- /**
- * Returns <code>true<code> if this Android build supports voice input. * *
- * * * @return <code>true<code>if this Android build supports voice input
- */
- public static boolean supportsVoiceInput() {
- return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO);
- }
- @Override
- public void onCreate() {
- super.onCreate();
- LatinImeLogger.init(this);
- // setStatusIcon(R.drawable.ime_qwerty);
- mResources = getResources();
- final Configuration conf = mResources.getConfiguration();
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- mLanguageSwitcher = new LanguageSwitcher(this);
- mLanguageSwitcher.loadLocales(prefs);
- mKeyboardSwitcher = new KeyboardSwitcher(this);
- mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
- mAccessibilityUtils = new AccessibilityUtils(this);
- mFeedbackUtil = new FeedbackUtil(this);
- mSystemLocale = conf.locale.toString();
- mLanguageSwitcher.setSystemLocale(conf.locale);
- String inputLanguage = mLanguageSwitcher.getInputLanguage();
- if (inputLanguage == null) {
- inputLanguage = conf.locale.toString();
- }
- mReCorrectionEnabled = prefs.getBoolean(PREF_RECORRECTION_ENABLED, getResources()
- .getBoolean(R.bool.default_recorrection_enabled));
- LatinIMEUtil.GCUtils.getInstance().reset();
- boolean tryGC = true;
- for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
- try {
- initSuggest(inputLanguage);
- tryGC = false;
- } catch (OutOfMemoryError e) {
- tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e);
- }
- }
- mOrientation = conf.orientation;
- initSuggestPuncList();
- // Register to receive phone status changes. We need to switch to hidden
- // mode when the user is receiving a phone call, then switch back when
- // they're done.
- mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- // Register to receive ringer mode changes for silent mode and keyboard
- // mode requests.
- IntentFilter filter = new IntentFilter();
- filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
- filter.addAction(REQUEST_KEYBOARD_MODE_CHANGE);
- filter.addAction(REQUEST_KEYBOARD_MODE_UPDATE);
- registerReceiver(mReceiver, filter);
- if (VOICE_INSTALLED) {
- mVoiceInput = new VoiceInput(this, this);
- mHints = new Hints(this, new Hints.Display() {
- @Override
- 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;
- }
- });
- }
- prefs.registerOnSharedPreferenceChangeListener(this);
- }
- @Override
- public void onWindowShown() {
- super.onWindowShown();
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- boolean hasRunTutorial = prefs.getBoolean(PREF_HAS_RUN_TUTORIAL, false);
- if (!hasRunTutorial) {
- Intent showDialog = new Intent(this, LatinTutorialDialog.class);
- showDialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(showDialog);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(PREF_HAS_RUN_TUTORIAL, true);
- editor.commit();
- }
- }
- /**
- * Loads a dictionary or multiple separated dictionary
- *
- * @return returns array of dictionary resource ids
- */
- /* package */static int[] getDictionary(Resources res) {
- String packageName = res.getResourcePackageName(R.xml.dictionary);
- XmlResourceParser xrp = res.getXml(R.xml.dictionary);
- ArrayList<Integer> dictionaries = new ArrayList<Integer>();
- try {
- int current = xrp.getEventType();
- while (current != XmlResourceParser.END_DOCUMENT) {
- if (current == XmlResourceParser.START_TAG) {
- String tag = xrp.getName();
- if (tag != null) {
- if (tag.equals("part")) {
- String dictFileName = xrp.getAttributeValue(null, "name");
- dictionaries.add(res.getIdentifier(dictFileName, "raw", packageName));
- }
- }
- }
- xrp.next();
- current = xrp.getEventType();
- }
- } catch (XmlPullParserException e) {
- Log.e(TAG, "Dictionary XML parsing failure");
- } catch (IOException e) {
- Log.e(TAG, "Dictionary XML IOException");
- }
- int count = dictionaries.size();
- int[] dict = new int[count];
- for (int i = 0; i < count; i++) {
- dict[i] = dictionaries.get(i);
- }
- return dict;
- }
- private void initSuggest(String locale) {
- mInputLocale = locale;
- Resources orig = getResources();
- Configuration conf = orig.getConfiguration();
- Locale saveLocale = conf.locale;
- conf.locale = new Locale(locale);
- orig.updateConfiguration(conf, orig.getDisplayMetrics());
- if (mSuggest != null) {
- mSuggest.close();
- }
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
- mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
- int[] dictionaries = getDictionary(orig);
- mSuggest = new Suggest(this, dictionaries);
- updateAutoTextEnabled(saveLocale);
- if (mUserDictionary != null)
- mUserDictionary.close();
- mUserDictionary = new UserDictionary(this, mInputLocale);
- if (mContactsDictionary == null) {
- mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
- }
- if (mAutoDictionary != null) {
- mAutoDictionary.close();
- }
- mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO);
- if (mUserBigramDictionary != null) {
- mUserBigramDictionary.close();
- }
- mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale, Suggest.DIC_USER);
- mSuggest.setUserBigramDictionary(mUserBigramDictionary);
- mSuggest.setUserDictionary(mUserDictionary);
- mSuggest.setContactsDictionary(mContactsDictionary);
- mSuggest.setAutoDictionary(mAutoDictionary);
- updateCorrectionMode();
- mWordSeparators = mResources.getString(R.string.word_separators);
- mSentenceSeparators = mResources.getString(R.string.sentence_separators);
- conf.locale = saveLocale;
- orig.updateConfiguration(conf, orig.getDisplayMetrics());
- }
- @Override
- public void onDestroy() {
- if (mUserDictionary != null) {
- mUserDictionary.close();
- }
- if (mContactsDictionary != null) {
- mContactsDictionary.close();
- }
- // Unregister the phone state listener.
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- // Unregister the broadcast receiver.
- try {
- unregisterReceiver(mReceiver);
- } catch (IllegalArgumentException e) {
- // Ignore the case where broadcast receiver is already unregistered.
- }
- if (VOICE_INSTALLED && mVoiceInput != null) {
- mVoiceInput.destroy();
- }
- LatinImeLogger.commit();
- LatinImeLogger.onDestroy();
- super.onDestroy();
- }
- @Override
- public void onConfigurationChanged(Configuration conf) {
- // If the system locale changes and is different from the saved
- // locale (mSystemLocale), then reload the input locale list from the
- // latin ime settings (shared prefs) and reset the input locale
- // to the first one.
- final String systemLocale = conf.locale.toString();
- if (!TextUtils.equals(systemLocale, mSystemLocale)) {
- mSystemLocale = systemLocale;
- if (mLanguageSwitcher != null) {
- mLanguageSwitcher.loadLocales(PreferenceManager.getDefaultSharedPreferences(this));
- mLanguageSwitcher.setSystemLocale(conf.locale);
- toggleLanguage(true, true);
- } else {
- reloadKeyboards();
- }
- }
- // If orientation changed while predicting, commit the change
- if (conf.orientation != mOrientation) {
- InputConnection ic = getCurrentInputConnection();
- commitTyped(ic);
- if (ic != null)
- ic.finishComposingText(); // For voice input
- mOrientation = conf.orientation;
- reloadKeyboards();
- }
- mConfigurationChanging = true;
- super.onConfigurationChanged(conf);
- if (mRecognizing) {
- switchToRecognitionStatusView();
- }
- mConfigurationChanging = false;
- }
- @Override
- public View onCreateInputView() {
- mKeyboardSwitcher.recreateInputView();
- mKeyboardSwitcher.makeKeyboards(true);
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 0,
- shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo()));
- return mKeyboardSwitcher.getInputView();
- }
- @Override
- public View onCreateCandidatesView() {
- mKeyboardSwitcher.makeKeyboards(true);
- mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate(R.layout.candidates,
- null);
- mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
- mCandidateView.setService(this);
- setCandidatesViewShown(true);
- return mCandidateViewContainer;
- }
- @Override
- public void onStartInputView(EditorInfo attribute, boolean restarting) {
- LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
- // In landscape mode, this method gets called without the input view
- // being created.
- if (inputView == null) {
- return;
- }
- if (mRefreshKeyboardRequired) {
- mRefreshKeyboardRequired = false;
- toggleLanguage(true, true);
- }
- mKeyboardSwitcher.makeKeyboards(false);
- TextEntryState.newSession(this);
- // 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 (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
- || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
- mPasswordText = true;
- }
- mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute);
- final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice;
- mAfterVoiceInput = false;
- mImmediatelyAfterVoiceInput = false;
- mShowingVoiceSuggestions = false;
- mVoiceInputHighlighted = false;
- mInputTypeNoAutoCorrect = false;
- mPredictionOn = false;
- mCompletionOn = false;
- mCompletions = null;
- mCapsLock = false;
- mEnteredText = null;
- mCachedPredictionOn = false;
- mCachedMode = KeyboardSwitcher.MODE_TEXT;
- switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
- case EditorInfo.TYPE_CLASS_NUMBER:
- case EditorInfo.TYPE_CLASS_DATETIME:
- // fall through
- // NOTE: For now, we use the phone keyboard for NUMBER and
- // DATETIME until we get
- // a dedicated number entry keypad.
- // TODO: Use a dedicated number entry keypad here when we get
- // one.
- case EditorInfo.TYPE_CLASS_PHONE:
- mCachedMode = KeyboardSwitcher.MODE_PHONE;
- break;
- case EditorInfo.TYPE_CLASS_TEXT:
- mCachedMode = KeyboardSwitcher.MODE_TEXT;
- mCachedPredictionOn = true;
- // Make sure that passwords are not displayed in candidate view
- if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
- || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
- mCachedPredictionOn = false;
- }
- if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
- || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
- mAutoSpace = false;
- } else {
- mAutoSpace = true;
- }
- if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
- mCachedPredictionOn = false;
- mCachedMode = KeyboardSwitcher.MODE_EMAIL;
- } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
- mCachedPredictionOn = false;
- mCachedMode = KeyboardSwitcher.MODE_URL;
- } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
- mCachedMode = KeyboardSwitcher.MODE_IM;
- } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
- mCachedPredictionOn = false;
- } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
- mCachedMode = KeyboardSwitcher.MODE_WEB;
- // If it's a browser edit field and auto correct is not ON
- // explicitly, then
- // disable auto correction, but keep suggestions on.
- if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
- mInputTypeNoAutoCorrect = true;
- }
- }
- // If NO_SUGGESTIONS is set, don't do prediction.
- if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
- mCachedPredictionOn = false;
- mInputTypeNoAutoCorrect = true;
- }
- // If it's not multiline and the autoCorrect flag is not set,
- // then don't correct
- if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0
- && (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
- mInputTypeNoAutoCorrect = true;
- }
- if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
- mCachedPredictionOn = false;
- mCompletionOn = isFullscreenMode();
- }
- break;
- }
- int keyboardMode = mCachedMode;
- boolean predictionOn = mCachedPredictionOn;
- if (isAccessibilityEnabled()) {
- // If auto-switch is on AND we're not already auto-switched AND we're
- // focused on an editable field THEN cache the current mode and force
- // typing mode.
- if (mAutoSwitch && mCachedForcedMode == FORCE_NONE
- && (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) != EditorInfo.TYPE_NULL) {
- mCachedForcedMode = mForcedMode;
- mForcedMode = FORCE_CACHED;
- mAccessibilityUtils.speakDescription(getString(R.string.spoken_mode_switch_keyboard));
- } else if (mForcedMode == FORCE_HIDDEN) {
- keyboardMode = KeyboardSwitcher.MODE_HIDDEN;
- predictionOn = false;
- } else if (mForcedMode == FORCE_DPAD) {
- keyboardMode = KeyboardSwitcher.MODE_DPAD;
- predictionOn = false;
- }
- }
- mPredictionOn = predictionOn;
- mKeyboardSwitcher.setKeyboardMode(keyboardMode, attribute.imeOptions, enableVoiceButton);
- inputView.closing();
- mComposing.setLength(0);
- mPredicting = false;
- mDeleteCount = 0;
- mJustAddedAutoSpace = false;
- loadSettings();
- updateShiftKeyState(attribute);
- setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn, false /* needsInputViewShown */);
- updateSuggestions();
- // If the dictionary is not big enough, don't auto correct
- mHasDictionary = mSuggest.hasMainDictionary();
- updateCorrectionMode();
- inputView.setPreviewEnabled(mPopupOn);
- inputView.setProximityCorrectionEnabled(true);
- inputView.setAccessibilityEnabled(isAccessibilityEnabled());
- mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions);
- // If we just entered a text field, maybe it has some old text that
- // requires correction
- checkReCorrectionOnStart();
- if (TRACE)
- Debug.startMethodTracing("/data/trace/latinime");
- }
- private void checkReCorrectionOnStart() {
- if (mReCorrectionEnabled && isPredictionOn()) {
- // First get the cursor position. This is required by
- // setOldSuggestions(), so that
- // it can pass the correct range to setComposingRegion(). At this
- // point, we don't
- // have valid values for mLastSelectionStart/Stop because
- // onUpdateSelection() has
- // not been called yet.
- InputConnection ic = getCurrentInputConnection();
- if (ic == null)
- return;
- ExtractedTextRequest etr = new ExtractedTextRequest();
- etr.token = 0; // anything is fine here
- ExtractedText et = ic.getExtractedText(etr, 0);
- if (et == null)
- return;
- mLastSelectionStart = et.startOffset + et.selectionStart;
- mLastSelectionEnd = et.startOffset + et.selectionEnd;
- // Then look for possible corrections in a delayed fashion
- if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) {
- postUpdateOldSuggestions();
- }
- }
- }
- @Override
- public void onFinishInput() {
- super.onFinishInput();
- LatinImeLogger.commit();
- onAutoCompletionStateChanged(false);
- if (VOICE_INSTALLED && !mConfigurationChanging) {
- if (mAfterVoiceInput) {
- mVoiceInput.flushAllTextModificationCounters();
- mVoiceInput.logInputEnded();
- }
- mVoiceInput.flushLogs();
- mVoiceInput.cancel();
- }
- if (mKeyboardSwitcher.getInputView() != null) {
- mKeyboardSwitcher.getInputView().closing();
- }
- if (mAutoDictionary != null)
- mAutoDictionary.flushPendingWrites();
- if (mUserBigramDictionary != null)
- mUserBigramDictionary.flushPendingWrites();
- }
- @Override
- public void onFinishInputView(boolean finishingInput) {
- super.onFinishInputView(finishingInput);
- // Remove pending messages related to update suggestions
- mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
- mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
- // If auto-switch is on and we're in auto-switched mode, then restore
- // the previous forced mode.
- if (mAutoSwitch && mCachedForcedMode != FORCE_NONE) {
- requestKeyboardMode(mCachedForcedMode);
- }
- }
- @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;
- }
- @Override
- public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
- int candidatesStart, int candidatesEnd) {
- super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
- candidatesEnd);
- if (DEBUG) {
- Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd + ", nss="
- + newSelStart + ", nse=" + newSelEnd + ", cs=" + candidatesStart + ", ce="
- + candidatesEnd);
- }
- if (mAfterVoiceInput) {
- mVoiceInput.setCursorPos(newSelEnd);
- mVoiceInput.setSelectionSpan(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)
- && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) {
- mComposing.setLength(0);
- mPredicting = false;
- postUpdateSuggestions();
- TextEntryState.reset();
- InputConnection ic = getCurrentInputConnection();
- if (ic != null) {
- ic.finishComposingText();
- }
- mVoiceInputHighlighted = false;
- } else if (!mPredicting && !mJustAccepted) {
- switch (TextEntryState.getState()) {
- case ACCEPTED_DEFAULT:
- TextEntryState.reset();
- //$FALL-THROUGH$
- case SPACE_AFTER_PICKED:
- mJustAddedAutoSpace = false; // The user moved the cursor.
- break;
- }
- }
- mJustAccepted = false;
- postUpdateShiftKeyState();
- // Make a note of the cursor position
- mLastSelectionStart = newSelStart;
- mLastSelectionEnd = newSelEnd;
- if (mReCorrectionEnabled) {
- // Don't look for corrections if the keyboard is not visible
- if (mKeyboardSwitcher != null && mKeyboardSwitcher.getInputView() != null
- && mKeyboardSwitcher.getInputView().isShown()) {
- // Check if we should go in or out of correction mode.
- if (isPredictionOn()
- && mJustRevertedSeparator == null
- && (candidatesStart == candidatesEnd || newSelStart != oldSelStart || TextEntryState
- .isCorrecting()) && (newSelStart < newSelEnd - 1 || (!mPredicting))
- && !mVoiceInputHighlighted) {
- if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
- postUpdateOldSuggestions();
- } else {
- abortCorrection(false);
- // Show the punctuation suggestions list if the current
- // one is not
- // and if not showing "Touch again to save".
- if (mCandidateView != null
- && !mSuggestPuncList.equals(mCandidateView.getSuggestions())
- && !mCandidateView.isShowingAddToDictionaryHint()) {
- setNextSuggestions();
- }
- }
- }
- }
- }
- }
- /**
- * This is called when the user has clicked on the extracted text view, when running in
- * fullscreen mode. The default implementation hides the candidates view when this happens, but
- * only if the extracted text editor has a vertical scroll bar because its text doesn't fit.
- * Here we override the behavior due to the possibility that a re-correction could cause the
- * candidate strip to disappear and re-appear.
- */
- @Override
- public void onExtractedTextClicked() {
- if (mReCorrectionEnabled && isPredictionOn())
- return;
- super.onExtractedTextClicked();
- }
- /**
- * This is called when the user has performed a cursor movement in the extracted text view, when
- * it is running in fullscreen mode. The default implementation hides the candidates view when a
- * vertical movement happens, but only if the extracted text editor has a vertical scroll bar
- * because its text doesn't fit. Here we override the behavior due to the possibility that a
- * re-correction could cause the candidate strip to disappear and re-appear.
- */
- @Override
- public void onExtractedCursorMovement(int dx, int dy) {
- if (mReCorrectionEnabled && isPredictionOn())
- return;
- super.onExtractedCursorMovement(dx, dy);
- }
- @Override
- public void hideWindow() {
- LatinImeLogger.commit();
- onAutoCompletionStateChanged(false);
- if (TRACE)
- Debug.stopMethodTracing();
- if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
- 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();
- mWordHistory.clear();
- super.hideWindow();
- TextEntryState.endSession();
- }
- @Override
- public void onDisplayCompletions(CompletionInfo[] completions) {
- if (DEBUG) {
- Log.i("foo", "Received completions:");
- for (int i = 0; i < (completions != null ? completions.length : 0); i++) {
- Log.i("foo", " #" + i + ": " + completions[i]);
- }
- }
- if (mCompletionOn) {
- mCompletions = completions;
- if (completions == null) {
- clearSuggestions();
- return;
- }
- List<CharSequence> stringList = new ArrayList<CharSequence>();
- for (int i = 0; i < (completions != null ? completions.length : 0); i++) {
- CompletionInfo ci = completions[i];
- if (ci != null)
- stringList.add(ci.getText());
- }
- // When in fullscreen mode, show completions generated by the
- // application
- setSuggestions(stringList, true, true, true);
- mBestWord = null;
- setCandidatesViewShown(true);
- }
- }
- private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
- // TODO: Remove this if we support candidates with hard keyboard
- if (onEvaluateInputViewShown()) {
- super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null
- && (needsInputViewShown ? mKeyboardSwitcher.getInputView().isShown() : true));
- }
- }
- @Override
- public void setCandidatesViewShown(boolean shown) {
- setCandidatesViewShownInternal(shown, true /* needsInputViewShown */);
- }
- @Override
- public void onComputeInsets(InputMethodService.Insets outInsets) {
- super.onComputeInsets(outInsets);
- if (!isFullscreenMode()) {
- outInsets.contentTopInsets = outInsets.visibleTopInsets;
- }
- }
- @Override
- public boolean onEvaluateFullscreenMode() {
- DisplayMetrics dm = getResources().getDisplayMetrics();
- float displayHeight = dm.heightPixels;
- // If the display is more than X inches high, don't go to fullscreen
- // mode
- float dimen = getResources().getDimension(R.dimen.max_height_for_fullscreen);
- if (displayHeight > dimen) {
- return false;
- } else {
- return super.onEvaluateFullscreenMode();
- }
- }
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_BACK:
- if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) {
- if (mKeyboardSwitcher.getInputView().handleBack()) {
- return true;
- }
- }
- break;
- }
- return super.onKeyDown(keyCode, event);
- }
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- LatinKeyboardView view = mKeyboardSwitcher.getInputView();
- if (view != null && view.isShifted()) {
- // Force this key to be shifted.
- event = new KeyEvent(event.getDownTime(), event.getEventTime(),
- event.getAction(), event.getKeyCode(), event.getRepeatCount(),
- event.getMetaState() | KeyEvent.META_SHIFT_ON
- | KeyEvent.META_SHIFT_LEFT_ON, event.getDeviceId(),
- event.getScanCode(), event.getFlags());
- }
- break;
- }
- return super.onKeyUp(keyCode, event);
- }
- @Override
- public boolean onKeyLongPress(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_VOLUME_UP:
- if (event.getRepeatCount() == 1) {
- cycleForcedType(1);
- }
- return true;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- if (event.getRepeatCount() == 1) {
- cycleForcedType(-1);
- }
- return true;
- }
- return super.onKeyLongPress(keyCode, event);
- }
- private void cycleForcedType(int di…