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