PageRenderTime 88ms CodeModel.GetById 9ms app.highlight 64ms RepoModel.GetById 1ms app.codeStats 1ms

/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 files are truncated, but you can click here to view the full file

   1/*
   2 * Copyright (C) 2008 The Android Open Source Project
   3 *
   4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
   5 * use this file except in compliance with the License. You may obtain a copy of
   6 * the License at
   7 *
   8 * http://www.apache.org/licenses/LICENSE-2.0
   9 *
  10 * Unless required by applicable law or agreed to in writing, software
  11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13 * License for the specific language governing permissions and limitations under
  14 * the License.
  15 */
  16
  17package com.googlecode.eyesfree.inputmethod.latin;
  18
  19import com.google.android.marvin.aime.AccessibleInputConnection;
  20
  21import android.app.AlertDialog;
  22import android.content.BroadcastReceiver;
  23import android.content.Context;
  24import android.content.DialogInterface;
  25import android.content.Intent;
  26import android.content.IntentFilter;
  27import android.content.SharedPreferences;
  28import android.content.res.Configuration;
  29import android.content.res.Resources;
  30import android.content.res.XmlResourceParser;
  31import android.inputmethodservice.InputMethodService;
  32import android.inputmethodservice.Keyboard;
  33import android.inputmethodservice.Keyboard.Key;
  34import android.media.AudioManager;
  35import android.os.Build;
  36import android.os.Debug;
  37import android.os.Handler;
  38import android.os.Message;
  39import android.os.SystemClock;
  40import android.preference.PreferenceActivity;
  41import android.preference.PreferenceManager;
  42import android.provider.Settings;
  43import android.speech.SpeechRecognizer;
  44import android.telephony.PhoneStateListener;
  45import android.telephony.TelephonyManager;
  46import android.text.TextUtils;
  47import android.util.DisplayMetrics;
  48import android.util.Log;
  49import android.util.PrintWriterPrinter;
  50import android.util.Printer;
  51import android.view.HapticFeedbackConstants;
  52import android.view.KeyCharacterMap;
  53import android.view.KeyEvent;
  54import android.view.LayoutInflater;
  55import android.view.View;
  56import android.view.ViewConfiguration;
  57import android.view.ViewGroup;
  58import android.view.ViewParent;
  59import android.view.Window;
  60import android.view.WindowManager;
  61import android.view.inputmethod.CompletionInfo;
  62import android.view.inputmethod.EditorInfo;
  63import android.view.inputmethod.ExtractedText;
  64import android.view.inputmethod.ExtractedTextRequest;
  65import android.view.inputmethod.InputConnection;
  66import android.view.inputmethod.InputMethodManager;
  67import android.widget.LinearLayout;
  68
  69import com.googlecode.eyesfree.inputmethod.FeedbackUtil;
  70import com.googlecode.eyesfree.inputmethod.PersistentInputMethodService;
  71import com.googlecode.eyesfree.inputmethod.latin.LatinIMEUtil.RingCharBuffer;
  72import com.googlecode.eyesfree.inputmethod.latin.tutorial.LatinTutorialDialog;
  73import com.googlecode.eyesfree.inputmethod.voice.FieldContext;
  74import com.googlecode.eyesfree.inputmethod.voice.SettingsUtil;
  75import com.googlecode.eyesfree.inputmethod.voice.VoiceInput;
  76
  77import org.xmlpull.v1.XmlPullParserException;
  78
  79import java.io.FileDescriptor;
  80import java.io.IOException;
  81import java.io.PrintWriter;
  82import java.util.ArrayList;
  83import java.util.Collections;
  84import java.util.HashMap;
  85import java.util.List;
  86import java.util.Locale;
  87import java.util.Map;
  88
  89/**
  90 * Input method implementation for Qwerty'ish keyboard.
  91 */
  92public class LatinIME extends PersistentInputMethodService implements VoiceInput.UiListener,
  93        SharedPreferences.OnSharedPreferenceChangeListener {
  94    private static final String TAG = "LatinIME";
  95    private static final boolean PERF_DEBUG = false;
  96    static final boolean DEBUG = false;
  97    static final boolean TRACE = false;
  98    static final boolean VOICE_INSTALLED = supportsVoiceInput();
  99    static final boolean ENABLE_VOICE_BUTTON = supportsVoiceInput();
 100
 101    /**
 102     * Permission to send {@link Intent} broadcast requests to LatinIME.
 103     */
 104    public static final String PERMISSION_REQUEST = "com.googlecode.eyesfree.inputmethod.latin.PERMISSION_REQUEST";
 105
 106    // Mode change request broadcast constants
 107    public static final String REQUEST_KEYBOARD_MODE_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.REQUEST_KEYBOARD_MODE_CHANGE";
 108    public static final String REQUEST_KEYBOARD_MODE_UPDATE = "com.googlecode.eyesfree.inputmethod.latin.REQUEST_KEYBOARD_MODE_UPDATE";
 109
 110    // Mode change broadcast constants
 111    public static final String BROADCAST_KEYBOARD_MODE_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.KEYBOARD_MODE_CHANGE";
 112    public static final String BROADCAST_KEYBOARD_MODE_UPDATE = "com.googlecode.eyesfree.inputmethod.latin.KEYBOARD_MODE_UPDATE";
 113    public static final String BROADCAST_LEFT_KEYBOARD_AREA = "com.googlecode.eyesfree.inputmethod.latin.LEFT_KEYBOARD_AREA";
 114    public static final String BROADCAST_ENTERED_KEYBOARD_AREA = "com.googlecode.eyesfree.inputmethod.latin.ENTERED_KEYBOARD_AREA";
 115    public static final String BROADCAST_UP_OUTSIDE_KEYBOARD = "com.googlecode.eyesfree.inputmethod.latin.TYPED_OUTSIDE_KEYBOARD";
 116    public static final String BROADCAST_GRANULARITY_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.GRANULARITY_CHANGE";
 117
 118    public static final String EXTRA_MODE = "mode";
 119    public static final String EXTRA_GRANULARITY = "granularity";
 120
 121    // Vibration pattern constants
 122    private static final long[] VIBRATE_PATTERN_SELECTED = new long[] {
 123            0, 30
 124    };
 125    private static final long[] VIBRATE_PATTERN_SWIPE = new long[] {
 126            0, 20, 0, 20
 127    };
 128    private static final long[] VIBRATE_PATTERN_TAP = new long[] {
 129            0, 20
 130    };
 131    private static final long[] VIBRATE_PATTERN_DOUBLE_TAP = new long[] {
 132            0, 20, 0, 20
 133    };
 134    private static final long[] VIBRATE_PATTERN_MODE_CHANGE = new long[] {
 135            0, 50
 136    };
 137    private static final long[] VIBRATE_PATTERN_ENTERED = new long[] {
 138            0, 20, 0, 50
 139    };
 140    private static final long[] VIBRATE_PATTERN_LEFT = new long[] {
 141            0, 50, 0, 20
 142    };
 143    private static final long[] VIBRATE_PATTERN_LONG_PRESS = new long[] {
 144            0, 20, 40, 20, 40, 20, 40, 20
 145    };
 146
 147    // Sound resource constants
 148    private static final int SOUND_RESOURCE_SELECTED = R.raw.tick;
 149    private static final int SOUND_RESOURCE_SWIPE = R.raw.tick;
 150    private static final int SOUND_RESOURCE_TAP = R.raw.tick;
 151    private static final int SOUND_RESOURCE_DOUBLE_TAP = R.raw.tick;
 152    private static final int SOUND_RESOURCE_LONG_PRESS = R.raw.tick;
 153
 154    private static final String PREF_AUTO_SWITCH = "auto_switch";
 155    private static final String PREF_VIBRATE_ON = "vibrate_on";
 156    private static final String PREF_SOUND_ON = "sound_on";
 157    private static final String PREF_POPUP_ON = "popup_on";
 158    private static final String PREF_AUTO_CAP = "auto_cap";
 159    private static final String PREF_QUICK_FIXES = "quick_fixes";
 160    private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
 161    private static final String PREF_AUTO_COMPLETE = "auto_complete";
 162    private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
 163    private static final String PREF_VOICE_MODE = "voice_mode";
 164    private static final String PREF_LINEAR_NAVIGATION = "linear_navigation";
 165
 166    // Whether or not the user has used voice input before (and thus, whether to
 167    // show the first-run warning dialog or not).
 168    private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
 169
 170    // Whether or not the user has used voice input from an unsupported locale
 171    // UI before. For example, the user has a Chinese UI but activates voice
 172    // input.
 173    private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = "has_used_voice_input_unsupported_locale";
 174
 175    // A list of locales which are supported by default for voice input, unless
 176    // we get a different list from Gservices.
 177    public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = "en " + "en_US " + "en_GB "
 178            + "en_AU " + "en_CA " + "en_IE " + "en_IN " + "en_NZ " + "en_SG " + "en_ZA ";
 179
 180    // The private IME option used to indicate that no microphone should be
 181    // shown for a given text field. For instance this is specified by the
 182    // search dialog
 183    // when the dialog is already showing a voice search button.
 184    private static final String IME_OPTION_NO_MICROPHONE = "nm";
 185
 186    public static final String PREF_HAS_RUN_TUTORIAL = "has_run_tutorial";
 187    public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
 188    public static final String PREF_INPUT_LANGUAGE = "input_language";
 189    private static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
 190
 191    private static final int MSG_UPDATE_SUGGESTIONS = 0;
 192    private static final int MSG_UPDATE_SHIFT_STATE = 1;
 193    private static final int MSG_VOICE_RESULTS = 2;
 194    private static final int MSG_UPDATE_OLD_SUGGESTIONS = 3;
 195
 196    // How many continuous deletes at which to start deleting at a higher speed.
 197    private static final int DELETE_ACCELERATE_AT = 20;
 198    // Key events coming any faster than this are long-presses.
 199    private static final int QUICK_PRESS = 200;
 200
 201    static final int KEYCODE_ENTER = '\n';
 202    static final int KEYCODE_SPACE = ' ';
 203    static final int KEYCODE_PERIOD = '.';
 204
 205    // Contextual menu positions
 206    private static final int POS_METHOD = 0;
 207    private static final int POS_SETTINGS = 1;
 208    private static final int POS_ACCESSIBILITY_SETTINGS = 2;
 209
 210    // Receive phone call status updates.
 211    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
 212        @Override
 213        public void onCallStateChanged(int state, String incomingNumber) {
 214            super.onCallStateChanged(state, incomingNumber);
 215
 216            switch (state) {
 217                case TelephonyManager.CALL_STATE_IDLE:
 218                    // This is a no-op if the cache has been invalidated.
 219                    mInCallScreen = false;
 220                    requestKeyboardMode(mCachedForcedMode);
 221                    break;
 222                case TelephonyManager.CALL_STATE_RINGING:
 223                    // Cache the current forced mode and switch to FORCE_HIDDEN.
 224                    int currentMode = getKeyboardMode();
 225                    requestKeyboardMode(FORCE_HIDDEN);
 226                    mCachedForcedMode = currentMode;
 227                    mInCallScreen = true;
 228                    break;
 229                case TelephonyManager.CALL_STATE_OFFHOOK:
 230                    // The user picked up the call. Do nothing.
 231                    break;
 232            }
 233        }
 234    };
 235
 236    // Receive ringer mode changes to detect silent mode and broadcast requests.
 237    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
 238        @Override
 239        public void onReceive(Context context, Intent intent) {
 240            String action = intent.getAction();
 241
 242            if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
 243                updateRingerMode();
 244            } else if (REQUEST_KEYBOARD_MODE_CHANGE.equals(action)) {
 245                // Don't make changes unless accessibility is enabled.
 246                if (isAccessibilityEnabled()) {
 247                    requestKeyboardMode(intent.getIntExtra(EXTRA_MODE, FORCE_DPAD));
 248                }
 249            } else if (REQUEST_KEYBOARD_MODE_UPDATE.equals(action)) {
 250                Intent broadcast = new Intent(BROADCAST_KEYBOARD_MODE_UPDATE);
 251                broadcast.putExtra(EXTRA_MODE, mForcedMode);
 252                sendBroadcast(broadcast, PERMISSION_REQUEST);
 253            }
 254        }
 255    };
 256
 257    // private LatinKeyboardView mInputView;
 258    private LinearLayout mCandidateViewContainer;
 259    private CandidateView mCandidateView;
 260    private Suggest mSuggest;
 261    private CompletionInfo[] mCompletions;
 262
 263    private AlertDialog mOptionsDialog;
 264    private AlertDialog mVoiceWarningDialog;
 265
 266    /* package */KeyboardSwitcher mKeyboardSwitcher;
 267
 268    private UserDictionary mUserDictionary;
 269    private UserBigramDictionary mUserBigramDictionary;
 270    private ContactsDictionary mContactsDictionary;
 271    private AutoDictionary mAutoDictionary;
 272
 273    private Hints mHints;
 274
 275    private Resources mResources;
 276    private TelephonyManager mTelephonyManager;
 277    private AccessibilityUtils mAccessibilityUtils;
 278    private FeedbackUtil mFeedbackUtil;
 279
 280    public static final int FORCE_HIDDEN = 0;
 281    public static final int FORCE_DPAD = 1;
 282    public static final int FORCE_CACHED = 2;
 283    private static final int NUM_FORCE_MODES = 3;
 284    private static final int FORCE_NONE = -1;
 285
 286    private int mForcedMode = FORCE_DPAD;
 287    private int mCachedMode = KeyboardSwitcher.MODE_NONE;
 288    private boolean mCachedPredictionOn = false;
 289    private int mCachedForcedMode = FORCE_NONE;
 290    private boolean mInCallScreen = false;
 291
 292    private String mInputLocale;
 293    private String mSystemLocale;
 294    private LanguageSwitcher mLanguageSwitcher;
 295
 296    private StringBuilder mComposing = new StringBuilder();
 297    private WordComposer mWord = new WordComposer();
 298    private int mCommittedLength;
 299    private boolean mPredicting;
 300    private boolean mRecognizing;
 301    private boolean mAfterVoiceInput;
 302    private boolean mImmediatelyAfterVoiceInput;
 303    private boolean mShowingVoiceSuggestions;
 304    private boolean mVoiceInputHighlighted;
 305    private boolean mEnableVoiceButton;
 306    private CharSequence mBestWord;
 307    private boolean mPredictionOn;
 308    private boolean mCompletionOn;
 309    private boolean mHasDictionary;
 310    private boolean mAutoSpace;
 311    private boolean mJustAddedAutoSpace;
 312    private boolean mAutoCorrectEnabled;
 313    private boolean mReCorrectionEnabled;
 314    private boolean mBigramSuggestionEnabled;
 315    private boolean mAutoCorrectOn;
 316    // TODO move this state variable outside LatinIME
 317    private boolean mCapsLock;
 318    private boolean mPasswordText;
 319    private boolean mAutoSwitch;
 320    private boolean mVibrateOn;
 321    private boolean mSoundOn;
 322    private boolean mPopupOn;
 323    private boolean mAutoCap;
 324    private boolean mQuickFixes;
 325    private boolean mHasUsedVoiceInput;
 326    private boolean mHasUsedVoiceInputUnsupportedLocale;
 327    private boolean mLocaleSupportedForVoiceInput;
 328    private boolean mShowSuggestions;
 329    private boolean mIsShowingHint;
 330    private int mCorrectionMode;
 331    private boolean mEnableVoice = true;
 332    private boolean mVoiceOnPrimary;
 333    private int mOrientation;
 334    private List<CharSequence> mSuggestPuncList;
 335    // Keep track of the last selection range to decide if we need to show word
 336    // alternatives
 337    private int mLastSelectionStart;
 338    private int mLastSelectionEnd;
 339
 340    // Input type is such that we should not auto-correct
 341    private boolean mInputTypeNoAutoCorrect;
 342
 343    // Indicates whether the suggestion strip is to be on in landscape
 344    private boolean mJustAccepted;
 345    private CharSequence mJustRevertedSeparator;
 346    private int mDeleteCount;
 347    private long mLastKeyTime;
 348    private int mPressedKey = LatinKeyboardView.KEYCODE_UNKNOWN;
 349
 350    // Modifier keys state
 351    private ModifierKeyState mShiftKeyState = new ModifierKeyState();
 352    private ModifierKeyState mSymbolKeyState = new ModifierKeyState();
 353
 354    private AudioManager mAudioManager;
 355    // Align sound effect volume on music volume
 356    private final float FX_VOLUME = -1.0f;
 357    private boolean mSilentMode;
 358
 359    /* package */String mWordSeparators;
 360    private String mSentenceSeparators;
 361    private String mSuggestPuncs;
 362    private VoiceInput mVoiceInput;
 363    private VoiceResults mVoiceResults = new VoiceResults();
 364    private boolean mConfigurationChanging;
 365
 366    // Keeps track of most recently inserted text (multi-character key) for
 367    // reverting
 368    private CharSequence mEnteredText;
 369    private boolean mRefreshKeyboardRequired;
 370
 371    // For each word, a list of potential replacements, usually from voice.
 372    private Map<String, List<CharSequence>> mWordToSuggestions = new HashMap<String, List<CharSequence>>();
 373
 374    private ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
 375
 376    private class VoiceResults {
 377        List<String> candidates;
 378        Map<String, List<CharSequence>> alternatives;
 379    }
 380
 381    public abstract static class WordAlternatives {
 382        protected CharSequence mChosenWord;
 383
 384        public WordAlternatives() {
 385            // Nothing
 386        }
 387
 388        public WordAlternatives(CharSequence chosenWord) {
 389            mChosenWord = chosenWord;
 390        }
 391
 392        @Override
 393        public int hashCode() {
 394            return mChosenWord.hashCode();
 395        }
 396
 397        public abstract CharSequence getOriginalWord();
 398
 399        public CharSequence getChosenWord() {
 400            return mChosenWord;
 401        }
 402
 403        public abstract List<CharSequence> getAlternatives();
 404    }
 405
 406    public class TypedWordAlternatives extends WordAlternatives {
 407        private WordComposer word;
 408
 409        public TypedWordAlternatives() {
 410            // Nothing
 411        }
 412
 413        public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
 414            super(chosenWord);
 415            word = wordComposer;
 416        }
 417
 418        @Override
 419        public CharSequence getOriginalWord() {
 420            return word.getTypedWord();
 421        }
 422
 423        @Override
 424        public List<CharSequence> getAlternatives() {
 425            return getTypedSuggestions(word);
 426        }
 427    }
 428
 429    /**
 430     * This class sends a key event when it runs. This is useful for sending delayed key events,
 431     * such as an UP event for a simulated long-press.
 432     *
 433     * @author alanv@google.com (Alan Viverette)
 434     */
 435    private class KeyEventRunnable implements Runnable {
 436        private final int mKeyCode;
 437        private final int mMetaState;
 438        private final long mDownTime;
 439
 440        public KeyEventRunnable(int keyCode, int metaState, long downTime) {
 441            mKeyCode = keyCode;
 442            mMetaState = metaState;
 443            mDownTime = downTime;
 444        }
 445
 446        @Override
 447        public void run() {
 448            final KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), mDownTime,
 449                    KeyEvent.ACTION_UP, mKeyCode, 0, mMetaState, KeyCharacterMap.BUILT_IN_KEYBOARD,
 450                    0, KeyEvent.FLAG_SOFT_KEYBOARD);
 451
 452            // First attempt to send the key to the IME, then to the input
 453            // connection.
 454            if (!onKeyUp(mKeyCode, event)) {
 455                final InputConnection ic = getCurrentInputConnection();
 456
 457                if (ic != null) {
 458                    ic.sendKeyEvent(event);
 459                }
 460            }
 461        }
 462    }
 463
 464    /* package */Handler mHandler = new Handler() {
 465        @Override
 466        public void handleMessage(Message msg) {
 467            switch (msg.what) {
 468                case MSG_UPDATE_SUGGESTIONS:
 469                    updateSuggestions();
 470                    break;
 471                case MSG_UPDATE_OLD_SUGGESTIONS:
 472                    setOldSuggestions();
 473                    break;
 474                case MSG_UPDATE_SHIFT_STATE:
 475                    updateShiftKeyState(getCurrentInputEditorInfo());
 476                    break;
 477                case MSG_VOICE_RESULTS:
 478                    handleVoiceResults();
 479                    break;
 480            }
 481        }
 482    };
 483
 484    /**
 485     * Returns <code>true<code> if this Android build supports voice input. * *
 486     * * * @return <code>true<code>if this Android build supports voice input
 487     */
 488    public static boolean supportsVoiceInput() {
 489        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO);
 490    }
 491
 492    @Override
 493    public void onCreate() {
 494        super.onCreate();
 495        LatinImeLogger.init(this);
 496        // setStatusIcon(R.drawable.ime_qwerty);
 497        mResources = getResources();
 498        final Configuration conf = mResources.getConfiguration();
 499        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
 500        mLanguageSwitcher = new LanguageSwitcher(this);
 501        mLanguageSwitcher.loadLocales(prefs);
 502        mKeyboardSwitcher = new KeyboardSwitcher(this);
 503        mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
 504        mAccessibilityUtils = new AccessibilityUtils(this);
 505        mFeedbackUtil = new FeedbackUtil(this);
 506        mSystemLocale = conf.locale.toString();
 507        mLanguageSwitcher.setSystemLocale(conf.locale);
 508        String inputLanguage = mLanguageSwitcher.getInputLanguage();
 509        if (inputLanguage == null) {
 510            inputLanguage = conf.locale.toString();
 511        }
 512        mReCorrectionEnabled = prefs.getBoolean(PREF_RECORRECTION_ENABLED, getResources()
 513                .getBoolean(R.bool.default_recorrection_enabled));
 514
 515        LatinIMEUtil.GCUtils.getInstance().reset();
 516        boolean tryGC = true;
 517        for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
 518            try {
 519                initSuggest(inputLanguage);
 520                tryGC = false;
 521            } catch (OutOfMemoryError e) {
 522                tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e);
 523            }
 524        }
 525
 526        mOrientation = conf.orientation;
 527        initSuggestPuncList();
 528
 529        // Register to receive phone status changes. We need to switch to hidden
 530        // mode when the user is receiving a phone call, then switch back when
 531        // they're done.
 532        mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
 533        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
 534
 535        // Register to receive ringer mode changes for silent mode and keyboard
 536        // mode requests.
 537        IntentFilter filter = new IntentFilter();
 538        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
 539        filter.addAction(REQUEST_KEYBOARD_MODE_CHANGE);
 540        filter.addAction(REQUEST_KEYBOARD_MODE_UPDATE);
 541        registerReceiver(mReceiver, filter);
 542
 543        if (VOICE_INSTALLED) {
 544            mVoiceInput = new VoiceInput(this, this);
 545            mHints = new Hints(this, new Hints.Display() {
 546                @Override
 547                public void showHint(int viewResource) {
 548                    LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 549                    View view = inflater.inflate(viewResource, null);
 550                    setCandidatesView(view);
 551                    setCandidatesViewShown(true);
 552                    mIsShowingHint = true;
 553                }
 554            });
 555        }
 556        prefs.registerOnSharedPreferenceChangeListener(this);
 557    }
 558
 559    @Override
 560    public void onWindowShown() {
 561        super.onWindowShown();
 562
 563        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
 564        boolean hasRunTutorial = prefs.getBoolean(PREF_HAS_RUN_TUTORIAL, false);
 565        if (!hasRunTutorial) {
 566            Intent showDialog = new Intent(this, LatinTutorialDialog.class);
 567            showDialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 568            startActivity(showDialog);
 569
 570            SharedPreferences.Editor editor = prefs.edit();
 571            editor.putBoolean(PREF_HAS_RUN_TUTORIAL, true);
 572            editor.commit();
 573        }
 574    }
 575
 576    /**
 577     * Loads a dictionary or multiple separated dictionary
 578     *
 579     * @return returns array of dictionary resource ids
 580     */
 581    /* package */static int[] getDictionary(Resources res) {
 582        String packageName = res.getResourcePackageName(R.xml.dictionary);
 583        XmlResourceParser xrp = res.getXml(R.xml.dictionary);
 584        ArrayList<Integer> dictionaries = new ArrayList<Integer>();
 585
 586        try {
 587            int current = xrp.getEventType();
 588            while (current != XmlResourceParser.END_DOCUMENT) {
 589                if (current == XmlResourceParser.START_TAG) {
 590                    String tag = xrp.getName();
 591                    if (tag != null) {
 592                        if (tag.equals("part")) {
 593                            String dictFileName = xrp.getAttributeValue(null, "name");
 594                            dictionaries.add(res.getIdentifier(dictFileName, "raw", packageName));
 595                        }
 596                    }
 597                }
 598                xrp.next();
 599                current = xrp.getEventType();
 600            }
 601        } catch (XmlPullParserException e) {
 602            Log.e(TAG, "Dictionary XML parsing failure");
 603        } catch (IOException e) {
 604            Log.e(TAG, "Dictionary XML IOException");
 605        }
 606
 607        int count = dictionaries.size();
 608        int[] dict = new int[count];
 609        for (int i = 0; i < count; i++) {
 610            dict[i] = dictionaries.get(i);
 611        }
 612
 613        return dict;
 614    }
 615
 616    private void initSuggest(String locale) {
 617        mInputLocale = locale;
 618
 619        Resources orig = getResources();
 620        Configuration conf = orig.getConfiguration();
 621        Locale saveLocale = conf.locale;
 622        conf.locale = new Locale(locale);
 623        orig.updateConfiguration(conf, orig.getDisplayMetrics());
 624        if (mSuggest != null) {
 625            mSuggest.close();
 626        }
 627        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
 628        mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
 629
 630        int[] dictionaries = getDictionary(orig);
 631        mSuggest = new Suggest(this, dictionaries);
 632        updateAutoTextEnabled(saveLocale);
 633        if (mUserDictionary != null)
 634            mUserDictionary.close();
 635        mUserDictionary = new UserDictionary(this, mInputLocale);
 636        if (mContactsDictionary == null) {
 637            mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
 638        }
 639        if (mAutoDictionary != null) {
 640            mAutoDictionary.close();
 641        }
 642        mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO);
 643        if (mUserBigramDictionary != null) {
 644            mUserBigramDictionary.close();
 645        }
 646        mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale, Suggest.DIC_USER);
 647        mSuggest.setUserBigramDictionary(mUserBigramDictionary);
 648        mSuggest.setUserDictionary(mUserDictionary);
 649        mSuggest.setContactsDictionary(mContactsDictionary);
 650        mSuggest.setAutoDictionary(mAutoDictionary);
 651        updateCorrectionMode();
 652        mWordSeparators = mResources.getString(R.string.word_separators);
 653        mSentenceSeparators = mResources.getString(R.string.sentence_separators);
 654
 655        conf.locale = saveLocale;
 656        orig.updateConfiguration(conf, orig.getDisplayMetrics());
 657    }
 658
 659    @Override
 660    public void onDestroy() {
 661        if (mUserDictionary != null) {
 662            mUserDictionary.close();
 663        }
 664
 665        if (mContactsDictionary != null) {
 666            mContactsDictionary.close();
 667        }
 668
 669        // Unregister the phone state listener.
 670        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
 671
 672        // Unregister the broadcast receiver.
 673        try {
 674            unregisterReceiver(mReceiver);
 675        } catch (IllegalArgumentException e) {
 676            // Ignore the case where broadcast receiver is already unregistered.
 677        }
 678
 679        if (VOICE_INSTALLED && mVoiceInput != null) {
 680            mVoiceInput.destroy();
 681        }
 682
 683        LatinImeLogger.commit();
 684        LatinImeLogger.onDestroy();
 685
 686        super.onDestroy();
 687    }
 688
 689    @Override
 690    public void onConfigurationChanged(Configuration conf) {
 691        // If the system locale changes and is different from the saved
 692        // locale (mSystemLocale), then reload the input locale list from the
 693        // latin ime settings (shared prefs) and reset the input locale
 694        // to the first one.
 695        final String systemLocale = conf.locale.toString();
 696        if (!TextUtils.equals(systemLocale, mSystemLocale)) {
 697            mSystemLocale = systemLocale;
 698            if (mLanguageSwitcher != null) {
 699                mLanguageSwitcher.loadLocales(PreferenceManager.getDefaultSharedPreferences(this));
 700                mLanguageSwitcher.setSystemLocale(conf.locale);
 701                toggleLanguage(true, true);
 702            } else {
 703                reloadKeyboards();
 704            }
 705        }
 706        // If orientation changed while predicting, commit the change
 707        if (conf.orientation != mOrientation) {
 708            InputConnection ic = getCurrentInputConnection();
 709            commitTyped(ic);
 710            if (ic != null)
 711                ic.finishComposingText(); // For voice input
 712            mOrientation = conf.orientation;
 713            reloadKeyboards();
 714        }
 715        mConfigurationChanging = true;
 716        super.onConfigurationChanged(conf);
 717        if (mRecognizing) {
 718            switchToRecognitionStatusView();
 719        }
 720        mConfigurationChanging = false;
 721    }
 722
 723    @Override
 724    public View onCreateInputView() {
 725        mKeyboardSwitcher.recreateInputView();
 726        mKeyboardSwitcher.makeKeyboards(true);
 727        mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 0,
 728                shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo()));
 729        return mKeyboardSwitcher.getInputView();
 730    }
 731
 732    @Override
 733    public View onCreateCandidatesView() {
 734        mKeyboardSwitcher.makeKeyboards(true);
 735        mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate(R.layout.candidates,
 736                null);
 737        mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
 738        mCandidateView.setService(this);
 739        setCandidatesViewShown(true);
 740        return mCandidateViewContainer;
 741    }
 742
 743    @Override
 744    public void onStartInputView(EditorInfo attribute, boolean restarting) {
 745        LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
 746        // In landscape mode, this method gets called without the input view
 747        // being created.
 748        if (inputView == null) {
 749            return;
 750        }
 751
 752        if (mRefreshKeyboardRequired) {
 753            mRefreshKeyboardRequired = false;
 754            toggleLanguage(true, true);
 755        }
 756
 757        mKeyboardSwitcher.makeKeyboards(false);
 758
 759        TextEntryState.newSession(this);
 760
 761        // Most such things we decide below in the switch statement, but we need
 762        // to know now whether this is a password text field, because we need to
 763        // know now (before the switch statement) whether we want to enable the
 764        // voice button.
 765        mPasswordText = false;
 766        int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
 767        if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
 768                || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
 769            mPasswordText = true;
 770        }
 771
 772        mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute);
 773        final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice;
 774
 775        mAfterVoiceInput = false;
 776        mImmediatelyAfterVoiceInput = false;
 777        mShowingVoiceSuggestions = false;
 778        mVoiceInputHighlighted = false;
 779        mInputTypeNoAutoCorrect = false;
 780        mPredictionOn = false;
 781        mCompletionOn = false;
 782        mCompletions = null;
 783        mCapsLock = false;
 784        mEnteredText = null;
 785
 786        mCachedPredictionOn = false;
 787        mCachedMode = KeyboardSwitcher.MODE_TEXT;
 788
 789        switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
 790            case EditorInfo.TYPE_CLASS_NUMBER:
 791            case EditorInfo.TYPE_CLASS_DATETIME:
 792                // fall through
 793                // NOTE: For now, we use the phone keyboard for NUMBER and
 794                // DATETIME until we get
 795                // a dedicated number entry keypad.
 796                // TODO: Use a dedicated number entry keypad here when we get
 797                // one.
 798            case EditorInfo.TYPE_CLASS_PHONE:
 799                mCachedMode = KeyboardSwitcher.MODE_PHONE;
 800                break;
 801            case EditorInfo.TYPE_CLASS_TEXT:
 802                mCachedMode = KeyboardSwitcher.MODE_TEXT;
 803                mCachedPredictionOn = true;
 804                // Make sure that passwords are not displayed in candidate view
 805                if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
 806                        || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
 807                    mCachedPredictionOn = false;
 808                }
 809                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
 810                        || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
 811                    mAutoSpace = false;
 812                } else {
 813                    mAutoSpace = true;
 814                }
 815                if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
 816                    mCachedPredictionOn = false;
 817                    mCachedMode = KeyboardSwitcher.MODE_EMAIL;
 818                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
 819                    mCachedPredictionOn = false;
 820                    mCachedMode = KeyboardSwitcher.MODE_URL;
 821                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
 822                    mCachedMode = KeyboardSwitcher.MODE_IM;
 823                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
 824                    mCachedPredictionOn = false;
 825                } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
 826                    mCachedMode = KeyboardSwitcher.MODE_WEB;
 827                    // If it's a browser edit field and auto correct is not ON
 828                    // explicitly, then
 829                    // disable auto correction, but keep suggestions on.
 830                    if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
 831                        mInputTypeNoAutoCorrect = true;
 832                    }
 833                }
 834
 835                // If NO_SUGGESTIONS is set, don't do prediction.
 836                if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
 837                    mCachedPredictionOn = false;
 838                    mInputTypeNoAutoCorrect = true;
 839                }
 840                // If it's not multiline and the autoCorrect flag is not set,
 841                // then don't correct
 842                if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0
 843                        && (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
 844                    mInputTypeNoAutoCorrect = true;
 845                }
 846                if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
 847                    mCachedPredictionOn = false;
 848                    mCompletionOn = isFullscreenMode();
 849                }
 850                break;
 851        }
 852
 853        int keyboardMode = mCachedMode;
 854        boolean predictionOn = mCachedPredictionOn;
 855
 856        if (isAccessibilityEnabled()) {
 857            // If auto-switch is on AND we're not already auto-switched AND we're
 858            // focused on an editable field THEN cache the current mode and force
 859            // typing mode.
 860            if (mAutoSwitch && mCachedForcedMode == FORCE_NONE
 861                    && (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) != EditorInfo.TYPE_NULL) {
 862                mCachedForcedMode = mForcedMode;
 863                mForcedMode = FORCE_CACHED;
 864                mAccessibilityUtils.speakDescription(getString(R.string.spoken_mode_switch_keyboard));
 865            } else if (mForcedMode == FORCE_HIDDEN) {
 866                keyboardMode = KeyboardSwitcher.MODE_HIDDEN;
 867                predictionOn = false;
 868            } else if (mForcedMode == FORCE_DPAD) {
 869                keyboardMode = KeyboardSwitcher.MODE_DPAD;
 870                predictionOn = false;
 871            }
 872        }
 873
 874        mPredictionOn = predictionOn;
 875        mKeyboardSwitcher.setKeyboardMode(keyboardMode, attribute.imeOptions, enableVoiceButton);
 876
 877        inputView.closing();
 878        mComposing.setLength(0);
 879        mPredicting = false;
 880        mDeleteCount = 0;
 881        mJustAddedAutoSpace = false;
 882        loadSettings();
 883        updateShiftKeyState(attribute);
 884
 885        setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn, false /* needsInputViewShown */);
 886        updateSuggestions();
 887
 888        // If the dictionary is not big enough, don't auto correct
 889        mHasDictionary = mSuggest.hasMainDictionary();
 890
 891        updateCorrectionMode();
 892
 893        inputView.setPreviewEnabled(mPopupOn);
 894        inputView.setProximityCorrectionEnabled(true);
 895        inputView.setAccessibilityEnabled(isAccessibilityEnabled());
 896        mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions);
 897        // If we just entered a text field, maybe it has some old text that
 898        // requires correction
 899        checkReCorrectionOnStart();
 900        if (TRACE)
 901            Debug.startMethodTracing("/data/trace/latinime");
 902    }
 903
 904    private void checkReCorrectionOnStart() {
 905        if (mReCorrectionEnabled && isPredictionOn()) {
 906            // First get the cursor position. This is required by
 907            // setOldSuggestions(), so that
 908            // it can pass the correct range to setComposingRegion(). At this
 909            // point, we don't
 910            // have valid values for mLastSelectionStart/Stop because
 911            // onUpdateSelection() has
 912            // not been called yet.
 913            InputConnection ic = getCurrentInputConnection();
 914            if (ic == null)
 915                return;
 916            ExtractedTextRequest etr = new ExtractedTextRequest();
 917            etr.token = 0; // anything is fine here
 918            ExtractedText et = ic.getExtractedText(etr, 0);
 919            if (et == null)
 920                return;
 921
 922            mLastSelectionStart = et.startOffset + et.selectionStart;
 923            mLastSelectionEnd = et.startOffset + et.selectionEnd;
 924
 925            // Then look for possible corrections in a delayed fashion
 926            if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) {
 927                postUpdateOldSuggestions();
 928            }
 929        }
 930    }
 931
 932    @Override
 933    public void onFinishInput() {
 934        super.onFinishInput();
 935
 936        LatinImeLogger.commit();
 937        onAutoCompletionStateChanged(false);
 938
 939        if (VOICE_INSTALLED && !mConfigurationChanging) {
 940            if (mAfterVoiceInput) {
 941                mVoiceInput.flushAllTextModificationCounters();
 942                mVoiceInput.logInputEnded();
 943            }
 944            mVoiceInput.flushLogs();
 945            mVoiceInput.cancel();
 946        }
 947        if (mKeyboardSwitcher.getInputView() != null) {
 948            mKeyboardSwitcher.getInputView().closing();
 949        }
 950        if (mAutoDictionary != null)
 951            mAutoDictionary.flushPendingWrites();
 952        if (mUserBigramDictionary != null)
 953            mUserBigramDictionary.flushPendingWrites();
 954    }
 955
 956    @Override
 957    public void onFinishInputView(boolean finishingInput) {
 958        super.onFinishInputView(finishingInput);
 959        // Remove pending messages related to update suggestions
 960        mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
 961        mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
 962
 963        // If auto-switch is on and we're in auto-switched mode, then restore
 964        // the previous forced mode.
 965        if (mAutoSwitch && mCachedForcedMode != FORCE_NONE) {
 966            requestKeyboardMode(mCachedForcedMode);
 967        }
 968    }
 969
 970    @Override
 971    public void onUpdateExtractedText(int token, ExtractedText text) {
 972        super.onUpdateExtractedText(token, text);
 973        InputConnection ic = getCurrentInputConnection();
 974        if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
 975            if (mHints.showPunctuationHintIfNecessary(ic)) {
 976                mVoiceInput.logPunctuationHintDisplayed();
 977            }
 978        }
 979        mImmediatelyAfterVoiceInput = false;
 980    }
 981
 982    @Override
 983    public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
 984            int candidatesStart, int candidatesEnd) {
 985        super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
 986                candidatesEnd);
 987
 988        if (DEBUG) {
 989            Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd + ", nss="
 990                    + newSelStart + ", nse=" + newSelEnd + ", cs=" + candidatesStart + ", ce="
 991                    + candidatesEnd);
 992        }
 993
 994        if (mAfterVoiceInput) {
 995            mVoiceInput.setCursorPos(newSelEnd);
 996            mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
 997        }
 998
 999        // If the current selection in the text view changes, we should
1000        // clear whatever candidate text we have.
1001        if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
1002                && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) {
1003            mComposing.setLength(0);
1004            mPredicting = false;
1005            postUpdateSuggestions();
1006            TextEntryState.reset();
1007            InputConnection ic = getCurrentInputConnection();
1008            if (ic != null) {
1009                ic.finishComposingText();
1010            }
1011            mVoiceInputHighlighted = false;
1012        } else if (!mPredicting && !mJustAccepted) {
1013            switch (TextEntryState.getState()) {
1014                case ACCEPTED_DEFAULT:
1015                    TextEntryState.reset();
1016                    //$FALL-THROUGH$
1017                case SPACE_AFTER_PICKED:
1018                    mJustAddedAutoSpace = false; // The user moved the cursor.
1019                    break;
1020            }
1021        }
1022        mJustAccepted = false;
1023        postUpdateShiftKeyState();
1024
1025        // Make a note of the cursor position
1026        mLastSelectionStart = newSelStart;
1027        mLastSelectionEnd = newSelEnd;
1028
1029        if (mReCorrectionEnabled) {
1030            // Don't look for corrections if the keyboard is not visible
1031            if (mKeyboardSwitcher != null && mKeyboardSwitcher.getInputView() != null
1032                    && mKeyboardSwitcher.getInputView().isShown()) {
1033                // Check if we should go in or out of correction mode.
1034                if (isPredictionOn()
1035                        && mJustRevertedSeparator == null
1036                        && (candidatesStart == candidatesEnd || newSelStart != oldSelStart || TextEntryState
1037                                .isCorrecting()) && (newSelStart < newSelEnd - 1 || (!mPredicting))
1038                        && !mVoiceInputHighlighted) {
1039                    if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
1040                        postUpdateOldSuggestions();
1041                    } else {
1042                        abortCorrection(false);
1043                        // Show the punctuation suggestions list if the current
1044                        // one is not
1045                        // and if not showing "Touch again to save".
1046                        if (mCandidateView != null
1047                                && !mSuggestPuncList.equals(mCandidateView.getSuggestions())
1048                                && !mCandidateView.isShowingAddToDictionaryHint()) {
1049                            setNextSuggestions();
1050                        }
1051                    }
1052                }
1053            }
1054        }
1055    }
1056
1057    /**
1058     * This is called when the user has clicked on the extracted text view, when running in
1059     * fullscreen mode. The default implementation hides the candidates view when this happens, but
1060     * only if the extracted text editor has a vertical scroll bar because its text doesn't fit.
1061     * Here we override the behavior due to the possibility that a re-correction could cause the
1062     * candidate strip to disappear and re-appear.
1063     */
1064    @Override
1065    public void onExtractedTextClicked() {
1066        if (mReCorrectionEnabled && isPredictionOn())
1067            return;
1068
1069        super.onExtractedTextClicked();
1070    }
1071
1072    /**
1073     * This is called when the user has performed a cursor movement in the extracted text view, when
1074     * it is running in fullscreen mode. The default implementation hides the candidates view when a
1075     * vertical movement happens, but only if the extracted text editor has a vertical scroll bar
1076     * because its text doesn't fit. Here we override the behavior due to the possibility that a
1077     * re-correction could cause the candidate strip to disappear and re-appear.
1078     */
1079    @Override
1080    public void onExtractedCursorMovement(int dx, int dy) {
1081        if (mReCorrectionEnabled && isPredictionOn())
1082            return;
1083
1084        super.onExtractedCursorMovement(dx, dy);
1085    }
1086
1087    @Override
1088    public void hideWindow() {
1089        LatinImeLogger.commit();
1090        onAutoCompletionStateChanged(false);
1091
1092        if (TRACE)
1093            Debug.stopMethodTracing();
1094        if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
1095            mOptionsDialog.dismiss();
1096            mOptionsDialog = null;
1097        }
1098        if (!mConfigurationChanging) {
1099            if (mAfterVoiceInput)
1100                mVoiceInput.logInputEnded();
1101            if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
1102                mVoiceInput.logKeyboardWarningDialogDismissed();
1103                mVoiceWarningDialog.dismiss();
1104                mVoiceWarningDialog = null;
1105            }
1106            if (VOICE_INSTALLED & mRecognizing) {
1107                mVoiceInput.cancel();
1108            }
1109        }
1110        mWordToSuggestions.clear();
1111        mWordHistory.clear();
1112        super.hideWindow();
1113        TextEntryState.endSession();
1114    }
1115
1116    @Override
1117    public void onDisplayCompletions(CompletionInfo[] completions) {
1118        if (DEBUG) {
1119            Log.i("foo", "Received completions:");
1120            for (int i = 0; i < (completions != null ? completions.length : 0); i++) {
1121                Log.i("foo", "  #" + i + ": " + completions[i]);
1122            }
1123        }
1124        if (mCompletionOn) {
1125            mCompletions = completions;
1126            if (completions == null) {
1127                clearSuggestions();
1128                return;
1129            }
1130
1131            List<CharSequence> stringList = new ArrayList<CharSequence>();
1132            for (int i = 0; i < (completions != null ? completions.length : 0); i++) {
1133                CompletionInfo ci = completions[i];
1134                if (ci != null)
1135                    stringList.add(ci.getText());
1136            }
1137            // When in fullscreen mode, show completions generated by the
1138            // application
1139            setSuggestions(stringList, true, true, true);
1140            mBestWord = null;
1141            setCandidatesViewShown(true);
1142        }
1143    }
1144
1145    private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
1146        // TODO: Remove this if we support candidates with hard keyboard
1147        if (onEvaluateInputViewShown()) {
1148            super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null
1149                    && (needsInputViewShown ? mKeyboardSwitcher.getInputView().isShown() : true));
1150        }
1151    }
1152
1153    @Override
1154    public void setCandidatesViewShown(boolean shown) {
1155        setCandidatesViewShownInternal(shown, true /* needsInputViewShown */);
1156    }
1157
1158    @Override
1159    public void onComputeInsets(InputMethodService.Insets outInsets) {
1160        super.onComputeInsets(outInsets);
1161        if (!isFullscreenMode()) {
1162            outInsets.contentTopInsets = outInsets.visibleTopInsets;
1163        }
1164    }
1165
1166    @Override
1167    public boolean onEvaluateFullscreenMode() {
1168        DisplayMetrics dm = getResources().getDisplayMetrics();
1169        float displayHeight = dm.heightPixels;
1170        // If the display is more than X inches high, don't go to fullscreen
1171        // mode
1172        float dimen = getResources().getDimension(R.dimen.max_height_for_fullscreen);
1173        if (displayHeight > dimen) {
1174            return false;
1175        } else {
1176            return super.onEvaluateFullscreenMode();
1177        }
1178    }
1179
1180    @Override
1181    public boolean onKeyDown(int keyCode, KeyEvent event) {
1182        switch (keyCode) {
1183            case KeyEvent.KEYCODE_BACK:
1184                if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) {
1185                    if (mKeyboardSwitcher.getInputView().handleBack()) {
1186                        return true;
1187                    }
1188                }
1189                break;
1190        }
1191        return super.onKeyDown(keyCode, event);
1192    }
1193
1194    @Override
1195    public boolean onKeyUp(int keyCode, KeyEvent event) {
1196        switch (keyCode) {
1197            case KeyEvent.KEYCODE_DPAD_DOWN:
1198            case KeyEvent.KEYCODE_DPAD_UP:
1199            case KeyEvent.KEYCODE_DPAD_LEFT:
1200            case KeyEvent.KEYCODE_DPAD_RIGHT:
1201                LatinKeyboardView view = mKeyboardSwitcher.getInputView();
1202                if (view != null && view.isShifted()) {
1203                    // Force this key to be shifted.
1204                    event = new KeyEvent(event.getDownTime(), event.getEventTime(),
1205                            event.getAction(), event.getKeyCode(), event.getRepeatCount(),
1206                            event.getMetaState() | KeyEvent.META_SHIFT_ON
1207                                    | KeyEvent.META_SHIFT_LEFT_ON, event.getDeviceId(),
1208                            event.getScanCode(), event.getFlags());
1209                }
1210                break;
1211        }
1212        return super.onKeyUp(keyCode, event);
1213    }
1214
1215    @Override
1216    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
1217        switch (keyCode) {
1218            case KeyEvent.KEYCODE_VOLUME_UP:
1219                if (event.getRepeatCount() == 1) {
1220                    cycleForcedType(1);
1221                }
1222                return true;
1223            case KeyEvent.KEYCODE_VOLUME_DOWN:
1224                if (event.getRepeatCount() == 1) {
1225                    cycleForcedType(-1);
1226                }
1227                return true;
1228        }
1229
1230        return super.onKeyLongPress(keyCode, event);
1231    }
1232
1233    private void cycleForcedType(int di

Large files files are truncated, but you can click here to view the full file