KeyboardSwitcher.java revision 5f922caff80d5067c5af2bbbae2731ef25c9572a
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.android.inputmethod.keyboard;
18
19import com.android.inputmethod.latin.LatinIME;
20import com.android.inputmethod.latin.Settings;
21import com.android.inputmethod.latin.Utils;
22import com.android.inputmethod.latin.LatinImeLogger;
23import com.android.inputmethod.latin.R;
24import com.android.inputmethod.latin.SubtypeSwitcher;
25
26import android.content.Context;
27import android.content.SharedPreferences;
28import android.content.res.Resources;
29import android.util.Log;
30import android.view.InflateException;
31import android.view.inputmethod.InputMethodManager;
32
33import java.lang.ref.SoftReference;
34import java.util.HashMap;
35import java.util.Locale;
36
37public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener {
38    private static final String TAG = "KeyboardSwitcher";
39    private static final boolean DEBUG = false;
40    public static final boolean DEBUG_STATE = false;
41
42    private static String sConfigDefaultKeyboardThemeId;
43    public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20100902";
44    private static final int[] KEYBOARD_THEMES = {
45        R.layout.input_basic,
46        R.layout.input_basic_highcontrast,
47        R.layout.input_stone_normal,
48        R.layout.input_stone_bold,
49        R.layout.input_gingerbread,
50        R.layout.input_honeycomb,
51    };
52
53    private SubtypeSwitcher mSubtypeSwitcher;
54    private SharedPreferences mPrefs;
55
56    private LatinKeyboardView mInputView;
57    private LatinIME mInputMethodService;
58
59    private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
60    private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
61
62    private KeyboardId mSymbolsId;
63    private KeyboardId mSymbolsShiftedId;
64
65    private KeyboardId mCurrentId;
66    private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
67            new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
68
69    private int mMode = KeyboardId.MODE_TEXT; /* default value */
70    private int mImeOptions;
71    private boolean mIsSymbols;
72    /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
73     * what user actually typed. */
74    private boolean mIsAutoCorrectionActive;
75    private boolean mVoiceKeyEnabled;
76    private boolean mVoiceButtonOnPrimary;
77
78    private static final int AUTO_MODE_SWITCH_STATE_ALPHA = 0;
79    private static final int AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN = 1;
80    private static final int AUTO_MODE_SWITCH_STATE_SYMBOL = 2;
81    // The following states are used only on the distinct multi-touch panel devices.
82    private static final int AUTO_MODE_SWITCH_STATE_MOMENTARY = 3;
83    private static final int AUTO_MODE_SWITCH_STATE_CHORDING = 4;
84    private int mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
85
86    // Indicates whether or not we have the settings key
87    private boolean mHasSettingsKey;
88    private static final int SETTINGS_KEY_MODE_AUTO = R.string.settings_key_mode_auto;
89    private static final int SETTINGS_KEY_MODE_ALWAYS_SHOW =
90            R.string.settings_key_mode_always_show;
91    // NOTE: No need to have SETTINGS_KEY_MODE_ALWAYS_HIDE here because it's not being referred to
92    // in the source code now.
93    // Default is SETTINGS_KEY_MODE_AUTO.
94    private static final int DEFAULT_SETTINGS_KEY_MODE = SETTINGS_KEY_MODE_AUTO;
95
96    private int mLayoutId;
97
98    private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
99
100    public static KeyboardSwitcher getInstance() {
101        return sInstance;
102    }
103
104    private KeyboardSwitcher() {
105        // Intentional empty constructor for singleton.
106    }
107
108    public static void init(LatinIME ims, SharedPreferences prefs) {
109        sInstance.mInputMethodService = ims;
110        sInstance.mPrefs = prefs;
111        sInstance.mSubtypeSwitcher = SubtypeSwitcher.getInstance();
112
113        try {
114            sConfigDefaultKeyboardThemeId = ims.getString(
115                    R.string.config_default_keyboard_theme_id);
116            sInstance.mLayoutId = Integer.valueOf(
117                    prefs.getString(PREF_KEYBOARD_LAYOUT, sConfigDefaultKeyboardThemeId));
118        } catch (NumberFormatException e) {
119            sConfigDefaultKeyboardThemeId = "0";
120            sInstance.mLayoutId = 0;
121        }
122        prefs.registerOnSharedPreferenceChangeListener(sInstance);
123    }
124
125    private void makeSymbolsKeyboardIds() {
126        final Locale locale = mSubtypeSwitcher.getInputLocale();
127        final Resources res = mInputMethodService.getResources();
128        final int orientation = res.getConfiguration().orientation;
129        final int mode = mMode;
130        final int colorScheme = getColorScheme();
131        final boolean hasSettingsKey = mHasSettingsKey;
132        final boolean voiceKeyEnabled = mVoiceKeyEnabled;
133        final boolean hasVoiceKey = voiceKeyEnabled && !mVoiceButtonOnPrimary;
134        final int imeOptions = mImeOptions;
135        // Note: This comment is only applied for phone number keyboard layout.
136        // On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch
137        // between "phone keyboard" and "phone symbols keyboard".  But on xlarge device,
138        // "@integer/key_shift" key code is used for that purpose in order to properly display
139        // "more" and "locked more" key labels.  To achieve these behavior, we should initialize
140        // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard"
141        // respectively here for xlarge device's layout switching.
142        int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols;
143        mSymbolsId = new KeyboardId(
144                res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme,
145                hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
146        xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift;
147        mSymbolsShiftedId = new KeyboardId(
148                res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme,
149                hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
150    }
151
152    private boolean hasVoiceKey(boolean isSymbols) {
153        return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary);
154    }
155
156    public void loadKeyboard(int mode, int imeOptions, boolean voiceKeyEnabled,
157            boolean voiceButtonOnPrimary) {
158        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
159        try {
160            loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary, false);
161        } catch (RuntimeException e) {
162            Log.w(TAG, e);
163            LatinImeLogger.logOnException(mode + "," + imeOptions, e);
164        }
165    }
166
167    private void loadKeyboardInternal(int mode, int imeOptions, boolean voiceButtonEnabled,
168            boolean voiceButtonOnPrimary, boolean isSymbols) {
169        if (mInputView == null) return;
170        mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
171
172        mMode = mode;
173        mImeOptions = imeOptions;
174        mVoiceKeyEnabled = voiceButtonEnabled;
175        mVoiceButtonOnPrimary = voiceButtonOnPrimary;
176        mIsSymbols = isSymbols;
177        // Update the settings key state because number of enabled IMEs could have been changed
178        mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService);
179        makeSymbolsKeyboardIds();
180
181        KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols);
182        LatinKeyboard keyboard = getKeyboard(id);
183
184        mCurrentId = id;
185        mInputView.setKeyboard(keyboard);
186    }
187
188    private LatinKeyboard getKeyboard(KeyboardId id) {
189        final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
190        LatinKeyboard keyboard = (ref == null) ? null : ref.get();
191        if (keyboard == null) {
192            final Locale savedLocale =  mSubtypeSwitcher.changeSystemLocale(
193                    mSubtypeSwitcher.getInputLocale());
194
195            keyboard = new LatinKeyboard(mInputMethodService, id);
196
197            if (id.mEnableShiftLock) {
198                keyboard.enableShiftLock();
199            }
200
201            mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard));
202            if (DEBUG)
203                Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": "
204                        + ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
205
206            mSubtypeSwitcher.changeSystemLocale(savedLocale);
207        } else if (DEBUG) {
208            Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT  id=" + id);
209        }
210
211        keyboard.onAutoCorrectionStateChanged(mIsAutoCorrectionActive);
212        keyboard.setShifted(false);
213        return keyboard;
214    }
215
216    private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) {
217        final boolean hasVoiceKey = hasVoiceKey(isSymbols);
218        final int charColorId = getColorScheme();
219        final int xmlId;
220        final boolean enableShiftLock;
221
222        if (isSymbols) {
223            if (mode == KeyboardId.MODE_PHONE) {
224                xmlId = R.xml.kbd_phone_symbols;
225            } else if (mode == KeyboardId.MODE_NUMBER) {
226                // Note: MODE_NUMBER keyboard layout has no "switch alpha symbol" key.
227                xmlId = R.xml.kbd_number;
228            } else {
229                xmlId = R.xml.kbd_symbols;
230            }
231            enableShiftLock = false;
232        } else {
233            if (mode == KeyboardId.MODE_PHONE) {
234                xmlId = R.xml.kbd_phone;
235                enableShiftLock = false;
236            } else if (mode == KeyboardId.MODE_NUMBER) {
237                xmlId = R.xml.kbd_number;
238                enableShiftLock = false;
239            } else {
240                xmlId = R.xml.kbd_qwerty;
241                enableShiftLock = true;
242            }
243        }
244        final Resources res = mInputMethodService.getResources();
245        final int orientation = res.getConfiguration().orientation;
246        final Locale locale = mSubtypeSwitcher.getInputLocale();
247        return new KeyboardId(
248                res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, charColorId,
249                mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock);
250    }
251
252    public int getKeyboardMode() {
253        return mMode;
254    }
255
256    public boolean isAlphabetMode() {
257        return mCurrentId != null && mCurrentId.isAlphabetKeyboard();
258    }
259
260    public boolean isInputViewShown() {
261        return mInputView != null && mInputView.isShown();
262    }
263
264    public boolean isKeyboardAvailable() {
265        if (mInputView != null)
266            return mInputView.getLatinKeyboard() != null;
267        return false;
268    }
269
270    private LatinKeyboard getLatinKeyboard() {
271        if (mInputView != null)
272            return mInputView.getLatinKeyboard();
273        return null;
274    }
275
276    public void setPreferredLetters(int[] frequencies) {
277        LatinKeyboard latinKeyboard = getLatinKeyboard();
278        if (latinKeyboard != null)
279            latinKeyboard.setPreferredLetters(frequencies);
280    }
281
282    public void keyReleased() {
283        LatinKeyboard latinKeyboard = getLatinKeyboard();
284        if (latinKeyboard != null)
285            latinKeyboard.keyReleased();
286    }
287
288    public boolean isShiftedOrShiftLocked() {
289        LatinKeyboard latinKeyboard = getLatinKeyboard();
290        if (latinKeyboard != null)
291            return latinKeyboard.isShiftedOrShiftLocked();
292        return false;
293    }
294
295    public boolean isShiftLocked() {
296        LatinKeyboard latinKeyboard = getLatinKeyboard();
297        if (latinKeyboard != null)
298            return latinKeyboard.isShiftLocked();
299        return false;
300    }
301
302    public boolean isAutomaticTemporaryUpperCase() {
303        LatinKeyboard latinKeyboard = getLatinKeyboard();
304        if (latinKeyboard != null)
305            return latinKeyboard.isAutomaticTemporaryUpperCase();
306        return false;
307    }
308
309    public boolean isManualTemporaryUpperCase() {
310        LatinKeyboard latinKeyboard = getLatinKeyboard();
311        if (latinKeyboard != null)
312            return latinKeyboard.isManualTemporaryUpperCase();
313        return false;
314    }
315
316    private void setManualTemporaryUpperCase(boolean shifted) {
317        LatinKeyboard latinKeyboard = getLatinKeyboard();
318        if (latinKeyboard != null) {
319            // On non-distinct multi touch panel device, we should also turn off the shift locked
320            // state when shift key is pressed to go to normal mode.
321            // On the other hand, on distinct multi touch panel device, turning off the shift locked
322            // state with shift key pressing is handled by onReleaseShift().
323            if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
324                latinKeyboard.setShiftLocked(false);
325            }
326            if (latinKeyboard.setShifted(shifted)) {
327                mInputView.invalidateAllKeys();
328            }
329        }
330    }
331
332    private void setShiftLocked(boolean shiftLocked) {
333        LatinKeyboard latinKeyboard = getLatinKeyboard();
334        if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) {
335            mInputView.invalidateAllKeys();
336        }
337    }
338
339    /**
340     * Toggle keyboard shift state triggered by user touch event.
341     */
342    public void toggleShift() {
343        mInputMethodService.mHandler.cancelUpdateShiftState();
344        if (DEBUG_STATE)
345            Log.d(TAG, "toggleShift:"
346                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
347                    + " shiftKeyState=" + mShiftKeyState);
348        if (isAlphabetMode()) {
349            setManualTemporaryUpperCase(!isShiftedOrShiftLocked());
350        } else {
351            toggleShiftInSymbol();
352        }
353    }
354
355    public void toggleCapsLock() {
356        mInputMethodService.mHandler.cancelUpdateShiftState();
357        if (DEBUG_STATE)
358            Log.d(TAG, "toggleCapsLock:"
359                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
360                    + " shiftKeyState=" + mShiftKeyState);
361        if (isAlphabetMode()) {
362            if (isShiftLocked()) {
363                // Shift key is long pressed while caps lock state, we will toggle back to normal
364                // state. And mark as if shift key is released.
365                setShiftLocked(false);
366                mShiftKeyState.onRelease();
367            } else {
368                setShiftLocked(true);
369            }
370        }
371    }
372
373    private void setAutomaticTemporaryUpperCase() {
374        LatinKeyboard latinKeyboard = getLatinKeyboard();
375        if (latinKeyboard != null) {
376            latinKeyboard.setAutomaticTemporaryUpperCase();
377            mInputView.invalidateAllKeys();
378        }
379    }
380
381    /**
382     * Update keyboard shift state triggered by connected EditText status change.
383     */
384    public void updateShiftState() {
385        final ShiftKeyState shiftKeyState = mShiftKeyState;
386        if (DEBUG_STATE)
387            Log.d(TAG, "updateShiftState:"
388                    + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()
389                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
390                    + " shiftKeyState=" + shiftKeyState);
391        if (isAlphabetMode()) {
392            if (!isShiftLocked() && !shiftKeyState.isIgnoring()) {
393                if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
394                    // Only when shift key is releasing, automatic temporary upper case will be set.
395                    setAutomaticTemporaryUpperCase();
396                } else {
397                    setManualTemporaryUpperCase(shiftKeyState.isMomentary());
398                }
399            }
400        } else {
401            // In symbol keyboard mode, we should clear shift key state because only alphabet
402            // keyboard has shift key.
403            shiftKeyState.onRelease();
404        }
405    }
406
407    public void changeKeyboardMode() {
408        if (DEBUG_STATE)
409            Log.d(TAG, "changeKeyboardMode:"
410                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
411                    + " shiftKeyState=" + mShiftKeyState);
412        toggleKeyboardMode();
413        if (isShiftLocked() && isAlphabetMode())
414            setShiftLocked(true);
415        updateShiftState();
416    }
417
418    public void onPressShift() {
419        if (!isKeyboardAvailable())
420            return;
421        ShiftKeyState shiftKeyState = mShiftKeyState;
422        if (DEBUG_STATE)
423            Log.d(TAG, "onPressShift:"
424                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
425                    + " shiftKeyState=" + shiftKeyState);
426        if (isAlphabetMode()) {
427            if (isShiftLocked()) {
428                // Shift key is pressed while caps lock state, we will treat this state as shifted
429                // caps lock state and mark as if shift key pressed while normal state.
430                shiftKeyState.onPress();
431                setManualTemporaryUpperCase(true);
432            } else if (isAutomaticTemporaryUpperCase()) {
433                // Shift key is pressed while automatic temporary upper case, we have to move to
434                // manual temporary upper case.
435                shiftKeyState.onPress();
436                setManualTemporaryUpperCase(true);
437            } else if (isShiftedOrShiftLocked()) {
438                // In manual upper case state, we just record shift key has been pressing while
439                // shifted state.
440                shiftKeyState.onPressOnShifted();
441            } else {
442                // In base layout, chording or manual temporary upper case mode is started.
443                shiftKeyState.onPress();
444                toggleShift();
445            }
446        } else {
447            // In symbol mode, just toggle symbol and symbol more keyboard.
448            shiftKeyState.onPress();
449            toggleShift();
450        }
451    }
452
453    public void onReleaseShift() {
454        if (!isKeyboardAvailable())
455            return;
456        ShiftKeyState shiftKeyState = mShiftKeyState;
457        if (DEBUG_STATE)
458            Log.d(TAG, "onReleaseShift:"
459                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
460                    + " shiftKeyState=" + shiftKeyState);
461        if (isAlphabetMode()) {
462            if (shiftKeyState.isMomentary()) {
463                // After chording input while normal state.
464                toggleShift();
465            } else if (isShiftLocked() && !shiftKeyState.isIgnoring()) {
466                // Shift has been pressed without chording while caps lock state.
467                toggleCapsLock();
468            } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()) {
469                // Shift has been pressed without chording while shifted state.
470                toggleShift();
471            }
472        }
473        shiftKeyState.onRelease();
474    }
475
476    public void onPressSymbol() {
477        if (DEBUG_STATE)
478            Log.d(TAG, "onPressSymbol:"
479                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
480                    + " symbolKeyState=" + mSymbolKeyState);
481        changeKeyboardMode();
482        mSymbolKeyState.onPress();
483        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_MOMENTARY;
484    }
485
486    public void onReleaseSymbol() {
487        if (DEBUG_STATE)
488            Log.d(TAG, "onReleaseSymbol:"
489                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
490                    + " symbolKeyState=" + mSymbolKeyState);
491        // Snap back to the previous keyboard mode if the user chords the mode change key and
492        // other key, then released the mode change key.
493        if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_CHORDING)
494            changeKeyboardMode();
495        mSymbolKeyState.onRelease();
496    }
497
498    public void onOtherKeyPressed() {
499        if (DEBUG_STATE)
500            Log.d(TAG, "onOtherKeyPressed:"
501                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
502                    + " shiftKeyState=" + mShiftKeyState
503                    + " symbolKeyState=" + mSymbolKeyState);
504        mShiftKeyState.onOtherKeyPressed();
505        mSymbolKeyState.onOtherKeyPressed();
506    }
507
508    public void onCancelInput() {
509        // Snap back to the previous keyboard mode if the user cancels sliding input.
510        if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY && getPointerCount() == 1)
511            changeKeyboardMode();
512    }
513
514    private void toggleShiftInSymbol() {
515        if (isAlphabetMode())
516            return;
517        final LatinKeyboard keyboard;
518        if (mCurrentId.equals(mSymbolsId) || !mCurrentId.equals(mSymbolsShiftedId)) {
519            mCurrentId = mSymbolsShiftedId;
520            keyboard = getKeyboard(mCurrentId);
521            // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To
522            // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true).
523            // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is
524            // called.
525            keyboard.setShiftLocked(true);
526        } else {
527            mCurrentId = mSymbolsId;
528            keyboard = getKeyboard(mCurrentId);
529            // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
530            // indicator, we need to call enableShiftLock() and setShiftLocked(false).
531            keyboard.setShifted(false);
532        }
533        mInputView.setKeyboard(keyboard);
534    }
535
536    public boolean isInMomentaryAutoModeSwitchState() {
537        return mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY;
538    }
539
540    public boolean isVibrateAndSoundFeedbackRequired() {
541        return mInputView == null || !mInputView.isInSlidingKeyInput();
542    }
543
544    private int getPointerCount() {
545        return mInputView == null ? 0 : mInputView.getPointerCount();
546    }
547
548    private void toggleKeyboardMode() {
549        loadKeyboardInternal(mMode, mImeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary,
550                !mIsSymbols);
551        if (mIsSymbols) {
552            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
553        } else {
554            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
555        }
556    }
557
558    public boolean hasDistinctMultitouch() {
559        return mInputView != null && mInputView.hasDistinctMultitouch();
560    }
561
562    /**
563     * Updates state machine to figure out when to automatically snap back to the previous mode.
564     */
565    public void onKey(int key) {
566        if (DEBUG_STATE)
567            Log.d(TAG, "onKey: code=" + key + " autoModeSwitchState=" + mAutoModeSwitchState
568                    + " pointers=" + getPointerCount());
569        switch (mAutoModeSwitchState) {
570        case AUTO_MODE_SWITCH_STATE_MOMENTARY:
571            // Only distinct multi touch devices can be in this state.
572            // On non-distinct multi touch devices, mode change key is handled by
573            // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and
574            // {@link LatinIME#onRelease}. So, on such devices, {@link #mAutoModeSwitchState} starts
575            // from {@link #AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN}, or
576            // {@link #AUTO_MODE_SWITCH_STATE_ALPHA}, not from
577            // {@link #AUTO_MODE_SWITCH_STATE_MOMENTARY}.
578            if (key == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
579                // Detected only the mode change key has been pressed, and then released.
580                if (mIsSymbols) {
581                    mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
582                } else {
583                    mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
584                }
585            } else if (getPointerCount() == 1) {
586                // Snap back to the previous keyboard mode if the user pressed the mode change key
587                // and slid to other key, then released the finger.
588                // If the user cancels the sliding input, snapping back to the previous keyboard
589                // mode is handled by {@link #onCancelInput}.
590                changeKeyboardMode();
591            } else {
592                // Chording input is being started. The keyboard mode will be snapped back to the
593                // previous mode in {@link onReleaseSymbol} when the mode change key is released.
594                mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_CHORDING;
595            }
596            break;
597        case AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN:
598            if (key != Keyboard.CODE_SPACE && key != Keyboard.CODE_ENTER && key >= 0) {
599                mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL;
600            }
601            break;
602        case AUTO_MODE_SWITCH_STATE_SYMBOL:
603            // Snap back to alpha keyboard mode if user types one or more non-space/enter
604            // characters followed by a space/enter.
605            if (key == Keyboard.CODE_ENTER || key == Keyboard.CODE_SPACE) {
606                changeKeyboardMode();
607            }
608            break;
609        }
610    }
611
612    public LatinKeyboardView getInputView() {
613        return mInputView;
614    }
615
616    public LatinKeyboardView onCreateInputView() {
617        createInputViewInternal(mLayoutId, true);
618        return mInputView;
619    }
620
621    private void createInputViewInternal(int newLayout, boolean forceReset) {
622        int layoutId = newLayout;
623        if (mLayoutId != layoutId || mInputView == null || forceReset) {
624            if (mInputView != null) {
625                mInputView.closing();
626            }
627            if (KEYBOARD_THEMES.length <= layoutId) {
628                layoutId = Integer.valueOf(sConfigDefaultKeyboardThemeId);
629            }
630
631            Utils.GCUtils.getInstance().reset();
632            boolean tryGC = true;
633            for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
634                try {
635                    mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater(
636                            ).inflate(KEYBOARD_THEMES[layoutId], null);
637                    tryGC = false;
638                } catch (OutOfMemoryError e) {
639                    Log.w(TAG, "load keyboard failed: " + e);
640                    tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
641                            mLayoutId + "," + layoutId, e);
642                } catch (InflateException e) {
643                    Log.w(TAG, "load keyboard failed: " + e);
644                    tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
645                            mLayoutId + "," + layoutId, e);
646                }
647            }
648            mInputView.setOnKeyboardActionListener(mInputMethodService);
649            mLayoutId = layoutId;
650        }
651    }
652
653    private void postSetInputView() {
654        mInputMethodService.mHandler.post(new Runnable() {
655            @Override
656            public void run() {
657                if (mInputView != null) {
658                    mInputMethodService.setInputView(mInputView);
659                }
660                mInputMethodService.updateInputViewShown();
661            }
662        });
663    }
664
665    @Override
666    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
667        if (PREF_KEYBOARD_LAYOUT.equals(key)) {
668            final int layoutId = Integer.valueOf(
669                    sharedPreferences.getString(key, sConfigDefaultKeyboardThemeId));
670            createInputViewInternal(layoutId, false);
671            postSetInputView();
672        } else if (Settings.PREF_SETTINGS_KEY.equals(key)) {
673            mHasSettingsKey = getSettingsKeyMode(sharedPreferences, mInputMethodService);
674            createInputViewInternal(mLayoutId, true);
675            postSetInputView();
676        }
677    }
678
679    private int getColorScheme() {
680        return (mInputView != null)
681                ? mInputView.getColorScheme() : KeyboardView.COLOR_SCHEME_WHITE;
682    }
683
684    public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
685        if (isAutoCorrection != mIsAutoCorrectionActive) {
686            LatinKeyboardView keyboardView = getInputView();
687            mIsAutoCorrectionActive = isAutoCorrection;
688            keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard())
689                    .onAutoCorrectionStateChanged(isAutoCorrection));
690        }
691    }
692
693    private static boolean getSettingsKeyMode(SharedPreferences prefs, Context context) {
694        Resources resources = context.getResources();
695        final boolean showSettingsKeyOption = resources.getBoolean(
696                R.bool.config_enable_show_settings_key_option);
697        if (showSettingsKeyOption) {
698            final String settingsKeyMode = prefs.getString(Settings.PREF_SETTINGS_KEY,
699                    resources.getString(DEFAULT_SETTINGS_KEY_MODE));
700            // We show the settings key when 1) SETTINGS_KEY_MODE_ALWAYS_SHOW or
701            // 2) SETTINGS_KEY_MODE_AUTO and there are two or more enabled IMEs on the system
702            if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))
703                    || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO))
704                            && Utils.hasMultipleEnabledIMEsOrSubtypes(
705                                    ((InputMethodManager) context.getSystemService(
706                                            Context.INPUT_METHOD_SERVICE))))) {
707                return true;
708            }
709        }
710        return false;
711    }
712}
713