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