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