KeyboardSwitcher.java revision 5cd87e1b1c4258e8d016518914eccfbb4437cace
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.LatinImeLogger;
21import com.android.inputmethod.latin.R;
22import com.android.inputmethod.latin.Settings;
23import com.android.inputmethod.latin.SubtypeSwitcher;
24import com.android.inputmethod.latin.Utils;
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            if (mInputView == null) return;
161            final Keyboard oldKeyboard = mInputView.getKeyboard();
162            loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary, false);
163            final Keyboard newKeyboard = mInputView.getKeyboard();
164            if (newKeyboard.isAlphaKeyboard()) {
165                final boolean localeChanged = (oldKeyboard == null)
166                        || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
167                mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
168            }
169        } catch (RuntimeException e) {
170            Log.w(TAG, e);
171            LatinImeLogger.logOnException(mode + "," + imeOptions, e);
172        }
173    }
174
175    private void loadKeyboardInternal(int mode, int imeOptions, boolean voiceButtonEnabled,
176            boolean voiceButtonOnPrimary, boolean isSymbols) {
177        if (mInputView == null) return;
178
179        mMode = mode;
180        mImeOptions = imeOptions;
181        mVoiceKeyEnabled = voiceButtonEnabled;
182        mVoiceButtonOnPrimary = voiceButtonOnPrimary;
183        mIsSymbols = isSymbols;
184        // Update the settings key state because number of enabled IMEs could have been changed
185        mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService);
186        final KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols);
187
188        final Keyboard oldKeyboard = mInputView.getKeyboard();
189        if (oldKeyboard != null && oldKeyboard.mId.equals(id))
190            return;
191
192        makeSymbolsKeyboardIds();
193        mCurrentId = id;
194        mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
195        mInputView.setKeyboard(getKeyboard(id));
196    }
197
198    private LatinKeyboard getKeyboard(KeyboardId id) {
199        final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
200        LatinKeyboard keyboard = (ref == null) ? null : ref.get();
201        if (keyboard == null) {
202            final Locale savedLocale =  mSubtypeSwitcher.changeSystemLocale(
203                    mSubtypeSwitcher.getInputLocale());
204
205            keyboard = new LatinKeyboard(mInputMethodService, id);
206
207            if (id.mEnableShiftLock) {
208                keyboard.enableShiftLock();
209            }
210
211            mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard));
212            if (DEBUG)
213                Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": "
214                        + ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
215
216            mSubtypeSwitcher.changeSystemLocale(savedLocale);
217        } else if (DEBUG) {
218            Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT  id=" + id);
219        }
220
221        keyboard.onAutoCorrectionStateChanged(mIsAutoCorrectionActive);
222        keyboard.setShifted(false);
223        // If the cached keyboard had been switched to another keyboard while the language was
224        // displayed on its spacebar, it might have had arbitrary text fade factor. In such case,
225        // we should reset the text fade factor. It is also applicable to shortcut key.
226        keyboard.setSpacebarTextFadeFactor(0.0f, null);
227        keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutAvailable(), null);
228        return keyboard;
229    }
230
231    private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) {
232        final boolean hasVoiceKey = hasVoiceKey(isSymbols);
233        final int charColorId = getColorScheme();
234        final int xmlId;
235        final boolean enableShiftLock;
236
237        if (isSymbols) {
238            if (mode == KeyboardId.MODE_PHONE) {
239                xmlId = R.xml.kbd_phone_symbols;
240            } else if (mode == KeyboardId.MODE_NUMBER) {
241                // Note: MODE_NUMBER keyboard layout has no "switch alpha symbol" key.
242                xmlId = R.xml.kbd_number;
243            } else {
244                xmlId = R.xml.kbd_symbols;
245            }
246            enableShiftLock = false;
247        } else {
248            if (mode == KeyboardId.MODE_PHONE) {
249                xmlId = R.xml.kbd_phone;
250                enableShiftLock = false;
251            } else if (mode == KeyboardId.MODE_NUMBER) {
252                xmlId = R.xml.kbd_number;
253                enableShiftLock = false;
254            } else {
255                xmlId = R.xml.kbd_qwerty;
256                enableShiftLock = true;
257            }
258        }
259        final Resources res = mInputMethodService.getResources();
260        final int orientation = res.getConfiguration().orientation;
261        final Locale locale = mSubtypeSwitcher.getInputLocale();
262        return new KeyboardId(
263                res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, charColorId,
264                mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock);
265    }
266
267    public int getKeyboardMode() {
268        return mMode;
269    }
270
271    public boolean isAlphabetMode() {
272        return mCurrentId != null && mCurrentId.isAlphabetKeyboard();
273    }
274
275    public boolean isInputViewShown() {
276        return mInputView != null && mInputView.isShown();
277    }
278
279    public boolean isKeyboardAvailable() {
280        if (mInputView != null)
281            return mInputView.getLatinKeyboard() != null;
282        return false;
283    }
284
285    private LatinKeyboard getLatinKeyboard() {
286        if (mInputView != null)
287            return mInputView.getLatinKeyboard();
288        return null;
289    }
290
291    public void setPreferredLetters(int[] frequencies) {
292        LatinKeyboard latinKeyboard = getLatinKeyboard();
293        if (latinKeyboard != null)
294            latinKeyboard.setPreferredLetters(frequencies);
295    }
296
297    public void keyReleased() {
298        LatinKeyboard latinKeyboard = getLatinKeyboard();
299        if (latinKeyboard != null)
300            latinKeyboard.keyReleased();
301    }
302
303    public boolean isShiftedOrShiftLocked() {
304        LatinKeyboard latinKeyboard = getLatinKeyboard();
305        if (latinKeyboard != null)
306            return latinKeyboard.isShiftedOrShiftLocked();
307        return false;
308    }
309
310    public boolean isShiftLocked() {
311        LatinKeyboard latinKeyboard = getLatinKeyboard();
312        if (latinKeyboard != null)
313            return latinKeyboard.isShiftLocked();
314        return false;
315    }
316
317    public boolean isAutomaticTemporaryUpperCase() {
318        LatinKeyboard latinKeyboard = getLatinKeyboard();
319        if (latinKeyboard != null)
320            return latinKeyboard.isAutomaticTemporaryUpperCase();
321        return false;
322    }
323
324    public boolean isManualTemporaryUpperCase() {
325        LatinKeyboard latinKeyboard = getLatinKeyboard();
326        if (latinKeyboard != null)
327            return latinKeyboard.isManualTemporaryUpperCase();
328        return false;
329    }
330
331    private boolean isManualTemporaryUpperCaseFromAuto() {
332        LatinKeyboard latinKeyboard = getLatinKeyboard();
333        if (latinKeyboard != null)
334            return latinKeyboard.isManualTemporaryUpperCaseFromAuto();
335        return false;
336    }
337
338    private void setManualTemporaryUpperCase(boolean shifted) {
339        LatinKeyboard latinKeyboard = getLatinKeyboard();
340        if (latinKeyboard != null) {
341            // On non-distinct multi touch panel device, we should also turn off the shift locked
342            // state when shift key is pressed to go to normal mode.
343            // On the other hand, on distinct multi touch panel device, turning off the shift locked
344            // state with shift key pressing is handled by onReleaseShift().
345            if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
346                latinKeyboard.setShiftLocked(false);
347            }
348            if (latinKeyboard.setShifted(shifted)) {
349                mInputView.invalidateAllKeys();
350            }
351        }
352    }
353
354    private void setShiftLocked(boolean shiftLocked) {
355        LatinKeyboard latinKeyboard = getLatinKeyboard();
356        if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) {
357            mInputView.invalidateAllKeys();
358        }
359    }
360
361    /**
362     * Toggle keyboard shift state triggered by user touch event.
363     */
364    public void toggleShift() {
365        mInputMethodService.mHandler.cancelUpdateShiftState();
366        if (DEBUG_STATE)
367            Log.d(TAG, "toggleShift:"
368                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
369                    + " shiftKeyState=" + mShiftKeyState);
370        if (isAlphabetMode()) {
371            setManualTemporaryUpperCase(!isShiftedOrShiftLocked());
372        } else {
373            toggleShiftInSymbol();
374        }
375    }
376
377    public void toggleCapsLock() {
378        mInputMethodService.mHandler.cancelUpdateShiftState();
379        if (DEBUG_STATE)
380            Log.d(TAG, "toggleCapsLock:"
381                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
382                    + " shiftKeyState=" + mShiftKeyState);
383        if (isAlphabetMode()) {
384            if (isShiftLocked()) {
385                // Shift key is long pressed while caps lock state, we will toggle back to normal
386                // state. And mark as if shift key is released.
387                setShiftLocked(false);
388                mShiftKeyState.onRelease();
389            } else {
390                setShiftLocked(true);
391            }
392        }
393    }
394
395    private void setAutomaticTemporaryUpperCase() {
396        LatinKeyboard latinKeyboard = getLatinKeyboard();
397        if (latinKeyboard != null) {
398            latinKeyboard.setAutomaticTemporaryUpperCase();
399            mInputView.invalidateAllKeys();
400        }
401    }
402
403    /**
404     * Update keyboard shift state triggered by connected EditText status change.
405     */
406    public void updateShiftState() {
407        final ShiftKeyState shiftKeyState = mShiftKeyState;
408        if (DEBUG_STATE)
409            Log.d(TAG, "updateShiftState:"
410                    + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()
411                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
412                    + " shiftKeyState=" + shiftKeyState);
413        if (isAlphabetMode()) {
414            if (!isShiftLocked() && !shiftKeyState.isIgnoring()) {
415                if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
416                    // Only when shift key is releasing, automatic temporary upper case will be set.
417                    setAutomaticTemporaryUpperCase();
418                } else {
419                    setManualTemporaryUpperCase(shiftKeyState.isMomentary());
420                }
421            }
422        } else {
423            // In symbol keyboard mode, we should clear shift key state because only alphabet
424            // keyboard has shift key.
425            shiftKeyState.onRelease();
426        }
427    }
428
429    public void changeKeyboardMode() {
430        if (DEBUG_STATE)
431            Log.d(TAG, "changeKeyboardMode:"
432                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
433                    + " shiftKeyState=" + mShiftKeyState);
434        toggleKeyboardMode();
435        if (isShiftLocked() && isAlphabetMode())
436            setShiftLocked(true);
437        updateShiftState();
438    }
439
440    public void onPressShift() {
441        if (!isKeyboardAvailable())
442            return;
443        ShiftKeyState shiftKeyState = mShiftKeyState;
444        if (DEBUG_STATE)
445            Log.d(TAG, "onPressShift:"
446                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
447                    + " shiftKeyState=" + shiftKeyState);
448        if (isAlphabetMode()) {
449            if (isShiftLocked()) {
450                // Shift key is pressed while caps lock state, we will treat this state as shifted
451                // caps lock state and mark as if shift key pressed while normal state.
452                shiftKeyState.onPress();
453                setManualTemporaryUpperCase(true);
454            } else if (isAutomaticTemporaryUpperCase()) {
455                // Shift key is pressed while automatic temporary upper case, we have to move to
456                // manual temporary upper case.
457                shiftKeyState.onPress();
458                setManualTemporaryUpperCase(true);
459            } else if (isShiftedOrShiftLocked()) {
460                // In manual upper case state, we just record shift key has been pressing while
461                // shifted state.
462                shiftKeyState.onPressOnShifted();
463            } else {
464                // In base layout, chording or manual temporary upper case mode is started.
465                shiftKeyState.onPress();
466                toggleShift();
467            }
468        } else {
469            // In symbol mode, just toggle symbol and symbol more keyboard.
470            shiftKeyState.onPress();
471            toggleShift();
472        }
473    }
474
475    public void onReleaseShift() {
476        if (!isKeyboardAvailable())
477            return;
478        ShiftKeyState shiftKeyState = mShiftKeyState;
479        if (DEBUG_STATE)
480            Log.d(TAG, "onReleaseShift:"
481                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
482                    + " shiftKeyState=" + shiftKeyState);
483        if (isAlphabetMode()) {
484            if (shiftKeyState.isMomentary()) {
485                // After chording input while normal state.
486                toggleShift();
487            } else if (isShiftLocked() && !shiftKeyState.isIgnoring()) {
488                // Shift has been pressed without chording while caps lock state.
489                toggleCapsLock();
490            } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()) {
491                // Shift has been pressed without chording while shifted state.
492                toggleShift();
493            } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing()) {
494                // Shift has been pressed without chording while manual temporary upper case
495                // transited from automatic temporary upper case.
496                toggleShift();
497            }
498        }
499        shiftKeyState.onRelease();
500    }
501
502    public void onPressSymbol() {
503        if (DEBUG_STATE)
504            Log.d(TAG, "onPressSymbol:"
505                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
506                    + " symbolKeyState=" + mSymbolKeyState);
507        changeKeyboardMode();
508        mSymbolKeyState.onPress();
509        mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_MOMENTARY;
510    }
511
512    public void onReleaseSymbol() {
513        if (DEBUG_STATE)
514            Log.d(TAG, "onReleaseSymbol:"
515                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
516                    + " symbolKeyState=" + mSymbolKeyState);
517        // Snap back to the previous keyboard mode if the user chords the mode change key and
518        // other key, then released the mode change key.
519        if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_CHORDING)
520            changeKeyboardMode();
521        mSymbolKeyState.onRelease();
522    }
523
524    public void onOtherKeyPressed() {
525        if (DEBUG_STATE)
526            Log.d(TAG, "onOtherKeyPressed:"
527                    + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
528                    + " shiftKeyState=" + mShiftKeyState
529                    + " symbolKeyState=" + mSymbolKeyState);
530        mShiftKeyState.onOtherKeyPressed();
531        mSymbolKeyState.onOtherKeyPressed();
532    }
533
534    public void onCancelInput() {
535        // Snap back to the previous keyboard mode if the user cancels sliding input.
536        if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY && getPointerCount() == 1)
537            changeKeyboardMode();
538    }
539
540    private void toggleShiftInSymbol() {
541        if (isAlphabetMode())
542            return;
543        final LatinKeyboard keyboard;
544        if (mCurrentId.equals(mSymbolsId) || !mCurrentId.equals(mSymbolsShiftedId)) {
545            mCurrentId = mSymbolsShiftedId;
546            keyboard = getKeyboard(mCurrentId);
547            // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To
548            // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true).
549            // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is
550            // called.
551            keyboard.setShiftLocked(true);
552        } else {
553            mCurrentId = mSymbolsId;
554            keyboard = getKeyboard(mCurrentId);
555            // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
556            // indicator, we need to call enableShiftLock() and setShiftLocked(false).
557            keyboard.setShifted(false);
558        }
559        mInputView.setKeyboard(keyboard);
560    }
561
562    public boolean isInMomentaryAutoModeSwitchState() {
563        return mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY;
564    }
565
566    public boolean isVibrateAndSoundFeedbackRequired() {
567        return mInputView == null || !mInputView.isInSlidingKeyInput();
568    }
569
570    private int getPointerCount() {
571        return mInputView == null ? 0 : mInputView.getPointerCount();
572    }
573
574    private void toggleKeyboardMode() {
575        loadKeyboardInternal(mMode, mImeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary,
576                !mIsSymbols);
577        if (mIsSymbols) {
578            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
579        } else {
580            mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
581        }
582    }
583
584    public boolean hasDistinctMultitouch() {
585        return mInputView != null && mInputView.hasDistinctMultitouch();
586    }
587
588    /**
589     * Updates state machine to figure out when to automatically snap back to the previous mode.
590     */
591    public void onKey(int key) {
592        if (DEBUG_STATE)
593            Log.d(TAG, "onKey: code=" + key + " autoModeSwitchState=" + mAutoModeSwitchState
594                    + " pointers=" + getPointerCount());
595        switch (mAutoModeSwitchState) {
596        case AUTO_MODE_SWITCH_STATE_MOMENTARY:
597            // Only distinct multi touch devices can be in this state.
598            // On non-distinct multi touch devices, mode change key is handled by
599            // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and
600            // {@link LatinIME#onRelease}. So, on such devices, {@link #mAutoModeSwitchState} starts
601            // from {@link #AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN}, or
602            // {@link #AUTO_MODE_SWITCH_STATE_ALPHA}, not from
603            // {@link #AUTO_MODE_SWITCH_STATE_MOMENTARY}.
604            if (key == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
605                // Detected only the mode change key has been pressed, and then released.
606                if (mIsSymbols) {
607                    mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
608                } else {
609                    mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
610                }
611            } else if (getPointerCount() == 1) {
612                // Snap back to the previous keyboard mode if the user pressed the mode change key
613                // and slid to other key, then released the finger.
614                // If the user cancels the sliding input, snapping back to the previous keyboard
615                // mode is handled by {@link #onCancelInput}.
616                changeKeyboardMode();
617            } else {
618                // Chording input is being started. The keyboard mode will be snapped back to the
619                // previous mode in {@link onReleaseSymbol} when the mode change key is released.
620                mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_CHORDING;
621            }
622            break;
623        case AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN:
624            if (key != Keyboard.CODE_SPACE && key != Keyboard.CODE_ENTER && key >= 0) {
625                mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL;
626            }
627            break;
628        case AUTO_MODE_SWITCH_STATE_SYMBOL:
629            // Snap back to alpha keyboard mode if user types one or more non-space/enter
630            // characters followed by a space/enter.
631            if (key == Keyboard.CODE_ENTER || key == Keyboard.CODE_SPACE) {
632                changeKeyboardMode();
633            }
634            break;
635        }
636    }
637
638    public LatinKeyboardView getInputView() {
639        return mInputView;
640    }
641
642    public LatinKeyboardView onCreateInputView() {
643        createInputViewInternal(mLayoutId, true);
644        return mInputView;
645    }
646
647    private void createInputViewInternal(int newLayout, boolean forceReset) {
648        int layoutId = newLayout;
649        if (mLayoutId != layoutId || mInputView == null || forceReset) {
650            if (mInputView != null) {
651                mInputView.closing();
652            }
653            if (KEYBOARD_THEMES.length <= layoutId) {
654                layoutId = Integer.valueOf(sConfigDefaultKeyboardThemeId);
655            }
656
657            Utils.GCUtils.getInstance().reset();
658            boolean tryGC = true;
659            for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
660                try {
661                    mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater(
662                            ).inflate(KEYBOARD_THEMES[layoutId], null);
663                    tryGC = false;
664                } catch (OutOfMemoryError e) {
665                    Log.w(TAG, "load keyboard failed: " + e);
666                    tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
667                            mLayoutId + "," + layoutId, e);
668                } catch (InflateException e) {
669                    Log.w(TAG, "load keyboard failed: " + e);
670                    tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
671                            mLayoutId + "," + layoutId, e);
672                }
673            }
674            mInputView.setOnKeyboardActionListener(mInputMethodService);
675            mLayoutId = layoutId;
676        }
677    }
678
679    private void postSetInputView() {
680        mInputMethodService.mHandler.post(new Runnable() {
681            @Override
682            public void run() {
683                if (mInputView != null) {
684                    mInputMethodService.setInputView(mInputView);
685                }
686                mInputMethodService.updateInputViewShown();
687            }
688        });
689    }
690
691    @Override
692    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
693        if (PREF_KEYBOARD_LAYOUT.equals(key)) {
694            final int layoutId = Integer.valueOf(
695                    sharedPreferences.getString(key, sConfigDefaultKeyboardThemeId));
696            createInputViewInternal(layoutId, false);
697            postSetInputView();
698        } else if (Settings.PREF_SETTINGS_KEY.equals(key)) {
699            mHasSettingsKey = getSettingsKeyMode(sharedPreferences, mInputMethodService);
700            createInputViewInternal(mLayoutId, true);
701            postSetInputView();
702        }
703    }
704
705    private int getColorScheme() {
706        return (mInputView != null)
707                ? mInputView.getColorScheme() : KeyboardView.COLOR_SCHEME_WHITE;
708    }
709
710    public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
711        if (isAutoCorrection != mIsAutoCorrectionActive) {
712            LatinKeyboardView keyboardView = getInputView();
713            mIsAutoCorrectionActive = isAutoCorrection;
714            keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard())
715                    .onAutoCorrectionStateChanged(isAutoCorrection));
716        }
717    }
718
719    private static boolean getSettingsKeyMode(SharedPreferences prefs, Context context) {
720        Resources resources = context.getResources();
721        final boolean showSettingsKeyOption = resources.getBoolean(
722                R.bool.config_enable_show_settings_key_option);
723        if (showSettingsKeyOption) {
724            final String settingsKeyMode = prefs.getString(Settings.PREF_SETTINGS_KEY,
725                    resources.getString(DEFAULT_SETTINGS_KEY_MODE));
726            // We show the settings key when 1) SETTINGS_KEY_MODE_ALWAYS_SHOW or
727            // 2) SETTINGS_KEY_MODE_AUTO and there are two or more enabled IMEs on the system
728            if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))
729                    || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO))
730                            && Utils.hasMultipleEnabledIMEsOrSubtypes(
731                                    ((InputMethodManager) context.getSystemService(
732                                            Context.INPUT_METHOD_SERVICE))))) {
733                return true;
734            }
735        }
736        return false;
737    }
738}
739